├── .gitattributes ├── .gitignore ├── .nuget ├── NuGet.Config └── NuGet.exe ├── .travis.yml ├── LICENSE.md ├── LinqOptimizer.sln ├── README.md ├── RELEASE_NOTES.md ├── appveyor.yml ├── benchmarks ├── LinqOptimizer.Benchmarks.CSharp │ ├── LinqOptimizer.Benchmarks.CSharp.csproj │ ├── Program.cs │ └── results-csharp.txt └── LinqOptimizer.Benchmarks.FSharp │ ├── LinqOptimizer.Benchmarks.FSharp.fsproj │ ├── Program.fs │ └── results-fsharp.txt ├── build.cmd ├── build.fsx ├── build.sh ├── nuget ├── LinqOptimizer.CSharp.nuspec └── LinqOptimizer.FSharp.nuspec ├── src ├── LinqOptimizer.Base │ ├── LinqOptimizer.Base.csproj │ ├── ParallelQueryExpr.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── QueryExpr.cs ├── LinqOptimizer.CSharp │ ├── Extensions.cs │ ├── LinqOptimizer.CSharp.csproj │ ├── ParallelExtensions.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── QueryExpr.cs ├── LinqOptimizer.Core │ ├── AccessChecker.fs │ ├── AnonymousTypeEraser.fs │ ├── AssemblyInfo.fs │ ├── CSharpExpressionOptimizer.fs │ ├── Collector.fs │ ├── Compiler.fs │ ├── ConstantLiftingTransformer.fs │ ├── ExpressionHelpers.fs │ ├── ExpressionTransformer.fs │ ├── ExtensionMethods.fs │ ├── FSharpExpressionOptimizer.fs │ ├── FreeVariablesVisitor.fs │ ├── GroupingHelpers.fs │ ├── LinqOptimizer.Core.fsproj │ ├── ParallelismHelpers.fs │ ├── QueryExpr.fs │ ├── ReflectionHelpers.fs │ ├── Session.fs │ ├── SortingHelpers.fs │ ├── TupleElimination.fs │ ├── TypeCollectorVisitor.fs │ └── Utils.fs └── LinqOptimizer.FSharp │ ├── AssemblyInfo.fs │ ├── FSharpParallelQuery.fs │ ├── FSharpQuery.fs │ ├── LinqOptimizer.FSharp.fsproj │ └── QueryBuilder.fs └── tests ├── LinqOptimizer.Tests.CSharp ├── LinqOptimizer.Tests.CSharp.csproj ├── ParallelQueryTests.cs ├── QueryTests.cs └── packages.config └── LinqOptimizer.Tests.FSharp ├── App.config ├── LinqOptimizer.Tests.FSharp.fsproj ├── PQuery.fs ├── PrecompileHelpers.fs └── QueryExpr.fs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Unix style backup files 5 | *~ 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.sln.docstates 11 | *.userprefs 12 | 13 | # Build results 14 | 15 | [Dd]ebug/ 16 | [Rr]elease/ 17 | x64/ 18 | build/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | 22 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 23 | !packages/*/build/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | *_i.c 30 | *_p.c 31 | *.ilk 32 | *.meta 33 | *.obj 34 | *.pch 35 | *.pdb 36 | *.pgc 37 | *.pgd 38 | *.rsp 39 | *.sbr 40 | *.tlb 41 | *.tli 42 | *.tlh 43 | *.tmp 44 | *.tmp_proj 45 | *.log 46 | *.vspscc 47 | *.vssscc 48 | .builds 49 | *.pidb 50 | *.log 51 | *.scc 52 | 53 | # Visual C++ cache files 54 | ipch/ 55 | *.aps 56 | *.ncb 57 | *.opensdf 58 | *.sdf 59 | *.cachefile 60 | 61 | # Visual Studio profiler 62 | *.psess 63 | *.vsp 64 | *.vspx 65 | 66 | # Guidance Automation Toolkit 67 | *.gpState 68 | 69 | # ReSharper is a .NET coding add-in 70 | _ReSharper*/ 71 | *.[Rr]e[Ss]harper 72 | 73 | # TeamCity is a build add-in 74 | _TeamCity* 75 | 76 | # DotCover is a Code Coverage Tool 77 | *.dotCover 78 | 79 | # NCrunch 80 | *.ncrunch* 81 | .*crunch*.local.xml 82 | 83 | # Installshield output folder 84 | [Ee]xpress/ 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish/ 98 | 99 | # Publish Web Output 100 | *.Publish.xml 101 | 102 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 103 | !.nuget/NuGet.exe 104 | 105 | # Windows Azure Build Output 106 | csx 107 | *.build.csdef 108 | 109 | # Windows Store app package directory 110 | AppPackages/ 111 | 112 | # Others 113 | sql/ 114 | *.Cache 115 | ClientBin/ 116 | [Ss]tyle[Cc]op.* 117 | ~$* 118 | *~ 119 | *.dbmdl 120 | *.[Pp]ublish.xml 121 | *.pfx 122 | *.publishsettings 123 | 124 | # RIA/Silverlight projects 125 | Generated_Code/ 126 | 127 | # Backup & report files from converting an old project file to a newer 128 | # Visual Studio version. Backup files are not needed, because we have git ;-) 129 | _UpgradeReport_Files/ 130 | Backup*/ 131 | UpgradeLog*.XML 132 | UpgradeLog*.htm 133 | 134 | # SQL Server files 135 | App_Data/*.mdf 136 | App_Data/*.ldf 137 | 138 | 139 | #LightSwitch generated files 140 | GeneratedArtifacts/ 141 | _Pvt_Extensions/ 142 | ModelManifest.xml 143 | 144 | # ========================= 145 | # Windows detritus 146 | # ========================= 147 | 148 | # Windows image file caches 149 | Thumbs.db 150 | ehthumbs.db 151 | 152 | # Folder config file 153 | Desktop.ini 154 | 155 | # Recycle Bin used on file shares 156 | $RECYCLE.BIN/ 157 | 158 | # Mac desktop service store files 159 | .DS_Store 160 | 161 | # =================================================== 162 | # Exclude F# project specific directories and files 163 | # =================================================== 164 | 165 | # NuGet Packages Directory 166 | packages/ 167 | 168 | # Generated documentation folder 169 | docs/output/ 170 | 171 | # Temp folder used for publishing docs 172 | temp/ 173 | 174 | # Test results produced by build 175 | TestResults.xml 176 | 177 | # Nuget outputs 178 | nuget/*.nupkg 179 | 180 | paket-files/ 181 | .fake/ 182 | temp/ 183 | .vs/ 184 | artifacts/ -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nessos/LinqOptimizer/2803f13258a27649bffb2d4691e8525909e28bed/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | env: 4 | matrix: 5 | - MONO_VERSION="3.4.0" 6 | 7 | install: 8 | - wget "http://download.mono-project.com/archive/${MONO_VERSION}/macos-10-x86/MonoFramework-MDK-${MONO_VERSION}.macos10.xamarin.x86.pkg" 9 | - sudo installer -pkg "MonoFramework-MDK-${MONO_VERSION}.macos10.xamarin.x86.pkg" -target / 10 | 11 | script: 12 | - ./build.sh Release-Mono 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2013 2 | 3 | Nick Palladinos 4 | Nessos Information Technologies SA 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 19 | Apache License, Version 2.0 20 | =========================== 21 | 22 | Apache License 23 | Version 2.0, January 2004 24 | http://www.apache.org/licenses/ 25 | 26 | ------------------------------------------------------------ 27 | 28 | ### TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 29 | 30 | 31 | **1. Definitions.** 32 | 33 | - "License" shall mean the terms and conditions for use, reproduction, 34 | and distribution as defined by Sections 1 through 9 of this document. 35 | 36 | - "Licensor" shall mean the copyright owner or entity authorized by 37 | the copyright owner that is granting the License. 38 | 39 | - "Legal Entity" shall mean the union of the acting entity and all 40 | other entities that control, are controlled by, or are under common 41 | control with that entity. For the purposes of this definition, 42 | "control" means (i) the power, direct or indirect, to cause the 43 | direction or management of such entity, whether by contract or 44 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 45 | outstanding shares, or (iii) beneficial ownership of such entity. 46 | 47 | - "You" (or "Your") shall mean an individual or Legal Entity 48 | exercising permissions granted by this License. 49 | 50 | - "Source" form shall mean the preferred form for making modifications, 51 | including but not limited to software source code, documentation 52 | source, and configuration files. 53 | 54 | - "Object" form shall mean any form resulting from mechanical 55 | transformation or translation of a Source form, including but 56 | not limited to compiled object code, generated documentation, 57 | and conversions to other media types. 58 | 59 | - "Work" shall mean the work of authorship, whether in Source or 60 | Object form, made available under the License, as indicated by a 61 | copyright notice that is included in or attached to the work 62 | (an example is provided in the Appendix below). 63 | 64 | - "Derivative Works" shall mean any work, whether in Source or Object 65 | form, that is based on (or derived from) the Work and for which the 66 | editorial revisions, annotations, elaborations, or other modifications 67 | represent, as a whole, an original work of authorship. For the purposes 68 | of this License, Derivative Works shall not include works that remain 69 | separable from, or merely link (or bind by name) to the interfaces of, 70 | the Work and Derivative Works thereof. 71 | 72 | - "Contribution" shall mean any work of authorship, including 73 | the original version of the Work and any modifications or additions 74 | to that Work or Derivative Works thereof, that is intentionally 75 | submitted to Licensor for inclusion in the Work by the copyright owner 76 | or by an individual or Legal Entity authorized to submit on behalf of 77 | the copyright owner. For the purposes of this definition, "submitted" 78 | means any form of electronic, verbal, or written communication sent 79 | to the Licensor or its representatives, including but not limited to 80 | communication on electronic mailing lists, source code control systems, 81 | and issue tracking systems that are managed by, or on behalf of, the 82 | Licensor for the purpose of discussing and improving the Work, but 83 | excluding communication that is conspicuously marked or otherwise 84 | designated in writing by the copyright owner as "Not a Contribution." 85 | 86 | - "Contributor" shall mean Licensor and any individual or Legal Entity 87 | on behalf of whom a Contribution has been received by Licensor and 88 | subsequently incorporated within the Work. 89 | 90 | **2. Grant of Copyright License.** 91 | Subject to the terms and conditions of 92 | this License, each Contributor hereby grants to You a perpetual, 93 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 94 | copyright license to reproduce, prepare Derivative Works of, 95 | publicly display, publicly perform, sublicense, and distribute the 96 | Work and such Derivative Works in Source or Object form. 97 | 98 | **3. Grant of Patent License.** 99 | Subject to the terms and conditions of 100 | this License, each Contributor hereby grants to You a perpetual, 101 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 102 | (except as stated in this section) patent license to make, have made, 103 | use, offer to sell, sell, import, and otherwise transfer the Work, 104 | where such license applies only to those patent claims licensable 105 | by such Contributor that are necessarily infringed by their 106 | Contribution(s) alone or by combination of their Contribution(s) 107 | with the Work to which such Contribution(s) was submitted. If You 108 | institute patent litigation against any entity (including a 109 | cross-claim or counterclaim in a lawsuit) alleging that the Work 110 | or a Contribution incorporated within the Work constitutes direct 111 | or contributory patent infringement, then any patent licenses 112 | granted to You under this License for that Work shall terminate 113 | as of the date such litigation is filed. 114 | 115 | **4. Redistribution.** 116 | You may reproduce and distribute copies of the 117 | Work or Derivative Works thereof in any medium, with or without 118 | modifications, and in Source or Object form, provided that You 119 | meet the following conditions: 120 | 121 | - You must give any other recipients of the Work or 122 | Derivative Works a copy of this License; and 123 | 124 | - You must cause any modified files to carry prominent notices 125 | stating that You changed the files; and 126 | 127 | - You must retain, in the Source form of any Derivative Works 128 | that You distribute, all copyright, patent, trademark, and 129 | attribution notices from the Source form of the Work, 130 | excluding those notices that do not pertain to any part of 131 | the Derivative Works; and 132 | 133 | - If the Work includes a "NOTICE" text file as part of its 134 | distribution, then any Derivative Works that You distribute must 135 | include a readable copy of the attribution notices contained 136 | within such NOTICE file, excluding those notices that do not 137 | pertain to any part of the Derivative Works, in at least one 138 | of the following places: within a NOTICE text file distributed 139 | as part of the Derivative Works; within the Source form or 140 | documentation, if provided along with the Derivative Works; or, 141 | within a display generated by the Derivative Works, if and 142 | wherever such third-party notices normally appear. The contents 143 | of the NOTICE file are for informational purposes only and 144 | do not modify the License. You may add Your own attribution 145 | notices within Derivative Works that You distribute, alongside 146 | or as an addendum to the NOTICE text from the Work, provided 147 | that such additional attribution notices cannot be construed 148 | as modifying the License. 149 | 150 | You may add Your own copyright statement to Your modifications and 151 | may provide additional or different license terms and conditions 152 | for use, reproduction, or distribution of Your modifications, or 153 | for any such Derivative Works as a whole, provided Your use, 154 | reproduction, and distribution of the Work otherwise complies with 155 | the conditions stated in this License. 156 | 157 | **5. Submission of Contributions.** 158 | Unless You explicitly state otherwise, 159 | any Contribution intentionally submitted for inclusion in the Work 160 | by You to the Licensor shall be under the terms and conditions of 161 | this License, without any additional terms or conditions. 162 | Notwithstanding the above, nothing herein shall supersede or modify 163 | the terms of any separate license agreement you may have executed 164 | with Licensor regarding such Contributions. 165 | 166 | **6. Trademarks.** 167 | This License does not grant permission to use the trade 168 | names, trademarks, service marks, or product names of the Licensor, 169 | except as required for reasonable and customary use in describing the 170 | origin of the Work and reproducing the content of the NOTICE file. 171 | 172 | **7. Disclaimer of Warranty.** 173 | Unless required by applicable law or 174 | agreed to in writing, Licensor provides the Work (and each 175 | Contributor provides its Contributions) on an "AS IS" BASIS, 176 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 177 | implied, including, without limitation, any warranties or conditions 178 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 179 | PARTICULAR PURPOSE. You are solely responsible for determining the 180 | appropriateness of using or redistributing the Work and assume any 181 | risks associated with Your exercise of permissions under this License. 182 | 183 | **8. Limitation of Liability.** 184 | In no event and under no legal theory, 185 | whether in tort (including negligence), contract, or otherwise, 186 | unless required by applicable law (such as deliberate and grossly 187 | negligent acts) or agreed to in writing, shall any Contributor be 188 | liable to You for damages, including any direct, indirect, special, 189 | incidental, or consequential damages of any character arising as a 190 | result of this License or out of the use or inability to use the 191 | Work (including but not limited to damages for loss of goodwill, 192 | work stoppage, computer failure or malfunction, or any and all 193 | other commercial damages or losses), even if such Contributor 194 | has been advised of the possibility of such damages. 195 | 196 | **9. Accepting Warranty or Additional Liability.** 197 | While redistributing 198 | the Work or Derivative Works thereof, You may choose to offer, 199 | and charge a fee for, acceptance of support, warranty, indemnity, 200 | or other liability obligations and/or rights consistent with this 201 | License. However, in accepting such obligations, You may act only 202 | on Your own behalf and on Your sole responsibility, not on behalf 203 | of any other Contributor, and only if You agree to indemnify, 204 | defend, and hold each Contributor harmless for any liability 205 | incurred by, or claims asserted against, such Contributor by reason 206 | of your accepting any such warranty or additional liability. 207 | -------------------------------------------------------------------------------- /LinqOptimizer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9C33CACA-C699-4C93-BA82-9AE99C9A0D3C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8D1A3558-9D76-4744-BDFE-AD25F8A1EE30}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{EC71519F-9DEA-49C1-9A29-8D97093C6BBF}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinqOptimizer.Tests.CSharp", "tests\LinqOptimizer.Tests.CSharp\LinqOptimizer.Tests.CSharp.csproj", "{1E812A89-C8F1-463E-8E1E-765389A0A4BC}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LinqOptimizer.Tests.FSharp", "tests\LinqOptimizer.Tests.FSharp\LinqOptimizer.Tests.FSharp.fsproj", "{38A228D2-764C-4103-9AC2-58452630CBA2}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinqOptimizer.Base", "src\LinqOptimizer.Base\LinqOptimizer.Base.csproj", "{F21F83A3-BDBD-4965-87D6-94E65A069A7B}" 17 | EndProject 18 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LinqOptimizer.Core", "src\LinqOptimizer.Core\LinqOptimizer.Core.fsproj", "{6FEA9D47-1291-40CE-9716-619BCC2F485A}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinqOptimizer.CSharp", "src\LinqOptimizer.CSharp\LinqOptimizer.CSharp.csproj", "{E606C2C2-C219-4E51-B928-5B192E283A54}" 21 | EndProject 22 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LinqOptimizer.FSharp", "src\LinqOptimizer.FSharp\LinqOptimizer.FSharp.fsproj", "{5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinqOptimizer.Benchmarks.CSharp", "benchmarks\LinqOptimizer.Benchmarks.CSharp\LinqOptimizer.Benchmarks.CSharp.csproj", "{2BB3E227-CB90-4FFD-9763-4DCD00B61D79}" 25 | EndProject 26 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LinqOptimizer.Benchmarks.FSharp", "benchmarks\LinqOptimizer.Benchmarks.FSharp\LinqOptimizer.Benchmarks.FSharp.fsproj", "{99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}" 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{3C6ADEC7-0540-4614-8454-3D8C7FD44C42}" 29 | ProjectSection(SolutionItems) = preProject 30 | .travis.yml = .travis.yml 31 | appveyor.yml = appveyor.yml 32 | build.cmd = build.cmd 33 | build.fsx = build.fsx 34 | build.sh = build.sh 35 | LICENSE.md = LICENSE.md 36 | nuget\LinqOptimizer.CSharp.nuspec = nuget\LinqOptimizer.CSharp.nuspec 37 | nuget\LinqOptimizer.FSharp.nuspec = nuget\LinqOptimizer.FSharp.nuspec 38 | README.md = README.md 39 | RELEASE_NOTES.md = RELEASE_NOTES.md 40 | EndProjectSection 41 | EndProject 42 | Global 43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 44 | Debug|Any CPU = Debug|Any CPU 45 | Release|Any CPU = Release|Any CPU 46 | Release-Mono|Any CPU = Release-Mono|Any CPU 47 | EndGlobalSection 48 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 49 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 54 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 55 | {38A228D2-764C-4103-9AC2-58452630CBA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {38A228D2-764C-4103-9AC2-58452630CBA2}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {38A228D2-764C-4103-9AC2-58452630CBA2}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {38A228D2-764C-4103-9AC2-58452630CBA2}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {38A228D2-764C-4103-9AC2-58452630CBA2}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 60 | {38A228D2-764C-4103-9AC2-58452630CBA2}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 61 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 66 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 67 | {6FEA9D47-1291-40CE-9716-619BCC2F485A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {6FEA9D47-1291-40CE-9716-619BCC2F485A}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {6FEA9D47-1291-40CE-9716-619BCC2F485A}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {6FEA9D47-1291-40CE-9716-619BCC2F485A}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {6FEA9D47-1291-40CE-9716-619BCC2F485A}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 72 | {6FEA9D47-1291-40CE-9716-619BCC2F485A}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 73 | {E606C2C2-C219-4E51-B928-5B192E283A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 74 | {E606C2C2-C219-4E51-B928-5B192E283A54}.Debug|Any CPU.Build.0 = Debug|Any CPU 75 | {E606C2C2-C219-4E51-B928-5B192E283A54}.Release|Any CPU.ActiveCfg = Release|Any CPU 76 | {E606C2C2-C219-4E51-B928-5B192E283A54}.Release|Any CPU.Build.0 = Release|Any CPU 77 | {E606C2C2-C219-4E51-B928-5B192E283A54}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 78 | {E606C2C2-C219-4E51-B928-5B192E283A54}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 79 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 80 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}.Debug|Any CPU.Build.0 = Debug|Any CPU 81 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 82 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}.Release|Any CPU.Build.0 = Release|Any CPU 83 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 84 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 85 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 86 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79}.Debug|Any CPU.Build.0 = Debug|Any CPU 87 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79}.Release|Any CPU.ActiveCfg = Release|Any CPU 88 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79}.Release|Any CPU.Build.0 = Release|Any CPU 89 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 90 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 91 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 92 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}.Debug|Any CPU.Build.0 = Debug|Any CPU 93 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}.Release|Any CPU.ActiveCfg = Release|Any CPU 94 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}.Release|Any CPU.Build.0 = Release|Any CPU 95 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}.Release-Mono|Any CPU.ActiveCfg = Release|Any CPU 96 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F}.Release-Mono|Any CPU.Build.0 = Release|Any CPU 97 | EndGlobalSection 98 | GlobalSection(SolutionProperties) = preSolution 99 | HideSolutionNode = FALSE 100 | EndGlobalSection 101 | GlobalSection(NestedProjects) = preSolution 102 | {1E812A89-C8F1-463E-8E1E-765389A0A4BC} = {8D1A3558-9D76-4744-BDFE-AD25F8A1EE30} 103 | {38A228D2-764C-4103-9AC2-58452630CBA2} = {8D1A3558-9D76-4744-BDFE-AD25F8A1EE30} 104 | {F21F83A3-BDBD-4965-87D6-94E65A069A7B} = {9C33CACA-C699-4C93-BA82-9AE99C9A0D3C} 105 | {6FEA9D47-1291-40CE-9716-619BCC2F485A} = {9C33CACA-C699-4C93-BA82-9AE99C9A0D3C} 106 | {E606C2C2-C219-4E51-B928-5B192E283A54} = {9C33CACA-C699-4C93-BA82-9AE99C9A0D3C} 107 | {5C5FE9F9-91DF-44E2-A68D-B9A49828E0BB} = {9C33CACA-C699-4C93-BA82-9AE99C9A0D3C} 108 | {2BB3E227-CB90-4FFD-9763-4DCD00B61D79} = {EC71519F-9DEA-49C1-9A29-8D97093C6BBF} 109 | {99EC4E0D-C8F6-40DC-97D1-1E30A261E67F} = {EC71519F-9DEA-49C1-9A29-8D97093C6BBF} 110 | EndGlobalSection 111 | GlobalSection(ExtensibilityGlobals) = postSolution 112 | SolutionGuid = {151A705D-0F2B-4C7B-B845-BBE63F2B48BC} 113 | EndGlobalSection 114 | EndGlobal 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinqOptimizer 2 | 3 | An automatic query optimizer-compiler for Sequential and Parallel LINQ. 4 | 5 | More information [here](http://nessos.github.io/LinqOptimizer/). 6 | 7 | ### Build Status 8 | 9 | Head (branch `master`), Build & Unit tests 10 | 11 | * Windows/.NET [![Build status](https://ci.appveyor.com/api/projects/status/w1avtn54cl6f4eo8/branch/master)](https://ci.appveyor.com/project/nessos/linqoptimizer) 12 | * Mac OS X/Mono 3.2.x [![Build Status](https://travis-ci.org/nessos/LinqOptimizer.png?branch=master)](https://travis-ci.org/nessos/LinqOptimizer/branches) 13 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 0.7.0 2 | * Target netstandard2.0 3 | 4 | #### 0.6.3 5 | * Bug fixes. 6 | * Minor code updates. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | configuration: Release 2 | test: 3 | assemblies: 4 | - LinqOptimizer.Tests.CSharp.exe 5 | - LinqOptimizer.Tests.FSharp.exe 6 | -------------------------------------------------------------------------------- /benchmarks/LinqOptimizer.Benchmarks.CSharp/LinqOptimizer.Benchmarks.CSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2;net461 5 | AnyCPU 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /benchmarks/LinqOptimizer.Benchmarks.CSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using BenchmarkDotNet.Attributes; 4 | using BenchmarkDotNet.Columns; 5 | using BenchmarkDotNet.Configs; 6 | using BenchmarkDotNet.Exporters; 7 | using BenchmarkDotNet.Jobs; 8 | using BenchmarkDotNet.Loggers; 9 | using BenchmarkDotNet.Running; 10 | using Nessos.LinqOptimizer.CSharp; 11 | 12 | namespace LinqOptimizer.Benchmarks.CSharp 13 | { 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | typeof(SequentialBenchmarks) 19 | .GetNestedTypes() 20 | .Concat(typeof(ParallelBenchmarks).GetNestedTypes()) 21 | .ToList() 22 | .ForEach(type => BenchmarkRunner.Run(type, new CustomConfig())); 23 | } 24 | } 25 | 26 | internal class CustomConfig : ManualConfig 27 | { 28 | public CustomConfig() 29 | { 30 | Add(Job.Default.WithLaunchCount(1)); 31 | Add(StatisticColumn.Median, StatisticColumn.StdDev); 32 | Add(MarkdownExporter.GitHub); 33 | Add(new ConsoleLogger()); 34 | } 35 | } 36 | 37 | public class BenchmarkBase 38 | { 39 | // the size used to be 200000000 but currently BenchmarkDotNet does not support gcAllowVeryLargeObjects (https://github.com/PerfDotNet/BenchmarkDotNet/issues/76) 40 | [Params(0, 10, 100, 10000, 1000000)] 41 | public int Count; 42 | 43 | const int useSameSeedEveryTimeToHaveSameData = 08041988; 44 | protected Random rnd = new Random(useSameSeedEveryTimeToHaveSameData); 45 | 46 | protected double[] values; 47 | 48 | [GlobalSetup] 49 | public virtual void SetUp() 50 | { 51 | values = Enumerable.Range(1, Count).Select(x => rnd.NextDouble()).ToArray(); 52 | } 53 | } 54 | 55 | public class SequentialBenchmarks 56 | { 57 | public class SumBechmarks : BenchmarkBase 58 | { 59 | [Benchmark(Description = "Sum Linq", Baseline = true)] 60 | public double SumLinq() 61 | { 62 | return values.Sum(); 63 | } 64 | 65 | [Benchmark(Description = "Sum Opt")] 66 | public double SumLinqOpt() 67 | { 68 | return values.AsQueryExpr().Sum().Run(); 69 | } 70 | } 71 | 72 | public class SumOfSquaresBechmarks : BenchmarkBase 73 | { 74 | [Benchmark(Description = "Sum of Squares Linq", Baseline = true)] 75 | public double SumSqLinq() 76 | { 77 | return values.Select(x => x * x).Sum(); 78 | } 79 | 80 | [Benchmark(Description = "Sum of Squares Opt")] 81 | public double SumSqLinqOpt() 82 | { 83 | return values.AsQueryExpr().Select(x => x * x).Sum().Run(); 84 | } 85 | } 86 | 87 | public class CartesianBenchmarks : BenchmarkBase 88 | { 89 | private double[] dim1, dim2; 90 | 91 | [GlobalSetup] 92 | public override void SetUp() 93 | { 94 | base.SetUp(); 95 | 96 | dim1 = values.Take(values.Length / 10).ToArray(); 97 | dim2 = values.Take(20).ToArray(); 98 | } 99 | 100 | [Benchmark(Description = "Cartesian Linq", Baseline = true)] 101 | public double CartLinq() 102 | { 103 | return (from x in dim1 104 | from y in dim2 105 | select x * y).Sum(); 106 | } 107 | 108 | [Benchmark(Description = "Cartesian Opt")] 109 | public double CartLinqOpt() 110 | { 111 | return (from x in dim1.AsQueryExpr() 112 | from y in dim2 113 | select x * y).Sum().Run(); 114 | } 115 | } 116 | 117 | public class GroupByBenchmarks : BenchmarkBase 118 | { 119 | [GlobalSetup] 120 | public override void SetUp() 121 | { 122 | base.SetUp(); 123 | 124 | values = 125 | Enumerable.Range(1, Count).Select(x => 100000000 * rnd.NextDouble() - 50000000).ToArray(); 126 | } 127 | 128 | [Benchmark(Description = "Group By Linq", Baseline = true)] 129 | public int[] GroupLinq() 130 | { 131 | return values 132 | .GroupBy(x => (int)x / 100) 133 | .OrderBy(x => x.Key) 134 | .Select(k => k.Count()) 135 | .ToArray(); 136 | } 137 | 138 | [Benchmark(Description = "Group By Opt")] 139 | public int[] GroupByOpt() 140 | { 141 | return values.AsQueryExpr() 142 | .GroupBy(x => (int)x / 100) 143 | .OrderBy(x => x.Key) 144 | .Select(k => k.Count()) 145 | .ToArray() 146 | .Run(); 147 | } 148 | } 149 | 150 | public class PythagoreanTriplesBenchmarks 151 | { 152 | [Params(0, 10, 100, 1000)] 153 | public int max = 1000; 154 | 155 | [Benchmark(Description = "Pythagorean Triples Linq", Baseline = true)] 156 | public int PythagoreanTriplesLinq() 157 | { 158 | return (from a in Enumerable.Range(1, max + 1) 159 | from b in Enumerable.Range(a, max + 1 - a) 160 | from c in Enumerable.Range(b, max + 1 - b) 161 | where a * a + b * b == c * c 162 | select true).Count(); 163 | } 164 | 165 | [Benchmark(Description = "Pythagorean Triples Opt")] 166 | public int PythagoreanTriplesLinqOpt() 167 | { 168 | return (from a in QueryExpr.Range(1, max + 1) 169 | from b in Enumerable.Range(a, max + 1 - a) 170 | from c in Enumerable.Range(b, max + 1 - b) 171 | where a * a + b * b == c * c 172 | select true).Count().Run(); 173 | } 174 | } 175 | } 176 | 177 | public class ParallelBenchmarks 178 | { 179 | public class ParallelSumBenchmarks : BenchmarkBase 180 | { 181 | [Benchmark(Description = "Parallel Sum Linq", Baseline = true)] 182 | public double ParallelSumLinq() 183 | { 184 | return values.AsParallel().Sum(); 185 | } 186 | 187 | [Benchmark(Description = "Parallel Sum Opt")] 188 | public double ParallelSumOpt() 189 | { 190 | return values.AsParallelQueryExpr().Sum().Run(); 191 | } 192 | } 193 | 194 | public class ParallelSumOfSquaresBenchmark : BenchmarkBase 195 | { 196 | [Benchmark(Description = "Parallel Sum of Squares Linq", Baseline = true)] 197 | public double ParallelSumSqLinq() 198 | { 199 | return values.AsParallel().Select(x => x * x).Sum(); 200 | } 201 | 202 | [Benchmark(Description = "Parallel Sum of Squares Opt")] 203 | public double ParallelSumSqLinqOpt() 204 | { 205 | return values.AsParallelQueryExpr().Select(x => x * x).Sum().Run(); 206 | } 207 | } 208 | 209 | public class ParallelCartesianBenchmarks : BenchmarkBase 210 | { 211 | private double[] dim1, dim2; 212 | 213 | [GlobalSetup] 214 | public override void SetUp() 215 | { 216 | base.SetUp(); 217 | 218 | dim1 = values.Take(values.Length / 10).ToArray(); 219 | dim2 = values.Take(20).ToArray(); 220 | } 221 | 222 | [Benchmark(Description = "Parallel Cartesian Linq", Baseline = true)] 223 | public double ParallelCartLinq() 224 | { 225 | return (from x in dim1.AsParallel() 226 | from y in dim2 227 | select x * y).Sum(); 228 | } 229 | 230 | [Benchmark(Description = "Parallel Cartesian Opt")] 231 | public double ParallelCartLinqOpt() 232 | { 233 | return (from x in dim1.AsParallelQueryExpr() 234 | from y in dim2 235 | select x * y).Sum().Run(); 236 | } 237 | } 238 | 239 | public class ParallelGroupByBenchmarks : BenchmarkBase 240 | { 241 | [GlobalSetup] 242 | public override void SetUp() 243 | { 244 | values = Enumerable.Range(1, Count).Select(x => 100000000 * rnd.NextDouble() - 50000000).ToArray(); 245 | } 246 | 247 | [Benchmark(Description = "Parallel Group By Linq", Baseline = true)] 248 | public int[] ParallelGroupLinq() 249 | { 250 | return values.AsParallel() 251 | .GroupBy(x => (int)x / 100) 252 | .OrderBy(x => x.Key) 253 | .Select(k => k.Count()) 254 | .ToArray(); 255 | } 256 | 257 | [Benchmark(Description = "Parallel Group By Opt")] 258 | public int[] ParallelGroupLinqOpt() 259 | { 260 | return values.AsParallelQueryExpr() 261 | .GroupBy(x => (int)x / 100) 262 | .OrderBy(x => x.Key) 263 | .Select(k => k.Count()) 264 | .ToArray() 265 | .Run(); 266 | } 267 | } 268 | 269 | public class ParallelPythagoreanTriplesBenchmarks 270 | { 271 | [Params(0, 10, 100, 1000)] 272 | public int max = 1000; 273 | 274 | [Benchmark(Description = "Parallel Pythagorean Triples Linq", Baseline = true)] 275 | public int ParallelPythagoreanTriplesLinq() 276 | { 277 | return (from a in Enumerable.Range(1, max + 1).AsParallel() 278 | from b in Enumerable.Range(a, max + 1 - a) 279 | from c in Enumerable.Range(b, max + 1 - b) 280 | where a * a + b * b == c * c 281 | select true).Count(); 282 | } 283 | 284 | [Benchmark(Description = "Parallel Pythagorean Triples Opt")] 285 | public int ParallelPythagoreanTriplesLinqOpt() 286 | { 287 | return (from a in Enumerable.Range(1, max + 1).AsParallelQueryExpr() 288 | from b in Enumerable.Range(a, max + 1 - a) 289 | from c in Enumerable.Range(b, max + 1 - b) 290 | where a * a + b * b == c * c 291 | select true).Count().Run(); 292 | } 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /benchmarks/LinqOptimizer.Benchmarks.CSharp/results-csharp.txt: -------------------------------------------------------------------------------- 1 | // CLR - .NET 4.5.1 2 | 3 | PS C:\Users\krontogiannis\Desktop\Release> .\LinqOptimizer.Benchmarks.CSharp.exe 4 | "Sum Linq": 00:00:02.4882719 5 | "Sum Opt": 00:00:01.3065410 6 | Validate : True 7 | 8 | "Sum of Squares Linq": 00:00:05.2756961 9 | "Sum of Squares Opt": 00:00:00.9198907 10 | Validate : True 11 | 12 | "Cartesian Linq": 00:00:22.9570171 13 | "Cartesian Opt": 00:00:01.7985289 14 | Validate : True 15 | 16 | "GroupBy Linq": 00:00:30.8481043 17 | "GroupBy Opt": 00:00:24.1036471 18 | Validate : True 19 | 20 | "Pythagorean Triples Linq": 00:00:11.3666039 21 | "Pythagorean Triples Opt": 00:00:00.5531833 22 | Validate : True 23 | 24 | "Parallel Sum Linq": 00:00:00.5915235 25 | "Parallel Sum Opt": 00:00:00.6187390 26 | Validate : True 27 | 28 | "Parallel Sum of Squares Linq": 00:00:00.7773579 29 | "Parallel Sum of Squares Opt": 00:00:00.3443493 30 | Validate : True 31 | 32 | "Parallel Cartesian Linq": 00:00:04.1006209 33 | "Parallel Cartesian Opt": 00:00:00.2822599 34 | Validate : True 35 | 36 | "Parallel GroupBy Linq": 00:00:19.1792742 37 | "Parallel GroupBy Opt": 00:00:10.9898634 38 | Validate : True 39 | 40 | "Parallel Pythagorean Triples Linq": 00:00:06.1770607 41 | "Parallel Pythagorean Triples Opt": 00:00:00.1449303 42 | Validate : True 43 | 44 | 45 | 46 | krontogiannis@benchlinux:~> mono -V 47 | Mono JIT compiler version 3.2.3 (tarball Sat Sep 14 19:35:34 EDT 2013) 48 | Copyright (C) 2002-2012 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com 49 | TLS: __thread 50 | SIGSEGV: altstack 51 | Notifications: epoll 52 | Architecture: amd64 53 | Disabled: none 54 | Misc: softdebug 55 | LLVM: supported, not enabled. 56 | GC: sgen 57 | 58 | ## krontogiannis@benchmarks-suse:~> mono LinqOptimizer.Benchmarks.CSharp.exe --gc=boehm 59 | ## "Sum Linq": 00:00:05.6977650 60 | ## "Sum Opt": 00:00:01.5374375 61 | ## Validate : True 62 | ## 63 | ## "Sum of Squares Linq": 00:00:15.5541196 64 | ## "Sum of Squares Opt": 00:00:01.4809488 65 | ## Validate : True 66 | ## 67 | ## "Cartesian Linq": 00:00:33.3545918 68 | ## "Cartesian Opt": 00:00:03.2554914 69 | ## Validate : True 70 | ## 71 | ## "GroupBy Linq": INF 72 | ## "GroupBy Opt": 00:00:14.4638251 73 | ## Validate : True 74 | ## 75 | ## "Pythagorean Triples Linq": 00:00:41.2675174 76 | ## "Pythagorean Triples Opt": 00:00:00.9898223 77 | ## Validate : True 78 | ## 79 | ## "Parallel Sum Linq": 00:00:38.6001130 80 | ## "Parallel Sum Opt": 00:00:04.6659863 81 | ## Validate : False 82 | ## Values 100002834.384839, 100002834.384842 83 | ## 84 | ## "Parallel Sum of Squares Linq": 00:00:39.3777961 85 | ## "Parallel Sum of Squares Opt": 00:00:01.9471529 86 | ## Validate : False 87 | ## Values 66669094.7481553, 66669094.7481595 88 | ## 89 | ## "Parallel Cartesian Linq": 00:01:19.0215583 90 | ## "Parallel Cartesian Opt": 00:00:00.5005809 91 | ## Validate : False 92 | ## Values 119693758.794496, 119693758.794503 93 | ## 94 | ## "Parallel GroupBy Linq": 00:01:03.8937090 95 | ## "Parallel GroupBy Opt": 00:00:20.0196676 96 | ## Validate : True 97 | ## 98 | ## "Parallel Pythagorean Triples Linq": 00:00:49.3715419 99 | ## "Parallel Pythagorean Triples Opt": 00:00:00.1776788 100 | ## Validate : True 101 | 102 | 103 | 104 | 105 | ## krontogiannis@benchmarks-suse:~> mono LinqOptimizer.Benchmarks.CSharp.exe --gc=sgen 106 | ## "Sum Linq": 00:00:05.7998593 107 | ## "Sum Opt": 00:00:01.5160914 108 | ## Validate : True 109 | ## 110 | ## "Sum of Squares Linq": 00:00:17.7935761 111 | ## "Sum of Squares Opt": 00:00:01.4834264 112 | ## Validate : True 113 | ## 114 | ## "Cartesian Linq": 00:00:34.3443617 115 | ## "Cartesian Opt": 00:00:03.2562661 116 | ## Validate : True 117 | ## 118 | ## "GroupBy Linq": 00:00:00.0000408 119 | ## "GroupBy Opt": 00:00:14.5225747 120 | ## Validate : True 121 | ## 122 | ## "Pythagorean Triples Linq": 00:00:41.3427813 123 | ## "Pythagorean Triples Opt": 00:00:00.9896847 124 | ## Validate : True 125 | ## 126 | ## "Parallel Sum Linq": 00:00:28.6741257 127 | ## "Parallel Sum Opt": 00:00:01.8270092 128 | ## Validate : False 129 | ## Values 99998558.1943546, 99998558.194349 130 | ## 131 | ## "Parallel Sum of Squares Linq": 00:00:32.8208704 132 | ## "Parallel Sum of Squares Opt": 00:00:02.6051483 133 | ## Validate : False 134 | ## Values 66665945.3143208, 66665945.3143219 135 | ## 136 | ## "Parallel Cartesian Linq": 00:00:55.5233462 137 | ## "Parallel Cartesian Opt": 00:00:00.4901280 138 | ## Validate : False 139 | ## Values 80982627.9073865, 80982627.9073846 140 | ## 141 | ## "Parallel GroupBy Linq": 00:00:56.3008424 142 | ## "Parallel GroupBy Opt": 00:00:32.1725844 143 | ## Validate : True 144 | ## 145 | ## "Parallel Pythagorean Triples Linq": 00:00:27.1318891 146 | ## "Parallel Pythagorean Triples Opt": 00:00:00.1645247 147 | ## Validate : True 148 | 149 | 150 | krontogiannis@benchlinux:~/cs> mono LinqOptimizer.Benchmarks.CSharp.exe --gc=sgen 151 | "Sum Linq": 00:00:06.2144176 152 | "Sum Opt": 00:00:01.6351289 153 | Validate : True 154 | 155 | "Sum of Squares Linq": 00:00:20.2180162 156 | "Sum of Squares Opt": 00:00:01.4805840 157 | Validate : True 158 | 159 | "Cartesian Linq": 00:01:31.1709912 160 | "Cartesian Opt": 00:00:03.2036639 161 | Validate : True 162 | 163 | "GroupBy Linq": 00:00:00.0000448 164 | "GroupBy Opt": 00:00:18.3385379 165 | Validate : True 166 | 167 | "Pythagorean Triples Linq": 00:00:21.6280102 168 | "Pythagorean Triples Opt": 00:00:01.0116490 169 | Validate : True 170 | 171 | "Parallel Sum Linq": 00:00:35.3149295 172 | "Parallel Sum Opt": 00:00:01.0298428 173 | Validate : False 174 | Values 99995226.0075829, 99995226.0075779 175 | 176 | "Parallel Sum of Squares Linq": 00:00:33.5472698 177 | "Parallel Sum of Squares Opt": 00:00:02.1278030 178 | Validate : False 179 | Values 66661804.8318145, 66661804.8318127 180 | 181 | "Parallel Cartesian Linq": 00:00:49.4028521 182 | "Parallel Cartesian Opt": 00:00:00.5670794 183 | Validate : False 184 | Values 112652980.318857, 112652980.318853 185 | 186 | "Parallel GroupBy Linq": 00:01:26.9799121 187 | "Parallel GroupBy Opt": 00:00:12.4332692 188 | Validate : True 189 | 190 | "Parallel Pythagorean Triples Linq": 00:00:46.2175854 191 | "Parallel Pythagorean Triples Opt": 00:00:00.1705963 192 | Validate : True 193 | 194 | 195 | -------------------------------------------------------------------------------- /benchmarks/LinqOptimizer.Benchmarks.FSharp/LinqOptimizer.Benchmarks.FSharp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2;net461 5 | AnyCPU 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /benchmarks/LinqOptimizer.Benchmarks.FSharp/Program.fs: -------------------------------------------------------------------------------- 1 | module Program 2 | 3 | open System 4 | open System.Linq 5 | open System.Collections 6 | open System.Collections.Generic 7 | open System.Diagnostics 8 | open Nessos.LinqOptimizer.FSharp 9 | open Nessos.LinqOptimizer.Base 10 | 11 | let measuref<'T>(title1, action1 : unit -> 'T, title2, action2 : unit -> 'T, validate : 'T * 'T -> bool) = 12 | let sw = new Stopwatch(); 13 | sw.Start(); 14 | let t1 = action1(); 15 | sw.Stop(); 16 | Console.WriteLine("\"{0}\":\t{1}",title1, sw.Elapsed); 17 | sw.Restart(); 18 | let t2 = action2(); 19 | sw.Stop(); 20 | Console.WriteLine("\"{0}\":\t{1}", title2, sw.Elapsed); 21 | let v = validate(t1, t2) 22 | if not v then 23 | Console.WriteLine("Values {0}, {1}", t1, t2) 24 | Console.WriteLine("Validate : {0}", v); 25 | Console.WriteLine(); 26 | 27 | let SumLinq(values : double []) = 28 | Seq.sum values 29 | 30 | let SumLinqOpt(values : double[]) = 31 | values |> Query.ofSeq |> Query.sum |> Query.run 32 | 33 | let SumSqLinq(values : double[]) = 34 | values |> Seq.map (fun x -> x * x) |> Seq.sum 35 | 36 | let SumSqLinqOpt(values : double[]) = 37 | values |> Query.ofSeq |> Query.map(fun x -> x * x) |> Query.sum |> Query.run 38 | 39 | let CartLinq (dim1 : double[], dim2 : double[]) = 40 | dim1 |> Seq.collect (fun x -> Seq.map (fun y -> x * y) dim2) |> Seq.sum 41 | 42 | let CartLinqOpt(dim1 : double[], dim2 : double[]) = 43 | dim1 |> Query.ofSeq |> Query.collect (fun x -> Seq.map (fun y -> x * y) dim2) |> Query.sum |> Query.run 44 | 45 | 46 | let GroupLinq(values : double[]) = 47 | values 48 | |> Seq.groupBy(fun x -> int x / 100) 49 | |> Seq.sortBy (fun (key, vs) -> key) 50 | |> Seq.map(fun (key, vs) -> Seq.length vs) 51 | |> Seq.toArray 52 | 53 | let GroupLinqOpt(values : double[]) = 54 | values 55 | |> Query.ofSeq 56 | |> Query.groupBy(fun x -> int x / 100) 57 | |> Query.sortBy (fun (key, vs) -> key) 58 | |> Query.map(fun (key, vs) -> Seq.length vs) 59 | |> Query.toArray 60 | |> Query.run 61 | 62 | 63 | let PythagoreanTriplesLinq(max) = 64 | Enumerable.Range(1, max + 1) 65 | |> Seq.collect(fun a -> 66 | Enumerable.Range(a, max + 1 - a) 67 | |> Seq.collect(fun b -> 68 | Enumerable.Range(b, max + 1 - b) 69 | |> Seq.map (fun c -> a, b, c))) 70 | |> Seq.filter (fun (a,b,c) -> a * a + b * b = c * c) 71 | |> Seq.length 72 | 73 | 74 | let PythagoreanTriplesLinqOpt(max) = 75 | Query.range(1, max + 1) 76 | |> Query.collect(fun a -> 77 | Enumerable.Range(a, max + 1 - a) 78 | |> Seq.collect(fun b -> 79 | Enumerable.Range(b, max + 1 - b) 80 | |> Seq.map (fun c -> a, b, c))) 81 | |> Query.filter (fun (a,b,c) -> a * a + b * b = c * c) 82 | |> Query.length 83 | |> Query.run 84 | 85 | 86 | ////////////////////////////////////////////////////////////////// 87 | 88 | let ParallelSumLinq(values : double []) = 89 | values.AsParallel().Sum() 90 | 91 | let ParallelSumLinqOpt(values : double[]) = 92 | values |> PQuery.ofSeq |> PQuery.sum |> PQuery.run 93 | 94 | let ParallelSumSqLinq(values : double[]) = 95 | values.AsParallel().Select(fun x -> x * x).Sum() 96 | 97 | let ParallelSumSqLinqOpt(values : double[]) = 98 | values |> PQuery.ofSeq |> PQuery.map(fun x -> x * x) |> PQuery.sum |> PQuery.run 99 | 100 | let ParallelCartLinq (dim1 : double[], dim2 : double[]) = 101 | dim1.AsParallel().SelectMany(fun x -> dim2.Select(fun y -> x * y)).Sum() 102 | 103 | let ParallelCartLinqOpt(dim1 : double[], dim2 : double[]) = 104 | dim1 |> PQuery.ofSeq |> PQuery.collect (fun x -> Seq.map (fun y -> x * y) dim2) |> PQuery.sum |> PQuery.run 105 | 106 | let ParallelGroupLinq(values : double[]) = 107 | values.AsParallel() 108 | .GroupBy(fun x -> (int)x / 100) 109 | .OrderBy(fun (x : IGrouping<_,_>) -> x.Key) 110 | .Select(fun (k : IGrouping<_,_>) -> k.Count()) 111 | .ToArray() 112 | 113 | let ParallelGroupLinqOpt(values : double[]) = 114 | values 115 | |> PQuery.ofSeq 116 | |> PQuery.groupBy(fun x -> int x / 100) 117 | |> PQuery.sortBy (fun (key, vs) -> key) 118 | |> PQuery.map(fun (key, vs) -> Seq.length vs) 119 | |> PQuery.toArray 120 | |> PQuery.run 121 | 122 | 123 | let ParallelPythagoreanTriplesLinq(max) = 124 | Enumerable.Range(1, max + 1).AsParallel() 125 | .SelectMany(fun a -> 126 | Enumerable.Range(a, max + 1 - a) 127 | .SelectMany(fun b -> 128 | Enumerable.Range(b, max + 1 - b) 129 | .Select(fun c -> (a,b,c)))) 130 | .Where(fun (a,b,c) -> a * a + b * b = c * c) 131 | .Count() 132 | 133 | let ParallelPythagoreanTriplesLinqOpt(max) = 134 | Enumerable.Range(1, max + 1) 135 | |> PQuery.ofSeq 136 | |> PQuery.collect(fun a -> 137 | Enumerable.Range(a, max + 1 - a) 138 | |> Seq.collect(fun b -> 139 | Enumerable.Range(b, max + 1 - b) 140 | |> Seq.map (fun c -> a, b, c))) 141 | |> PQuery.filter (fun (a,b,c) -> a * a + b * b = c * c) 142 | |> PQuery.length 143 | |> PQuery.run 144 | 145 | ////////////////////////////////////////////////////////////////// 146 | 147 | [] 148 | let main argv = 149 | let rnd = new Random() 150 | let v = Enumerable.Range(1, 200000000).Select(fun x -> rnd.NextDouble()).ToArray() 151 | let cmp (x1, x2 : double) = Math.Abs(x1 - x2) < 1E-07 152 | 153 | measuref("Sum Seq", (fun () -> SumLinq v), 154 | "Sum Opt", (fun () -> SumLinqOpt v), 155 | cmp) 156 | 157 | measuref("Sum Squares Seq", (fun () -> SumSqLinq v), 158 | "Sum Squares Opt", (fun () -> SumSqLinqOpt v), 159 | cmp) 160 | 161 | let v1 = v.Take(v.Length / 10).ToArray() 162 | let v2 = v.Take(20).ToArray() 163 | measuref("Cartesian Seq", (fun () -> CartLinq(v1, v2)), 164 | "Cartesian Linq Opt", (fun () -> CartLinqOpt(v1, v2)), 165 | cmp) 166 | 167 | let g = Enumerable.Range(1, 20000000).Select(fun x -> 100000000. * rnd.NextDouble() - 50000000.).ToArray() 168 | measuref("Group Seq", (fun () -> GroupLinq g), 169 | "Group Opt", (fun () -> GroupLinqOpt g), 170 | fun (x, y) -> Enumerable.SequenceEqual(x, y)) 171 | 172 | let n = 1000 173 | measuref("Pythagorean Seq", (fun () -> PythagoreanTriplesLinq n), 174 | "Pythagorean Opt", (fun () -> PythagoreanTriplesLinqOpt n ), 175 | fun (x, y) -> x = y) 176 | 177 | //////////////////////////////////////////////////////////////////////////// 178 | 179 | measuref("Parallel Sum Seq", (fun () -> ParallelSumLinq v), 180 | "Parallel Sum Opt", (fun () -> ParallelSumLinqOpt v), 181 | cmp) 182 | 183 | measuref("Parallel Sum Squares Seq", (fun () -> ParallelSumSqLinq v), 184 | "Parallel Sum Squares Opt", (fun () -> ParallelSumSqLinqOpt v), 185 | cmp) 186 | 187 | measuref("Parallel Cartesian Linq", (fun () -> ParallelCartLinq(v1, v2)), 188 | "Parallel Cartesian Opt", (fun () -> ParallelCartLinqOpt(v1, v2)), 189 | cmp) 190 | 191 | measuref("Parallel Group Linq", (fun () -> ParallelGroupLinq g), 192 | "Parallel Group Opt", (fun () -> ParallelGroupLinqOpt g), 193 | fun (x, y) -> Enumerable.SequenceEqual(x, y)) 194 | 195 | measuref("Parallel Pythagorean Linq", (fun () -> ParallelPythagoreanTriplesLinq n), 196 | "Parallel Pythagorean Opt", (fun () -> ParallelPythagoreanTriplesLinqOpt n ), 197 | fun (x, y) -> x = y) 198 | 199 | 0 200 | -------------------------------------------------------------------------------- /benchmarks/LinqOptimizer.Benchmarks.FSharp/results-fsharp.txt: -------------------------------------------------------------------------------- 1 | PS C:\Users\krontogiannis\Desktop\fsharp> .\LinqOptimizer.Benchmarks.FSharp.exe 2 | "Sum Seq": 00:00:02.8199866 3 | "Sum Opt": 00:00:01.1505856 4 | Validate : True 5 | 6 | "Sum Squares Seq": 00:00:07.3947223 7 | "Sum Squares Opt": 00:00:02.4090330 8 | Validate : True 9 | 10 | "Cartesian Seq": 00:00:33.3088133 11 | "Cartesian Linq Opt": 00:00:01.9047700 12 | Validate : True 13 | 14 | "Group Seq": 00:01:12.7457616 15 | "Group Opt": 00:00:24.6694342 16 | Validate : True 17 | 18 | ## "Pythagorean Seq": 00:00:26.0756178 19 | ## "Pythagorean Opt": 00:00:59.7736766 20 | ## Validate : True 21 | "Pythagorean Seq": 00:00:24.1093710 22 | "Pythagorean Opt": 00:00:00.5481868 23 | Validate : True 24 | 25 | "Parallel Sum Seq": 00:00:00.5089718 26 | "Parallel Sum Opt": 00:00:00.6138561 27 | Validate : True 28 | 29 | "Parallel Sum Squares Seq": 00:00:01.0234322 30 | "Parallel Sum Squares Opt": 00:00:00.5227186 31 | Validate : True 32 | 33 | "Parallel Cartesian Linq": 00:00:07.3078305 34 | "Parallel Cartesian Opt": 00:00:00.4272950 35 | Validate : True 36 | 37 | "Parallel Group Linq": 00:00:18.6451904 38 | "Parallel Group Opt": 00:00:09.1253919 39 | Validate : True 40 | 41 | ## "Parallel Pythagorean Linq": 00:00:09.0055781 42 | ## "Parallel Pythagorean Opt": 00:01:02.9909313 43 | ## Validate : True 44 | "Parallel Pythagorean Linq": 00:00:08.6221434 45 | "Parallel Pythagorean Opt": 00:00:00.1720263 46 | Validate : True 47 | 48 | 49 | 50 | 51 | 52 | 53 | ## krontogiannis@benchmarks-suse:~/fsharptest> mono LinqOptimizer.Benchmarks.FSharp.exe --gc=boehm 54 | ## "Sum Seq": 00:00:05.7900152 55 | ## "Sum Opt": 00:00:01.9753200 56 | ## Validate : True 57 | ## 58 | ## "Sum Squares Seq": 00:00:12.5300960 59 | ## "Sum Squares Opt": 00:00:01.7239223 60 | ## Validate : True 61 | ## 62 | ## "Cartesian Seq": 00:00:43.5201732 63 | ## "Cartesian Linq Opt": 00:00:03.1048342 64 | ## Validate : True 65 | ## 66 | ## "Group Seq": 00:00:21.0231150 67 | ## "Group Opt": 00:00:17.5624660 68 | ## Validate : True 69 | ## 70 | ## "Pythagorean Seq": 00:00:30.3968180 71 | ## "Pythagorean Opt": 00:03:29.2544626 72 | ## Validate : True 73 | ## 74 | ## "Parallel Sum Seq": 00:00:29.2880201 75 | ## "Parallel Sum Opt": 00:00:00.9779574 76 | ## Values 100001344.894611, 100001344.894609 77 | ## Validate : False 78 | ## 79 | ## "Parallel Sum Squares Seq": 00:00:42.6457523 80 | ## "Parallel Sum Squares Opt": 00:00:00.9633095 81 | ## Values 66667650.1864986, 66667650.1865005 82 | ## Validate : False 83 | ## 84 | ## "Parallel Cartesian Linq": 00:01:18.9780920 85 | ## "Parallel Cartesian Opt": 00:00:00.5017145 86 | ## Values 103369593.171146, 103369593.171139 87 | ## Validate : False 88 | ## 89 | ## "Parallel Group Linq": 00:00:54.8168058 90 | ## "Parallel Group Opt": 00:00:33.7890481 91 | ## Validate : True 92 | ## 93 | ## "Parallel Pythagorean Linq": 00:00:44.9215935 94 | ## "Parallel Pythagorean Opt": 00:07:41.5885317 95 | ## Validate : True 96 | ## 97 | ## 98 | ## krontogiannis@benchmarks-suse:~/fsharptest> mono LinqOptimizer.Benchmarks.FSharp --gc=sgen 99 | ## "Sum Seq": 00:00:05.6858785 100 | ## "Sum Opt": 00:00:01.7235633 101 | ## Validate : True 102 | ## 103 | ## "Sum Squares Seq": 00:00:12.0939924 104 | ## "Sum Squares Opt": 00:00:01.7389354 105 | ## Validate : True 106 | ## 107 | ## "Cartesian Seq": 00:00:42.4520462 108 | ## "Cartesian Linq Opt": 00:00:03.1192097 109 | ## Validate : True 110 | ## 111 | ## "Group Seq": 00:00:21.2268391 112 | ## "Group Opt": 00:00:16.8401549 113 | ## Validate : True 114 | ## 115 | ## "Pythagorean Seq": 00:00:28.5491907 116 | ## "Pythagorean Opt": 00:03:21.7066634 117 | ## Validate : True 118 | ## 119 | ## "Parallel Sum Seq": 00:00:27.7542858 120 | ## "Parallel Sum Opt": 00:00:02.4945969 121 | ## Values 100001454.444871, 100001454.444874 122 | ## Validate : False 123 | ## 124 | ## "Parallel Sum Squares Seq": 00:00:30.1456743 125 | ## "Parallel Sum Squares Opt": 00:00:03.6991790 126 | ## Values 66667389.8417738, 66667389.8417703 127 | ## Validate : False 128 | ## 129 | ## "Parallel Cartesian Linq": 00:01:00.1074199 130 | ## "Parallel Cartesian Opt": 00:00:00.4679154 131 | ## Values 92563924.8012052, 92563924.8012029 132 | ## Validate : False 133 | ## 134 | ## "Parallel Group Linq": 00:00:49.7698066 135 | ## "Parallel Group Opt": 00:00:32.6395047 136 | ## Validate : True 137 | ## 138 | ## "Parallel Pythagorean Linq": 00:00:36.6311734 139 | ## "Parallel Pythagorean Opt": 00:07:58.5362407 140 | ## Validate : True 141 | 142 | 143 | krontogiannis@benchlinux:~/fs> mono LinqOptimizer.Benchmarks.FSharp.exe --gc=sgen 144 | "Sum Seq": 00:00:05.4402164 145 | "Sum Opt": 00:00:01.6571258 146 | Validate : True 147 | 148 | "Sum Squares Seq": 00:00:20.1435323 149 | "Sum Squares Opt": 00:00:01.7821322 150 | Validate : True 151 | 152 | "Cartesian Seq": 00:01:48.8153359 153 | "Cartesian Linq Opt": 00:00:03.2311105 154 | Validate : True 155 | 156 | "Group Seq": 00:00:29.2657332 157 | "Group Opt": 00:00:18.1786424 158 | Validate : True 159 | 160 | "Pythagorean Seq": 00:00:28.6682705 161 | "Pythagorean Opt": 00:00:00.9956975 162 | Validate : True 163 | 164 | "Parallel Sum Seq": 00:00:33.6194553 165 | "Parallel Sum Opt": 00:00:01.0214878 166 | Values 100004014.538589, 100004014.53859 167 | Validate : False 168 | 169 | "Parallel Sum Squares Seq": 00:00:32.4996878 170 | "Parallel Sum Squares Opt": 00:00:01.0878441 171 | Values 66669777.2572909, 66669777.257289 172 | Validate : False 173 | 174 | "Parallel Cartesian Linq": 00:01:03.8188772 175 | "Parallel Cartesian Opt": 00:00:00.5069616 176 | Values 82871437.6990987, 82871437.699096 177 | Validate : False 178 | 179 | "Parallel Group Linq": 00:01:20.9994334 180 | "Parallel Group Opt": 00:00:30.8935315 181 | Validate : True 182 | 183 | "Parallel Pythagorean Linq": 00:00:57.4454289 184 | "Parallel Pythagorean Opt": 00:00:00.1678209 185 | Validate : True 186 | 187 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if not exist packages\FAKE\tools\Fake.exe ( 4 | .nuget\NuGet.exe install FAKE -OutputDirectory packages -ExcludeVersion -Version 4.64.17 5 | ) 6 | 7 | packages\FAKE\tools\FAKE.exe build.fsx %* -------------------------------------------------------------------------------- /build.fsx: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------- 2 | // FAKE build script 3 | // -------------------------------------------------------------------------------------- 4 | 5 | #I "packages/FAKE/tools" 6 | #r "packages/FAKE/tools/FakeLib.dll" 7 | 8 | open System 9 | open Fake 10 | open Fake.Git 11 | open Fake.ReleaseNotesHelper 12 | open Fake.AssemblyInfoFile 13 | 14 | // -------------------------------------------------------------------------------------- 15 | // Information about the project to be used at NuGet and in AssemblyInfo files 16 | // -------------------------------------------------------------------------------------- 17 | 18 | let project = "LinqOptimizer" 19 | let authors = ["Nessos Information Technologies, Nick Palladinos, Kostas Rontogiannis"] 20 | let summary = "An automatic query optimizer for LINQ to Objects and PLINQ." 21 | 22 | let description = """ 23 | An automatic query optimizer for LINQ to Objects and PLINQ. 24 | LinqOptimizer compiles declarative LINQ queries into fast loop-based imperative code. 25 | The compiled code has fewer virtual calls, better data locality and speedups of up to 15x. 26 | """ 27 | 28 | let tags = "C# F# linq optimization" 29 | 30 | let gitHome = "https://github.com/nessos" 31 | let gitName = "LinqOptimizer" 32 | let gitRaw = environVarOrDefault "gitRaw" "https://raw.github.com/nessos" 33 | let nugetSource = "https://api.nuget.org/v3/index.json" 34 | let nugetApiKey = environVarOrDefault "NUGET_KEY" "" 35 | 36 | let testProjects = 37 | [ 38 | "tests/LinqOptimizer.Tests.CSharp" 39 | "tests/LinqOptimizer.Tests.FSharp" 40 | ] 41 | 42 | 43 | // -------------------------------------------------------------------------------------- 44 | // The rest of the code is standard F# build script 45 | // -------------------------------------------------------------------------------------- 46 | 47 | // Read release notes & version info from RELEASE_NOTES.md 48 | Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ 49 | let release = parseReleaseNotes (IO.File.ReadAllLines "RELEASE_NOTES.md") 50 | let nugetVersion = release.NugetVersion 51 | 52 | Target "BuildVersion" (fun _ -> 53 | Shell.Exec("appveyor", sprintf "UpdateBuild -Version \"%s\"" nugetVersion) |> ignore 54 | ) 55 | 56 | // Generate assembly info files with the right version & up-to-date information 57 | Target "AssemblyInfo" (fun _ -> 58 | let attributes = 59 | [ 60 | Attribute.Title project 61 | Attribute.Product project 62 | Attribute.Company "Nessos Information Technologies" 63 | Attribute.Version release.AssemblyVersion 64 | Attribute.FileVersion release.AssemblyVersion 65 | ] 66 | 67 | CreateCSharpAssemblyInfo "src/LinqOptimizer.Base/Properties/AssemblyInfo.cs" attributes 68 | CreateFSharpAssemblyInfo "src/LinqOptimizer.Core/AssemblyInfo.fs" attributes 69 | CreateCSharpAssemblyInfo "src/LinqOptimizer.CSharp/Properties/AssemblyInfo.cs" attributes 70 | CreateFSharpAssemblyInfo "src/LinqOptimizer.FSharp/AssemblyInfo.fs" attributes 71 | ) 72 | 73 | 74 | // -------------------------------------------------------------------------------------- 75 | // Clean build results & restore NuGet packages 76 | 77 | Target "RestorePackages" (fun _ -> 78 | !! "./**/packages.config" 79 | |> Seq.iter (RestorePackage (fun p -> { p with ToolPath = "./.nuget/NuGet.exe" })) 80 | ) 81 | 82 | Target "Clean" (fun _ -> 83 | CleanDirs (!! "**/bin/Release/") 84 | ) 85 | 86 | // 87 | //// -------------------------------------------------------------------------------------- 88 | //// Build library & test project 89 | 90 | let configuration = environVarOrDefault "Configuration" "Release" 91 | 92 | Target "Build" (fun _ -> 93 | DotNetCli.Build (fun c -> 94 | { c with 95 | Project = project + ".sln" 96 | Configuration = configuration }) 97 | ) 98 | 99 | // -------------------------------------------------------------------------------------- 100 | // Run the unit tests using test runner & kill test runner when complete 101 | 102 | Target "RunTests" (fun _ -> 103 | for proj in testProjects do 104 | DotNetCli.Test (fun c -> 105 | { c with 106 | Project = proj 107 | Configuration = configuration }) 108 | ) 109 | 110 | // 111 | //// -------------------------------------------------------------------------------------- 112 | //// Build a NuGet package 113 | 114 | Target "NuGet" (fun _ -> 115 | 116 | let mkNuGetPackage project = 117 | // Format the description to fit on a single line (remove \r\n and double-spaces) 118 | let description = description.Replace("\r", "").Replace("\n", "").Replace(" ", " ") 119 | let nugetPath = ".nuget/NuGet.exe" 120 | NuGet (fun p -> 121 | { p with 122 | Authors = authors 123 | Project = project 124 | Summary = summary 125 | Description = description 126 | Version = nugetVersion 127 | ReleaseNotes = String.concat " " release.Notes 128 | Tags = tags 129 | OutputPath = "nuget" 130 | ToolPath = nugetPath 131 | AccessKey = getBuildParamOrDefault "nugetkey" "" 132 | Publish = hasBuildParam "nugetkey" }) 133 | ("nuget/" + project + ".nuspec") 134 | 135 | mkNuGetPackage "LinqOptimizer.CSharp" 136 | mkNuGetPackage "LinqOptimizer.FSharp" 137 | ) 138 | 139 | Target "PublishNuGet" (fun _ -> 140 | if String.IsNullOrEmpty nugetApiKey then failwith "build did not specify a nuget API key" 141 | 142 | let pushPkg nupkg = 143 | // omg dotnet pack doesn't work wih absolute paths 144 | let relativePath = ProduceRelativePath __SOURCE_DIRECTORY__ nupkg 145 | DotNetCli.RunCommand 146 | (fun c -> { c with WorkingDir = __SOURCE_DIRECTORY__ }) 147 | (sprintf "nuget push %s -s %s -k %s" relativePath nugetSource nugetApiKey) 148 | 149 | !! "NuGet/*.nupkg" |> Seq.toArray |> Array.Parallel.iter pushPkg 150 | ) 151 | 152 | 153 | Target "Release" DoNothing 154 | 155 | // -------------------------------------------------------------------------------------- 156 | // Run all targets by default. Invoke 'build ' to override 157 | 158 | Target "Prepare" DoNothing 159 | Target "PrepareRelease" DoNothing 160 | Target "All" DoNothing 161 | 162 | "Clean" 163 | ==> "RestorePackages" 164 | ==> "AssemblyInfo" 165 | ==> "Prepare" 166 | ==> "Build" 167 | ==> "RunTests" 168 | ==> "All" 169 | 170 | "All" 171 | ==> "PrepareRelease" 172 | ==> "NuGet" 173 | ==> "PublishNuGet" 174 | ==> "Release" 175 | 176 | RunTargetOrDefault "NuGet" -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "X$OS" = "XWindows_NT" ] ; then 4 | # use .Net 5 | 6 | .nuget/NuGet.exe install FAKE -OutputDirectory packages -ExcludeVersion -Version 4.64.17 7 | exit_code=$? 8 | if [ $exit_code -ne 0 ]; then 9 | exit $exit_code 10 | fi 11 | 12 | packages/FAKE/tools/FAKE.exe $@ --fsiargs build.fsx 13 | else 14 | 15 | mono .nuget/NuGet.exe install FAKE -OutputDirectory packages -ExcludeVersion -Version 4.64.17 16 | exit_code=$? 17 | if [ $exit_code -ne 0 ]; then 18 | exit $exit_code 19 | fi 20 | 21 | mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx 22 | fi -------------------------------------------------------------------------------- /nuget/LinqOptimizer.CSharp.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | @project@ 5 | @build.number@ 6 | @authors@ 7 | @authors@ 8 | https://github.com/palladin/LinqOptimizer/blob/master/LICENSE.md 9 | https://github.com/palladin/LinqOptimizer 10 | https://avatars1.githubusercontent.com/u/5538081 11 | false 12 | @summary@ 13 | @description@ 14 | @releaseNotes@ 15 | Copyright 2014 16 | @tags@ 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /nuget/LinqOptimizer.FSharp.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | @project@ 5 | @build.number@ 6 | @authors@ 7 | @authors@ 8 | https://github.com/palladin/LinqOptimizer/blob/master/LICENSE.md 9 | https://github.com/palladin/LinqOptimizer 10 | https://avatars1.githubusercontent.com/u/5538081 11 | false 12 | @summary@ 13 | @description@ 14 | @releaseNotes@ 15 | Copyright 2014 16 | @tags@ 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Base/LinqOptimizer.Base.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | AnyCPU 6 | false 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Base/ParallelQueryExpr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Nessos.LinqOptimizer.Core; 6 | 7 | namespace Nessos.LinqOptimizer.Base 8 | { 9 | /// 10 | /// This interface represents an optimized parallel query. 11 | /// 12 | public interface IParallelQueryExpr 13 | { 14 | /// 15 | /// The expression representing the query. 16 | /// 17 | QueryExpr Expr { get; } 18 | } 19 | 20 | /// 21 | /// This interface represents an optimized parallel query. 22 | /// 23 | /// The type of the query. 24 | public interface IParallelQueryExpr : IParallelQueryExpr { } 25 | 26 | /// 27 | /// The concrete implementation of an optimized query. 28 | /// 29 | public class ParallelQueryExprVoid : IParallelQueryExpr 30 | { 31 | private QueryExpr _expr; 32 | /// 33 | /// The expression representing the query. 34 | /// 35 | public QueryExpr Expr { get { return _expr; } } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// The expression. 41 | public ParallelQueryExprVoid(QueryExpr query) 42 | { 43 | _expr = query; 44 | } 45 | } 46 | 47 | /// 48 | /// The concrete implementation of an optimized query. 49 | /// 50 | public class ParallelQueryExpr : IParallelQueryExpr 51 | { 52 | private QueryExpr _expr; 53 | /// 54 | /// The expression representing the query. 55 | /// 56 | public QueryExpr Expr { get { return _expr; } } 57 | 58 | /// 59 | /// Initializes a new instance of the class. 60 | /// 61 | /// The expression. 62 | public ParallelQueryExpr(QueryExpr query) 63 | { 64 | _expr = query; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Base/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System.Reflection; 3 | 4 | [assembly: AssemblyTitleAttribute("LinqOptimizer")] 5 | [assembly: AssemblyProductAttribute("LinqOptimizer")] 6 | [assembly: AssemblyCompanyAttribute("Nessos Information Technologies")] 7 | [assembly: AssemblyVersionAttribute("0.7.0")] 8 | [assembly: AssemblyFileVersionAttribute("0.7.0")] 9 | namespace System { 10 | internal static class AssemblyVersionInformation { 11 | internal const System.String AssemblyTitle = "LinqOptimizer"; 12 | internal const System.String AssemblyProduct = "LinqOptimizer"; 13 | internal const System.String AssemblyCompany = "Nessos Information Technologies"; 14 | internal const System.String AssemblyVersion = "0.7.0"; 15 | internal const System.String AssemblyFileVersion = "0.7.0"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Base/QueryExpr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Nessos.LinqOptimizer.Core; 6 | 7 | namespace Nessos.LinqOptimizer.Base 8 | { 9 | /// 10 | /// This interface represents an optimized query. 11 | /// 12 | public interface IQueryExpr 13 | { 14 | /// 15 | /// The expression representing the query. 16 | /// 17 | QueryExpr Expr { get; } 18 | } 19 | 20 | /// 21 | /// This interface represents an optimized query. 22 | /// 23 | /// The type of the query. 24 | public interface IQueryExpr : IQueryExpr { } 25 | 26 | /// 27 | /// The concrete implementation of an optimized query. 28 | /// 29 | public class QueryExprVoid : IQueryExpr 30 | { 31 | private QueryExpr _expr; 32 | /// 33 | /// The expression representing the query. 34 | /// 35 | public QueryExpr Expr { get { return _expr; } } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// The expression. 41 | public QueryExprVoid(QueryExpr query) 42 | { 43 | _expr = query; 44 | } 45 | } 46 | 47 | /// 48 | /// The concrete implementation of an optimized query. 49 | /// 50 | /// The type of the query. 51 | public class QueryExpr : IQueryExpr 52 | { 53 | private QueryExpr _expr; 54 | /// 55 | /// The expression representing the query. 56 | /// 57 | public QueryExpr Expr { get { return _expr; } } 58 | 59 | /// 60 | /// Initializes a new instance of the class. 61 | /// 62 | /// The expression. 63 | public QueryExpr(QueryExpr query) 64 | { 65 | _expr = query; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/LinqOptimizer.CSharp/LinqOptimizer.CSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | AnyCPU 6 | false 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/LinqOptimizer.CSharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System.Reflection; 3 | 4 | [assembly: AssemblyTitleAttribute("LinqOptimizer")] 5 | [assembly: AssemblyProductAttribute("LinqOptimizer")] 6 | [assembly: AssemblyCompanyAttribute("Nessos Information Technologies")] 7 | [assembly: AssemblyVersionAttribute("0.7.0")] 8 | [assembly: AssemblyFileVersionAttribute("0.7.0")] 9 | namespace System { 10 | internal static class AssemblyVersionInformation { 11 | internal const System.String AssemblyTitle = "LinqOptimizer"; 12 | internal const System.String AssemblyProduct = "LinqOptimizer"; 13 | internal const System.String AssemblyCompany = "Nessos Information Technologies"; 14 | internal const System.String AssemblyVersion = "0.7.0"; 15 | internal const System.String AssemblyFileVersion = "0.7.0"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/LinqOptimizer.CSharp/QueryExpr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using Nessos.LinqOptimizer.Base; 7 | using QExpr = Nessos.LinqOptimizer.Core.QueryExpr; 8 | 9 | namespace Nessos.LinqOptimizer.CSharp 10 | { 11 | /// 12 | /// Provides a set of static methods for creating queries. 13 | /// 14 | public static class QueryExpr 15 | { 16 | /// 17 | /// Creates a new query that generates a sequence of integral numbers within a specified range. 18 | /// 19 | /// The value of the first integer in the sequence. 20 | /// The number of sequential integers to generate. 21 | /// A query that contains a range of sequential integral numbers. 22 | public static IQueryExpr> Range(int start, int count) 23 | { 24 | if(count < 0 || ((long)start + (long) count) - 1L > Int64.MaxValue) 25 | throw new ArgumentOutOfRangeException("count"); 26 | else 27 | return new QueryExpr>(QExpr.NewRangeGenerator(Expression.Constant(start), Expression.Constant(count))); 28 | } 29 | 30 | /// 31 | /// Creates a new query that generates a sequence that contains one repeated value. 32 | /// 33 | /// The value to be repeated. 34 | /// The number of sequential integers to generate. 35 | /// A query that contains a repeated value. 36 | public static IQueryExpr> Repeat(TResult element, int count) 37 | { 38 | if (count < 0) 39 | throw new ArgumentOutOfRangeException("count"); 40 | else 41 | return new QueryExpr>(QExpr.NewRepeatGenerator(Expression.Constant(element, typeof(TResult)), Expression.Constant(count))); 42 | } 43 | 44 | /// 45 | /// Creates a query that applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. 46 | /// 47 | /// The first sequence to merge. 48 | /// The first sequence to merge. 49 | /// A function that specifies how to merge the elements from the two sequences. 50 | /// A query that contains merged elements of two input sequences. 51 | public static IQueryExpr> Zip(IEnumerable first, IEnumerable second, Expression> resultSelector) 52 | { 53 | return new QueryExpr>(QExpr.NewZipWith(Expression.Constant(first), Expression.Constant(second), resultSelector)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/AccessChecker.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | type private AccessCheckerVisitor () = 12 | inherit ExpressionVisitor() with 13 | 14 | let rec isPublic (ty : Type) = 15 | if ty = null || ty = typeof then true 16 | else ty.IsPublic && isPublic ty.DeclaringType 17 | 18 | let invalidAccesses = List() 19 | 20 | member this.InvalidAccesses with get () = invalidAccesses 21 | 22 | override this.VisitNew(expr : NewExpression) = 23 | if not(isPublic expr.Type) then 24 | invalidAccesses.Add(expr :> _, Some expr.Type.FullName) 25 | expr.Update(this.Visit expr.Arguments) :> _ 26 | 27 | override this.VisitMember(expr : MemberExpression) = 28 | match expr.Member with 29 | | :? PropertyInfo as pi -> 30 | if pi.GetGetMethod() = null then invalidAccesses.Add(expr :> _, Some expr.Member.DeclaringType.FullName) 31 | | :? FieldInfo as fi -> 32 | if not fi.IsPublic || not (isPublic fi.DeclaringType) then invalidAccesses.Add(expr :> _, Some expr.Member.DeclaringType.FullName) 33 | | _ -> () 34 | 35 | expr.Update(this.Visit expr.Expression) :> _ 36 | 37 | override this.VisitMethodCall(expr : MethodCallExpression) = 38 | if not expr.Method.IsPublic || not (isPublic expr.Method.DeclaringType) then 39 | invalidAccesses.Add(expr :> _, Some expr.Method.DeclaringType.FullName) 40 | expr.Update(this.Visit expr.Object, this.Visit expr.Arguments) :> _ 41 | 42 | // cast 43 | override this.VisitUnary(expr : UnaryExpression) = 44 | if expr.NodeType = ExpressionType.Convert && not (isPublic expr.Type) then invalidAccesses.Add(expr :> _, Some(expr.Type.FullName.ToString())) 45 | expr.Update(this.Visit expr.Operand) :> _ 46 | 47 | module AccessChecker = 48 | let check(expr : Expression) : seq<_> option = 49 | let ac = new AccessCheckerVisitor() 50 | ac.Visit(expr) |> ignore 51 | if ac.InvalidAccesses.Count > 0 then Some(ac.InvalidAccesses :> _) else None 52 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/AnonymousTypeEraser.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | open System.Runtime.CompilerServices 11 | 12 | // Transform anonymous type access and transparent identifiers 13 | // to local variables. 14 | type private AnonymousTypeEraserVisitor () = 15 | inherit ExpressionVisitor() with 16 | 17 | 18 | 19 | let generated = sprintf "___%s___" 20 | 21 | let mappings = Dictionary() 22 | let existing = List() 23 | let redundants = List() 24 | 25 | override this.VisitMember(expr : MemberExpression) = 26 | match expr.Member.MemberType with 27 | | MemberTypes.Property when isAnonymousType expr.Member.DeclaringType -> 28 | let p = mappings.Values.SingleOrDefault(fun p -> p.Name = generated expr.Member.Name) //Expression.Parameter(expr.Type, expr.Member.Name) 29 | if p = null then 30 | let e = existing.SingleOrDefault(fun p -> p.Name = expr.Member.Name) 31 | if e = null then expr.Update(this.Visit expr.Expression) :> _ else e :> _ 32 | else 33 | p :> _ 34 | | _ -> 35 | //expr :> _ 36 | expr.Update(this.Visit expr.Expression) :> _ 37 | 38 | override this.VisitBinary(expr : BinaryExpression) = 39 | match expr with 40 | | AnonymousTypeAssign(left, right) -> 41 | let first = right.Arguments.First() :?> ParameterExpression 42 | if not <| isTransparentIdentifier first then existing.Add(first) 43 | 44 | let right' = this.Visit(right.Arguments.Last()) 45 | let left' = Expression.Parameter(right'.Type, generated(right.Members.Last().Name)) 46 | mappings.Add(left, left') 47 | Expression.Assign(left', right') :> _ 48 | | TransparentIdentifierIdentityAssignment(ti1, ti2) -> 49 | redundants.Add(ti1) 50 | redundants.Add(ti2) 51 | Expression.Empty() :> _ 52 | | _ -> 53 | expr.Update(this.Visit(expr.Left), null, this.Visit(expr.Right)) :> _ 54 | 55 | override this.VisitBlock(expr : BlockExpression) = 56 | let exprs = this.Visit(expr.Expressions) 57 | let vars = expr.Variables 58 | |> Seq.map (fun p -> if mappings.ContainsKey(p) then mappings.[p] else p) 59 | |> Seq.filter (fun p -> not(Seq.exists ((=) p) redundants)) 60 | Expression.Block(vars, exprs ) :> _ 61 | 62 | module AnonymousTypeEraser = 63 | let apply(expr : Expression) = 64 | let ate = new AnonymousTypeEraserVisitor() 65 | ate.Visit(expr) -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | do () 11 | 12 | module internal AssemblyVersionInformation = 13 | let [] AssemblyTitle = "LinqOptimizer" 14 | let [] AssemblyProduct = "LinqOptimizer" 15 | let [] AssemblyCompany = "Nessos Information Technologies" 16 | let [] AssemblyVersion = "0.7.0" 17 | let [] AssemblyFileVersion = "0.7.0" 18 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/CSharpExpressionOptimizer.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | // C#/Linq call patterns 12 | // TODO: expr type checks 13 | module private CSharpExpressionOptimizerHelpers = 14 | let private sourceOfExpr (expr : Expression) sourceType : QueryExpr = 15 | if expr.Type.IsArray then 16 | Source (expr, expr.Type.GetElementType(), sourceType) 17 | elif expr.Type.IsGenericType && expr.Type.GetGenericTypeDefinition() = typedefof> then 18 | Source(expr, expr.Type.GetGenericArguments().[0], sourceType) 19 | elif expr.Type.IsGenericType then 20 | Source (expr, expr.Type.GetInterface("IEnumerable`1").GetGenericArguments().[0], sourceType) 21 | else 22 | failwithf "Not supported source %A" expr 23 | 24 | let rec toQueryExpr (expr : Expression) : QueryExpr = 25 | match expr with 26 | | MethodCall (_, MethodName "Select" _, [expr'; LambdaOrQuote ([_], bodyExpr, f')]) -> 27 | Transform (f', toQueryExpr expr') 28 | 29 | | MethodCall (_, MethodName "Select" _, [expr'; LambdaOrQuote ([_; _], bodyExpr, f')]) -> 30 | TransformIndexed (f', toQueryExpr expr') 31 | 32 | | MethodCall (_, MethodName "Where" _, [expr'; LambdaOrQuote ([paramExpr], _, f')]) -> 33 | Filter (f', toQueryExpr expr') 34 | 35 | | MethodCall (_, MethodName "Where" _, [expr'; LambdaOrQuote ([paramExpr; indexExpr], _,f')]) -> 36 | FilterIndexed (f' , toQueryExpr expr') 37 | 38 | | MethodCall (_, MethodName "Aggregate" _, [expr'; seedExpr; LambdaOrQuote ([_;_] as paramsExpr, bodyExpr, f') ] ) -> 39 | Aggregate(seedExpr, f' , toQueryExpr expr') 40 | 41 | | MethodCall (_, MethodName "Generate" _, [startExpr; Lambda (_,_) as pred; LambdaOrQuote(_,_,state); LambdaOrQuote(_,_,result)]) -> 42 | Generate(startExpr, pred :?> LambdaExpression, state, result) 43 | 44 | | MethodCall (_, MethodName "Take" _, [expr'; countExpr]) when countExpr.Type = typeof -> 45 | Take (countExpr, toQueryExpr expr' ) 46 | 47 | | MethodCall (_, MethodName "TakeWhile" _, [expr'; LambdaOrQuote ([paramExpr], _,f')]) -> 48 | TakeWhile(f', toQueryExpr expr' ) 49 | 50 | | MethodCall (_, MethodName "SkipWhile" _, [expr'; LambdaOrQuote ([paramExpr], _,f')]) -> 51 | SkipWhile(f', toQueryExpr expr' ) 52 | 53 | | MethodCall (_, MethodName "Skip" _, [expr'; countExpr]) when countExpr.Type = typeof -> 54 | Skip (countExpr, toQueryExpr expr') 55 | 56 | | MethodCall (_, (MethodName "SelectMany" _), [expr'; LambdaOrQuote ([paramExpr], bodyExpr, _)]) -> 57 | NestedQuery ((paramExpr, toQueryExpr (bodyExpr)), toQueryExpr expr') 58 | 59 | | MethodCall (_, (MethodName "SelectMany" _), [expr'; LambdaOrQuote ([paramExpr], bodyExpr, _); LambdaOrQuote (_,_,lam)]) -> 60 | NestedQueryTransform((paramExpr, toQueryExpr (bodyExpr)), lam, toQueryExpr expr') 61 | 62 | | MethodCall (_, MethodName "GroupBy" _, [expr'; LambdaOrQuote ([paramExpr], bodyExpr,f')]) -> 63 | GroupBy (f', toQueryExpr expr', typedefof>.MakeGenericType [|bodyExpr.Type; paramExpr.Type|]) 64 | 65 | | MethodCall (_, MethodName "OrderBy" _, [expr'; LambdaOrQuote ([paramExpr], bodyExpr, f')]) -> 66 | OrderBy ([f', Order.Ascending], toQueryExpr expr') 67 | 68 | | MethodCall (_, MethodName "OrderByDescending" _, [expr'; LambdaOrQuote ([paramExpr], bodyExpr, f')]) -> 69 | OrderBy ([f', Order.Descending], toQueryExpr expr') 70 | 71 | | MethodCall (_, MethodName "Count" _, [expr']) -> 72 | Count(toQueryExpr expr') 73 | 74 | | MethodCall (_, MethodName "Range" _, [startExpr; countExpr]) -> 75 | RangeGenerator(startExpr, countExpr) 76 | 77 | | MethodCall (_, MethodName "Sum" _, [expr']) -> 78 | Sum(toQueryExpr expr') 79 | 80 | | MethodCall (_, MethodName "ToList" _, [expr']) -> 81 | ToList(toQueryExpr expr') 82 | 83 | | MethodCall (_, MethodName "ToArray" _, [expr']) -> 84 | ToArray(toQueryExpr expr') 85 | 86 | | MethodCall (expr', MethodName "ForEach" _, [ LambdaOrQuote ([paramExpr], bodyExpr, f')]) 87 | | MethodCall (_, MethodName "ForEach" _, [expr'; LambdaOrQuote ([paramExpr], bodyExpr, f')]) -> 88 | ForEach(f', toQueryExpr expr') 89 | 90 | | MethodCall (_, MethodName "AsQueryExpr" _, [expr']) -> 91 | sourceOfExpr expr' QueryExprType.Sequential 92 | 93 | | MethodCall (_, MethodName "AsParallelQueryExpr" _, [expr']) -> 94 | sourceOfExpr expr' QueryExprType.Parallel 95 | 96 | | NotNull expr' -> 97 | sourceOfExpr expr' QueryExprType.Sequential 98 | 99 | | _ -> 100 | invalidArg "expr" "Cannot extract QueryExpr from null Expression" 101 | 102 | and private transformer (expr : Expression) : Expression option = 103 | match expr with 104 | | MethodCall (_, MethodName "AsQueryExpr" _, [_ ]) 105 | | MethodCall (_, MethodName "Select" _, [_; LambdaOrQuote _ ]) 106 | | MethodCall (_, MethodName "Select" _, [_; LambdaOrQuote _ ]) 107 | | MethodCall (_, MethodName "Where" _, [_; LambdaOrQuote _ ]) 108 | | MethodCall (_, MethodName "Where" _, [_; LambdaOrQuote _ ]) 109 | | MethodCall (_, MethodName "Aggregate" _, [_; _; LambdaOrQuote ([_;_], _, _) ]) 110 | | MethodCall (_, MethodName "Take" _, [_; _ ]) 111 | | MethodCall (_, MethodName "TakeWhile" _, [_; LambdaOrQuote _ ]) 112 | | MethodCall (_, MethodName "SkipWhile" _, [_; LambdaOrQuote _ ]) 113 | | MethodCall (_, MethodName "Skip" _, [_; _ ]) 114 | | MethodCall (_, MethodName "SelectMany" _, [_; LambdaOrQuote _ ]) 115 | | MethodCall (_, MethodName "SelectMany" _, [_; LambdaOrQuote _; LambdaOrQuote _]) 116 | | MethodCall (_, MethodName "GroupBy" _, [_; LambdaOrQuote _ ]) 117 | | MethodCall (_, MethodName "OrderBy" _, [_; LambdaOrQuote _ ]) 118 | | MethodCall (_, MethodName "OrderByDescending" _, [_; LambdaOrQuote _ ]) 119 | | MethodCall (_, MethodName "Count" _, [_ ]) 120 | | MethodCall (_, MethodName "Range" _, [_; _ ]) 121 | | MethodCall (_, MethodName "Sum" _, [_ ]) 122 | | MethodCall (_, MethodName "ToList" _, [_ ]) 123 | | MethodCall (_, MethodName "ToArray" _, [_ ]) 124 | | MethodCall (_, MethodName "ForEach" _, [_; LambdaOrQuote _ ]) 125 | | MethodCall (_, MethodName "ForEach" _, [ LambdaOrQuote _ ]) -> 126 | let query = toQueryExpr expr 127 | let expr = (Compiler.compileToSequential query optimize) 128 | Some expr 129 | | _ -> 130 | None 131 | 132 | and private opt = ExpressionTransformer.transform transformer 133 | 134 | and optimize (expr : Expression) : Expression = opt expr 135 | 136 | type CSharpExpressionOptimizer = 137 | static member Optimize(expr : Expression) : Expression = 138 | CSharpExpressionOptimizerHelpers.optimize(expr) 139 | static member ToQueryExpr(expr : Expression) : QueryExpr = 140 | CSharpExpressionOptimizerHelpers.toQueryExpr(expr) 141 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/Collector.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | type ArrayCollector<'T> () = 12 | 13 | let ls = List<'T [] * int>() 14 | 15 | let mutable isAdd = false 16 | 17 | let bufferSize = 1024 18 | let mutable currentBufferSize = bufferSize 19 | let newBuffer( size ) = currentBufferSize <- size; Array.zeroCreate<'T> size 20 | let mutable buffer : 'T [] = newBuffer bufferSize 21 | let mutable idx = 0 22 | 23 | let flushBuffer () = 24 | ls.Add(buffer, idx) 25 | buffer <- newBuffer bufferSize 26 | isAdd <- false 27 | idx <- 0 28 | 29 | member this.AddRange(collector : ArrayCollector<'T>) = 30 | this.Flush() 31 | collector.Flush() 32 | Seq.iter ls.Add collector.ArrayList 33 | 34 | member this.Add(elem : 'T) = 35 | isAdd <- true 36 | if idx >= currentBufferSize then 37 | let newBuf = newBuffer (currentBufferSize * 2) 38 | Array.Copy(buffer, newBuf, idx) 39 | buffer <- newBuf 40 | this.Add(elem) 41 | else 42 | buffer.[idx] <- elem 43 | idx <- idx + 1 44 | 45 | member this.ToArray () : 'T [] = 46 | this.Flush() 47 | 48 | let mutable length = 0 49 | for (_, l) in ls do 50 | length <- length + l 51 | 52 | let final = Array.zeroCreate length 53 | let mutable currIdx = 0 54 | for (array, l) in ls do 55 | Array.Copy(array, 0, final, currIdx, l) 56 | currIdx <- currIdx + l 57 | final 58 | 59 | member this.ToList () : List<'T> = 60 | List<'T>(this.ToArray()) 61 | 62 | member internal this.ArrayList with get () = ls 63 | 64 | member internal this.Flush () = 65 | if isAdd then flushBuffer () 66 | 67 | interface IEnumerable<'T> with 68 | member this.GetEnumerator() = 69 | let array = this.ToArray() :> IEnumerable<'T> 70 | array.GetEnumerator() 71 | 72 | member this.GetEnumerator() = 73 | let list = this.ToArray() 74 | list.GetEnumerator() 75 | 76 | interface IOrderedEnumerable<'T> with 77 | member __.CreateOrderedEnumerable<'TKey>(keySelector : Func<'T, 'TKey> , comparer : IComparer<'TKey>, desc : bool) = 78 | raise <| NotImplementedException() 79 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/ConstantLiftingTransformer.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | // Lift constants and member access into parameters 12 | // due to the live-object limitation. 13 | type private ConstantLiftingVisitor () = 14 | inherit ExpressionVisitor() with 15 | let mutable x = 0 16 | let getName () = 17 | x <- x + 1 18 | sprintf "___param%d___" x 19 | 20 | let x = Dictionary() 21 | 22 | member this.Environment with get () = x 23 | 24 | override this.VisitConstant(expr : ConstantExpression) = 25 | if not <| isPrimitive expr && expr.Value <> null then 26 | let p = Expression.Parameter(expr.Type, getName()) 27 | this.Environment.Add(p, expr.Value) 28 | p :> _ 29 | else 30 | expr :> _ 31 | 32 | override this.VisitMember(expr : MemberExpression) = 33 | if expr.Expression :? ConstantExpression then 34 | 35 | let obj = (expr.Expression :?> ConstantExpression).Value 36 | 37 | let (value, p) = 38 | match expr.Member.MemberType with 39 | | MemberTypes.Field -> 40 | let fi = expr.Member :?> FieldInfo 41 | fi.GetValue(obj), Expression.Parameter(expr.Type, sprintf "___param%s___" fi.Name) 42 | | MemberTypes.Property -> 43 | let pi = expr.Member :?> PropertyInfo 44 | let indexed = pi.GetIndexParameters() |> Seq.cast |> Seq.toArray 45 | pi.GetValue(obj, indexed), Expression.Parameter(expr.Type, sprintf "___param%s___" pi.Name) 46 | | _ -> 47 | failwithf "Internal error : Accessing non Field or Property from MemberExpression %A" expr 48 | 49 | this.Environment.Add(p, value) 50 | p :> _ 51 | else 52 | expr.Update(this.Visit expr.Expression) :> _ 53 | 54 | module ConstantLiftingTransformer = 55 | let apply(expr : Expression) = 56 | let clv = new ConstantLiftingVisitor() 57 | let expr = clv.Visit(expr) 58 | expr, clv.Environment.Keys.ToArray(), clv.Environment.Values.ToArray() -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/ExpressionHelpers.fs: -------------------------------------------------------------------------------- 1 |  2 | namespace Nessos.LinqOptimizer.Core 3 | open System 4 | open System.Linq 5 | open System.Linq.Expressions 6 | open System.Reflection 7 | open System.Runtime.CompilerServices 8 | 9 | [] 10 | module ExpressionHelpers = 11 | // F# friendly Expression functions 12 | let empty = Expression.Empty() 13 | let ``default`` t = Expression.Default(t) 14 | let block (varExprs : seq) (exprs : seq) = 15 | Expression.Block(varExprs, exprs) :> Expression 16 | let tryfinally bodyExpr finallyExpr = 17 | Expression.TryFinally(bodyExpr, finallyExpr) 18 | let assign leftExpr rightExpr = 19 | Expression.Assign(leftExpr, rightExpr) :> Expression 20 | let addAssign leftExpr rightExpr = 21 | Expression.AddAssign (leftExpr, rightExpr) :> Expression 22 | let subAssign leftExpr rightExpr = 23 | Expression.SubtractAssign(leftExpr, rightExpr) :> Expression 24 | 25 | let cast expr ty = 26 | Expression.Convert(expr, ty) :> Expression 27 | 28 | let var name (t : Type) = Expression.Parameter(t, name) 29 | let lambda paramExprs bodyExpr = 30 | Expression.Lambda(bodyExpr, paramExprs) 31 | 32 | let labelTarget (name : string) = Expression.Label(name) 33 | let label (labelTarget : LabelTarget) = Expression.Label(labelTarget) 34 | let goto (labelTarget : LabelTarget) = Expression.Goto(labelTarget) 35 | 36 | let constant (value : obj) : Expression = Expression.Constant(value) :> _ 37 | 38 | let ``new`` (t : Type) = Expression.New(t) 39 | let call (methodInfo : MethodInfo) (instance : Expression) 40 | (args : seq) = 41 | Expression.Call(instance, methodInfo, args) :> Expression 42 | 43 | let ifThenElse boolExpr thenExpr elseExpr = 44 | Expression.IfThenElse(boolExpr, thenExpr, elseExpr) :> Expression 45 | let ifThen boolExpr thenExpr = 46 | Expression.IfThen(boolExpr, thenExpr) :> Expression 47 | let loop bodyExpr breakLabel continueLabel = 48 | Expression.Loop(bodyExpr, breakLabel, continueLabel) 49 | let ``break`` (label : LabelTarget) = 50 | Expression.Break(label) 51 | let ``continue`` (label : LabelTarget) = 52 | Expression.Continue(label) 53 | 54 | 55 | let equal leftExpr rightExpr = Expression.Equal(leftExpr, rightExpr) 56 | let greaterThan leftExpr rightExpr = Expression.GreaterThan(leftExpr, rightExpr) 57 | let greaterThanOrEqual leftExpr rightExpr = Expression.GreaterThanOrEqual(leftExpr, rightExpr) 58 | let lessThan leftExpr rightExpr = Expression.LessThan(leftExpr, rightExpr) 59 | let lessThanOrEqual leftExpr rightExpr = Expression.LessThanOrEqual(leftExpr, rightExpr) 60 | let notEqual leftExpr rightExpr = Expression.NotEqual(leftExpr, rightExpr) 61 | 62 | let notExpr expr = Expression.Not(expr) 63 | 64 | let arrayNew (t : Type) (lengthExpr : Expression) = 65 | Expression.NewArrayBounds(t, [|lengthExpr|]) 66 | let arrayAccess (arrayExpr : Expression) (indexExpr : Expression) = 67 | Expression.ArrayAccess(arrayExpr, indexExpr) 68 | let arrayIndex (arrayExpr : Expression) (indexExpr : Expression) = 69 | Expression.ArrayIndex(arrayExpr, indexExpr) 70 | let arrayLength arrayExpr = Expression.ArrayLength(arrayExpr) 71 | 72 | let isPrimitive (expr : ConstantExpression) = 73 | expr.Type.IsPrimitive || expr.Type = typeof 74 | 75 | let (|Map|) (f : 'T -> 'S) (v : 'T) = f v 76 | 77 | let (|QuotedLambda|_|) (expr : Expression) = 78 | match expr with 79 | | :? UnaryExpression as unaryExpr when unaryExpr.NodeType = ExpressionType.Quote -> 80 | match unaryExpr.Operand with 81 | | :? LambdaExpression as lam -> Some lam 82 | | _ -> None 83 | | _ -> None 84 | 85 | // Expression Active Patterns 86 | let (|Lambda|_|) (expr : Expression) = 87 | match expr with 88 | | :? LambdaExpression as lam -> Some (Seq.toList lam.Parameters, lam.Body) 89 | | _ -> None 90 | 91 | // Expression Active Patterns 92 | let (|LambdaOrQuote|_|) (expr : Expression) = 93 | match expr with 94 | | Lambda(param, body) -> 95 | Some (Seq.toList param, body, expr :?> LambdaExpression) 96 | | QuotedLambda lam -> 97 | Some (Seq.toList lam.Parameters, lam.Body, lam) 98 | | _ -> 99 | None 100 | 101 | 102 | let (|MethodCall|_|) (expr : Expression) = 103 | if expr <> null && (expr.NodeType = ExpressionType.Call && (expr :? MethodCallExpression)) 104 | then 105 | let methodCallExpr = expr :?> MethodCallExpression 106 | Some (methodCallExpr.Object, methodCallExpr.Method, Seq.toList methodCallExpr.Arguments) 107 | else None 108 | 109 | let (|NotNull|_|) (expr : Expression) = 110 | if expr <> null then Some expr else None 111 | 112 | let (|ExprType|) (expr : Expression) = ExprType expr.Type 113 | 114 | let (|Constant|_|) (expr : Expression) = 115 | match expr with 116 | | :? ConstantExpression as constExpr -> Some (constExpr.Value, constExpr.Type) 117 | | _ -> None 118 | 119 | let (|Parameter|_|) (expr : Expression) = 120 | match expr with 121 | | :? ParameterExpression as paramExpr -> Some (paramExpr) 122 | | _ -> None 123 | 124 | let (|Assign|_|) (expr : Expression) = 125 | match expr with 126 | | :? BinaryExpression as assignExpr 127 | when assignExpr.NodeType = ExpressionType.Assign -> Some (assignExpr.Left, assignExpr.Right) 128 | | _ -> None 129 | 130 | let (|AddAssign|_|) (expr : Expression) = 131 | match expr with 132 | | :? BinaryExpression as assignExpr 133 | when assignExpr.NodeType = ExpressionType.AddAssign -> Some (assignExpr.Left, assignExpr.Right) 134 | | _ -> None 135 | 136 | let (|Plus|_|) (expr : Expression) = 137 | match expr with 138 | | :? BinaryExpression as plusExpr 139 | when plusExpr.NodeType = ExpressionType.Add -> Some (plusExpr.Left, plusExpr.Right) 140 | | _ -> None 141 | 142 | let (|Subtract|_|) (expr : Expression) = 143 | match expr with 144 | | :? BinaryExpression as plusExpr 145 | when plusExpr.NodeType = ExpressionType.Subtract -> Some (plusExpr.Left, plusExpr.Right) 146 | | _ -> None 147 | 148 | let (|Divide|_|) (expr : Expression) = 149 | match expr with 150 | | :? BinaryExpression as plusExpr 151 | when plusExpr.NodeType = ExpressionType.Divide -> Some (plusExpr.Left, plusExpr.Right) 152 | | _ -> None 153 | 154 | let (|Times|_|) (expr : Expression) = 155 | match expr with 156 | | :? BinaryExpression as plusExpr 157 | when plusExpr.NodeType = ExpressionType.Multiply -> Some (plusExpr.Left, plusExpr.Right) 158 | | _ -> None 159 | 160 | let (|Modulo|_|) (expr : Expression) = 161 | match expr with 162 | | :? BinaryExpression as plusExpr 163 | when plusExpr.NodeType = ExpressionType.Modulo -> Some (plusExpr.Left, plusExpr.Right) 164 | | _ -> None 165 | 166 | let (|Negate|_|) (expr : Expression) = 167 | match expr with 168 | | :? UnaryExpression as unaryExpr 169 | when unaryExpr.NodeType = ExpressionType.Negate -> Some (unaryExpr.Operand) 170 | | _ -> None 171 | 172 | let (|IfThenElse|_|) (expr : Expression) = 173 | match expr with 174 | | :? ConditionalExpression as contExpr -> 175 | Some (contExpr.Test, contExpr.IfTrue, contExpr.IfFalse) 176 | | _ -> None 177 | 178 | 179 | let (|Equal|_|) (expr : Expression) = 180 | match expr with 181 | | :? BinaryExpression as equalExpr 182 | when equalExpr.NodeType = ExpressionType.Equal -> Some (equalExpr.Left, equalExpr.Right) 183 | | _ -> None 184 | 185 | let (|NotEqual|_|) (expr : Expression) = 186 | match expr with 187 | | :? BinaryExpression as equalExpr 188 | when equalExpr.NodeType = ExpressionType.NotEqual -> Some (equalExpr.Left, equalExpr.Right) 189 | | _ -> None 190 | 191 | let (|GreaterThan|_|) (expr : Expression) = 192 | match expr with 193 | | :? BinaryExpression as equalExpr 194 | when equalExpr.NodeType = ExpressionType.GreaterThan -> Some (equalExpr.Left, equalExpr.Right) 195 | | _ -> None 196 | 197 | let (|GreaterThanOrEqual|_|) (expr : Expression) = 198 | match expr with 199 | | :? BinaryExpression as equalExpr 200 | when equalExpr.NodeType = ExpressionType.GreaterThanOrEqual -> Some (equalExpr.Left, equalExpr.Right) 201 | | _ -> None 202 | 203 | let (|LessThan|_|) (expr : Expression) = 204 | match expr with 205 | | :? BinaryExpression as equalExpr 206 | when equalExpr.NodeType = ExpressionType.LessThan -> Some (equalExpr.Left, equalExpr.Right) 207 | | _ -> None 208 | 209 | let (|LessThanOrEqual|_|) (expr : Expression) = 210 | match expr with 211 | | :? BinaryExpression as equalExpr 212 | when equalExpr.NodeType = ExpressionType.LessThanOrEqual -> Some (equalExpr.Left, equalExpr.Right) 213 | | _ -> None 214 | 215 | let (|Nop|_|) (expr : Expression) = 216 | match expr with 217 | | :? DefaultExpression as defaultExpr -> Some (defaultExpr) 218 | | _ -> None 219 | 220 | let (|Goto|_|) (expr : Expression) = 221 | match expr with 222 | | :? GotoExpression as gotoExpr -> Some (gotoExpr.Kind, gotoExpr.Target, gotoExpr.Value) 223 | | _ -> None 224 | 225 | let (|Block|_|) (expr : Expression) = 226 | match expr with 227 | | :? BlockExpression as blockExpr -> Some (blockExpr.Variables, blockExpr.Expressions, blockExpr.Result) 228 | | _ -> None 229 | 230 | let (|Convert|_|) (expr : Expression) = 231 | match expr with 232 | | :? UnaryExpression as unaryExpr when unaryExpr.NodeType = ExpressionType.Convert -> 233 | Some (unaryExpr.Operand, unaryExpr.Type) 234 | | _ -> None 235 | 236 | 237 | // http://stackoverflow.com/questions/1650681/determining-whether-a-type-is-an-anonymous-type 238 | // TODO : Mono? 239 | let isAnonymousType (ty : Type) = 240 | ty.GetCustomAttributes(typeof, false).Count() > 0 241 | && ty.FullName.Contains "AnonymousType" 242 | && ty.Namespace = null 243 | && ty.IsSealed 244 | && not ty.IsPublic 245 | 246 | // TODO : Mono? 247 | let isTransparentIdentifier (expr : Expression) = 248 | match expr with 249 | | :? ParameterExpression as expr -> expr.Name.Contains "TransparentIdentifier" 250 | | _ -> false 251 | 252 | let isAnonymousConstructor (expr : Expression) = 253 | match expr with 254 | | :? NewExpression as expr -> isAnonymousType expr.Constructor.DeclaringType 255 | | _ -> false 256 | 257 | let (|AnonymousTypeAssign|_|) (expr : Expression) = 258 | if expr.NodeType = ExpressionType.Assign then 259 | let expr = expr :?> BinaryExpression 260 | if isTransparentIdentifier expr.Left && isAnonymousConstructor expr.Right then 261 | let left = expr.Left :?> ParameterExpression 262 | let right = expr.Right :?> NewExpression 263 | Some(left, right) 264 | else None 265 | else None 266 | 267 | let (|AnonymousTypeConstruction|_|) (expr : Expression) = 268 | match expr with 269 | | :? NewExpression as expr when isAnonymousType expr.Constructor.DeclaringType -> Some (expr.Members, expr.Arguments) 270 | | _ -> None 271 | 272 | let (|TransparentIdentifierIdentityAssignment|_|) (expr : BinaryExpression) = 273 | if expr.NodeType = ExpressionType.Assign 274 | && isTransparentIdentifier expr.Left 275 | && isTransparentIdentifier expr.Right then 276 | let left = expr.Left :?> ParameterExpression 277 | let right = expr.Right :?> ParameterExpression 278 | if left.Name = right.Name then Some (left, right) 279 | else None 280 | else 281 | None 282 | 283 | let (|AnonymousTypeMember|_|) (expr : Expression) = 284 | match expr with 285 | | :? MemberExpression as expr -> 286 | match expr.Member.MemberType with 287 | | MemberTypes.Property when isAnonymousType expr.Member.DeclaringType -> Some expr 288 | | _ -> None 289 | | _ -> None 290 | 291 | let (|FieldMember|_|) (expr : Expression) = 292 | match expr with 293 | | :? MemberExpression as expr -> 294 | match expr.Member.MemberType with 295 | | MemberTypes.Field -> Some (expr.Expression, expr.Member :?> FieldInfo) 296 | | _ -> None 297 | | _ -> None 298 | 299 | let (|PropertyMember|_|) (expr : Expression) = 300 | match expr with 301 | | :? MemberExpression as expr -> 302 | match expr.Member.MemberType with 303 | | MemberTypes.Property -> Some (expr.Expression, expr.Member) 304 | | _ -> None 305 | | _ -> None 306 | 307 | let (|ValueTypeMemberInit|_|) (expr : Expression) = 308 | match expr with 309 | | :? MemberInitExpression as expr when expr.Type.IsValueType -> Some (expr.NewExpression, expr.Bindings |> Seq.map (fun binding -> binding :?> MemberAssignment)) 310 | | _ -> None 311 | 312 | let (|Loop|_|) (expr : Expression) = 313 | match expr with 314 | | :? LoopExpression as expr -> Some (expr.Body, expr.BreakLabel, expr.ContinueLabel) 315 | | _ -> None 316 | 317 | type internal Expression with 318 | static member ofFSharpFunc<'T,'R>(func : Expression>) = func 319 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/ExpressionTransformer.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | module internal ExpressionTransformer = 12 | 13 | type private ExpressionTransformer (transformer : Expression -> Expression option) = 14 | 15 | inherit ExpressionVisitor() with 16 | override this.Visit (expr : Expression) = 17 | match transformer expr with 18 | | None -> base.Visit(expr) 19 | | Some expr -> expr 20 | 21 | let transform (transformer : Expression -> Expression option) = 22 | let t = new ExpressionTransformer(transformer) 23 | fun (expr : Expression) -> t.Visit expr 24 | 25 | 26 | // inherit ExpressionVisitor() with 27 | // member private this.Transformer = transformer 28 | // 29 | // override this.VisitBinary(expr : BinaryExpression) = 30 | // match transformer expr with 31 | // | None -> 32 | // let l = this.Visit expr.Left 33 | // let r = this.Visit expr.Right 34 | // Expression.MakeBinary(expr.NodeType, l, r) :> _ 35 | // | Some expr -> 36 | // expr 37 | // 38 | // override this.VisitBlock(expr : BlockExpression) = 39 | // let exprs' = this.Visit expr.Expressions 40 | // let e = Expression.Block(exprs') 41 | // defaultArg (transformer e) (e :> _) 42 | // 43 | // override this.VisitCatchBlock(expr : CatchBlock) = 44 | // ExpressionTransformer.VisitCatchBlockWrapper(this, expr) 45 | // 46 | // override this.VisitConditional(expr : ConditionalExpression) = 47 | // let ifTrue = this.Visit expr.IfTrue 48 | // let ifFalse = this.Visit expr.IfFalse 49 | // let test = this.Visit expr.Test 50 | // let e = Expression.Condition(test, ifTrue, ifFalse, expr.Type) 51 | // defaultArg (transformer e) (e :> _) 52 | // 53 | // override this.VisitConstant(expr : ConstantExpression) = 54 | // defaultArg (transformer expr) (expr :> _) 55 | // 56 | // override this.VisitDebugInfo(expr : DebugInfoExpression) = 57 | // defaultArg (transformer expr) (expr :> _) 58 | // 59 | // override this.VisitDefault(expr : DefaultExpression) = 60 | // defaultArg (transformer expr) (expr :> _) 61 | // 62 | // override this.VisitDynamic(expr : DynamicExpression) = 63 | // let args = this.Visit expr.Arguments 64 | // let e = expr.Update(args) 65 | // defaultArg (transformer e) (e :> _) 66 | // 67 | // override this.VisitElementInit(expr : ElementInit) = 68 | // ExpressionTransformer.VisitElementInitWrapper(this, expr) 69 | // 70 | // override this.VisitExtension(expr : Expression) = 71 | // defaultArg (transformer expr) expr 72 | // 73 | // override this.VisitGoto(expr : GotoExpression) = 74 | // let value = this.Visit expr.Value 75 | // let e = expr.Update(expr.Target, value) 76 | // defaultArg (transformer e) (e :> _) 77 | // 78 | // override this.VisitIndex(expr : IndexExpression) = 79 | // let o = this.Visit expr.Object 80 | // let args = this.Visit expr.Arguments 81 | // let e = expr.Update(o, args) 82 | // defaultArg (transformer e) (e :> _) 83 | // 84 | // override this.VisitInvocation(expr : InvocationExpression) = 85 | // let expr' = this.Visit expr.Expression 86 | // let args = this.Visit expr.Arguments 87 | // let e = expr.Update(expr', args) 88 | // defaultArg (transformer e) (e :> _) 89 | // 90 | // override this.VisitLabel(expr : LabelExpression) = 91 | // let value = this.Visit(expr.DefaultValue) 92 | // let e = expr.Update(expr.Target, value) 93 | // defaultArg (transformer e) (e :> _) 94 | // 95 | // override this.VisitLabelTarget(expr : LabelTarget) = 96 | // expr 97 | // 98 | // override this.VisitLambda<'T>(expr : Expression<'T>) = 99 | // let body = this.Visit expr.Body 100 | // let par = expr.Parameters |> Seq.map (fun e -> ExpressionTransformer.VisitParameterWrapper(this,e)) |> Seq.cast 101 | // let e = expr.Update(body, par) 102 | // defaultArg (transformer e) (e :> _) 103 | // 104 | // override this.VisitListInit(expr : ListInitExpression) = 105 | // let newExpr = this.Visit expr.NewExpression :?> NewExpression 106 | // let inits = expr.Initializers |> Seq.map (fun ei -> ExpressionTransformer.VisitElementInitWrapper(this, ei)) 107 | // let e = expr.Update(newExpr, inits) 108 | // defaultArg (transformer e) (e :> _) 109 | // 110 | // override this.VisitLoop(expr : LoopExpression) = 111 | // let body = this.Visit expr.Body 112 | // let e = expr.Update(expr.BreakLabel, expr.ContinueLabel, body) 113 | // defaultArg (transformer e) (e :> _) 114 | // 115 | // override this.VisitMember(expr : MemberExpression) = 116 | // let expr' = this.Visit expr.Expression 117 | // let e = expr.Update(expr') 118 | // defaultArg (transformer e) (e :> _) 119 | // 120 | // override this.VisitMemberAssignment(expr : MemberAssignment) = 121 | // let expr' = this.Visit expr.Expression 122 | // expr.Update(expr') 123 | // 124 | // override this.VisitMemberBinding(expr : MemberBinding) = 125 | // ExpressionTransformer.VisitMemberBindingWrapper(this, expr) 126 | // 127 | // override this.VisitMemberInit(expr : MemberInitExpression) = 128 | // let newExpr = this.Visit expr.NewExpression :?> NewExpression 129 | // let bindings = expr.Bindings |> Seq.map (fun mb -> ExpressionTransformer.VisitMemberBindingWrapper(this, mb)) 130 | // let e = expr.Update(newExpr, bindings) 131 | // defaultArg (transformer e) (e :> _) 132 | // 133 | // override this.VisitMemberListBinding(expr : MemberListBinding) = 134 | // let inits = expr.Initializers |> Seq.map (fun ei -> ExpressionTransformer.VisitElementInitWrapper(this,ei)) 135 | // expr.Update(inits) 136 | // 137 | // override this.VisitMemberMemberBinding(expr : MemberMemberBinding) = 138 | // let binds = expr.Bindings |> Seq.map (fun mb -> ExpressionTransformer.VisitMemberBindingWrapper(this, mb)) 139 | // expr.Update(binds) 140 | // 141 | // override this.VisitMethodCall(expr : MethodCallExpression) = 142 | // match transformer expr with 143 | // | None -> 144 | // let o = this.Visit expr.Object 145 | // let args = this.Visit expr.Arguments 146 | // expr.Update(o, args) :> _ 147 | // | Some expr -> 148 | // this.Visit expr 149 | // 150 | // override this.VisitNew(expr : NewExpression) = 151 | // let args = this.Visit expr.Arguments 152 | // let e = expr.Update(args) 153 | // defaultArg (transformer e) (e :> _) 154 | // 155 | // override this.VisitNewArray(expr : NewArrayExpression) = 156 | // let exprs = this.Visit expr.Expressions 157 | // let e = expr.Update(exprs) 158 | // defaultArg (transformer e) (e :> _) 159 | // 160 | // override this.VisitParameter(expr : ParameterExpression) = 161 | // ExpressionTransformer.VisitParameterWrapper(this,expr) 162 | // 163 | // override this.VisitRuntimeVariables(expr : RuntimeVariablesExpression) = 164 | // let vars = expr.Variables |> Seq.map (fun e -> ExpressionTransformer.VisitParameterWrapper(this,e)) |> Seq.cast 165 | // let e = expr.Update(vars) 166 | // defaultArg (transformer e) (e :> _) 167 | // 168 | // override this.VisitSwitch(expr : SwitchExpression) = 169 | // let value = this.Visit expr.SwitchValue 170 | // let cases = expr.Cases |> Seq.map (fun sc -> ExpressionTransformer.VisitSwitchCaseWrapper(this, sc)) 171 | // let defaultBody = this.Visit expr.DefaultBody 172 | // let e = expr.Update(value, cases, defaultBody) 173 | // defaultArg (transformer e) (e :> _) 174 | // 175 | // override this.VisitSwitchCase(expr : SwitchCase) = 176 | // ExpressionTransformer.VisitSwitchCaseWrapper(this, expr) 177 | // 178 | // override this.VisitTry(expr : TryExpression) = 179 | // let body = this.Visit expr.Body 180 | // let handlers = expr.Handlers |> Seq.map (fun handler -> ExpressionTransformer.VisitCatchBlockWrapper(this, handler)) 181 | // let finallyExpr = this.Visit expr.Finally 182 | // let fault = this.Visit expr.Fault 183 | // let e = expr.Update(body, handlers, finallyExpr, fault) 184 | // defaultArg (transformer e) (e :> _) 185 | // 186 | // override this.VisitTypeBinary(expr : TypeBinaryExpression) = 187 | // let expr' = this.Visit expr.Expression 188 | // let e = expr.Update(expr') 189 | // defaultArg (transformer e) (e :> _) 190 | // 191 | // override this.VisitUnary(expr : UnaryExpression) = 192 | // let expr' = this.Visit expr.Operand 193 | // let e = expr.Update(expr') 194 | // defaultArg (transformer e) (e :> _) 195 | // 196 | // 197 | // static member private VisitParameterWrapper(visitor : ExpressionTransformer, expr : ParameterExpression) = 198 | // defaultArg (visitor.Transformer (expr :> _)) (expr :> _) 199 | // 200 | // static member private VisitElementInitWrapper(visitor : ExpressionVisitor, expr : ElementInit) = 201 | // let args = visitor.Visit(expr.Arguments) 202 | // expr.Update(args) 203 | // 204 | // static member private VisitMemberBindingWrapper(visitor : ExpressionVisitor, expr : MemberBinding) = 205 | // expr 206 | // 207 | // static member private VisitSwitchCaseWrapper(visitor : ExpressionVisitor, expr : SwitchCase) = 208 | // let tests = visitor.Visit expr.TestValues 209 | // let body = visitor.Visit expr.Body 210 | // expr.Update(tests, body) 211 | // 212 | // static member private VisitCatchBlockWrapper(visitor : ExpressionVisitor, expr : CatchBlock) = 213 | // let var = visitor.Visit expr.Variable :?> ParameterExpression 214 | // let body = visitor.Visit expr.Body 215 | // let filter = visitor.Visit expr.Filter 216 | // expr.Update(var, filter, body) -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/ExtensionMethods.fs: -------------------------------------------------------------------------------- 1 |  2 | namespace Nessos.LinqOptimizer.Core 3 | 4 | open System 5 | open System.Collections 6 | open System.Collections.Generic 7 | open System.Linq 8 | open System.Linq.Expressions 9 | open System.Reflection 10 | open System.Reflection.Emit 11 | 12 | 13 | module CompiledThunks = 14 | let cache = Concurrent.ConcurrentDictionary() 15 | 16 | type CoreHelpers = 17 | 18 | 19 | static member AsQueryExpr(enumerable : IEnumerable, ty : Type) : QueryExpr = 20 | // Hack removed 21 | //// Hack to optimize Enumerable.Range and Enumerable.Repeat calls 22 | //// TODO : check Mono generated types 23 | //let t = enumerable.GetType() 24 | //match t.FullName with 25 | //| s when s.StartsWith "System.Linq.Enumerable+" -> 26 | // let start = t.GetFields().First(fun f -> f.Name.EndsWith "__start").GetValue(enumerable) 27 | // let count = t.GetFields().First(fun f -> f.Name.EndsWith "__count").GetValue(enumerable) 28 | // RangeGenerator(constant start , constant count) 29 | //| s when s.StartsWith "System.Linq.Enumerable+" -> 30 | // let element = t.GetFields().First(fun f -> f.Name.EndsWith "__element").GetValue(enumerable) 31 | // let count = t.GetFields().First(fun f -> f.Name.EndsWith "__count").GetValue(enumerable) 32 | // RepeatGenerator(Expression.Convert(constant element, ty) , constant count) 33 | //| _ -> 34 | Source (constant enumerable, ty, QueryExprType.Sequential) 35 | 36 | static member private CompileToMethod(query : QueryExpr, compile : QueryExpr -> Expression) : Func<'T> = 37 | let source = query.ToString() 38 | let expr = compile query 39 | let expr = TupleElimination.apply(expr) 40 | let expr = AnonymousTypeEraser.apply(expr) 41 | let expr, pms, objs = ConstantLiftingTransformer.apply(expr) 42 | 43 | if CompiledThunks.cache.ContainsKey(source) then 44 | let func = CompiledThunks.cache.[source] :?> Func 45 | Func<'T>(fun () -> func.Invoke(objs) :?> 'T) 46 | else 47 | let lambda = Expression.Lambda(expr, pms) 48 | let dele = Session.Compile(lambda) 49 | let iaccs = AccessChecker.check lambda 50 | let func = CoreHelpers.WrapInvocation(dele, iaccs) 51 | CompiledThunks.cache.TryAdd(source, func) |> ignore 52 | Func<'T>(fun () -> func.Invoke(objs) :?> 'T) 53 | 54 | static member private Compile(query : QueryExpr, compile : QueryExpr -> Expression) : Func<'T> = 55 | let source = sprintf "allowNonPublicMemberAccess query (%s)" <| query.ToString() 56 | let expr = compile query 57 | let expr = TupleElimination.apply(expr) 58 | let expr = AnonymousTypeEraser.apply(expr) 59 | let expr, pms, objs = ConstantLiftingTransformer.apply(expr) 60 | 61 | if CompiledThunks.cache.ContainsKey(source) then 62 | let func = CompiledThunks.cache.[source] 63 | Func<'T>(fun () -> func.DynamicInvoke(objs) :?> 'T) 64 | else 65 | let lambda = Expression.Lambda(expr, pms) 66 | let func = lambda.Compile() 67 | CompiledThunks.cache.TryAdd(source, func) |> ignore 68 | Func<'T>(fun () -> func.DynamicInvoke(objs) :?> 'T) 69 | 70 | static member private WrapInvocation<'T>(dele : Delegate, iaccs : seq option) : Func = 71 | Func( 72 | fun (args : obj[]) -> 73 | try dele.DynamicInvoke(args) 74 | with :? TargetInvocationException as ex -> 75 | match ex.InnerException with 76 | | :? MemberAccessException as innerEx -> 77 | let msg = 78 | "Attempting to access non public member or type from dynamic assembly. Consider making your type/member public or use the appropriate Run method.\n" + 79 | match iaccs with 80 | | None -> String.Empty 81 | | Some iaccs -> 82 | "Possible invalid accesses :\n" + 83 | (iaccs 84 | |> Seq.map (fun (expr, msg) -> 85 | let msg = match msg with None -> String.Empty | Some msg -> msg 86 | sprintf "At expression : %A, %s" expr msg) 87 | |> String.concat "\n") 88 | raise <| Exception(msg, innerEx) 89 | | e when e <> null -> raise e 90 | | _ -> reraise()) 91 | 92 | static member Compile<'T>(queryExpr : QueryExpr, optimize : Func) : Func<'T> = 93 | CoreHelpers.Compile<'T>(queryExpr, optimize, false) 94 | 95 | static member Compile(queryExpr : QueryExpr, optimize : Func) : Action = 96 | CoreHelpers.Compile(queryExpr, optimize, false) 97 | 98 | static member CompileToParallel<'T>(queryExpr : QueryExpr, optimize : Func) : Func<'T> = 99 | CoreHelpers.CompileToParallel<'T>(queryExpr, optimize, false) 100 | 101 | 102 | 103 | 104 | static member Compile<'T>(queryExpr : QueryExpr, optimize : Func, allowNonPublicMemberAccess : bool) : Func<'T> = 105 | if allowNonPublicMemberAccess then 106 | CoreHelpers.Compile(queryExpr, fun expr -> Compiler.compileToSequential expr optimize.Invoke) 107 | else 108 | CoreHelpers.CompileToMethod(queryExpr, fun expr -> Compiler.compileToSequential expr optimize.Invoke ) 109 | 110 | static member Compile(queryExpr : QueryExpr, optimize : Func, allowNonPublicMemberAccess : bool) : Action = 111 | if allowNonPublicMemberAccess then 112 | let func = CoreHelpers.Compile(queryExpr, fun expr -> Compiler.compileToSequential expr optimize.Invoke) 113 | Action(fun () -> func.Invoke()) 114 | else 115 | let func = CoreHelpers.CompileToMethod(queryExpr, fun expr -> Compiler.compileToSequential expr optimize.Invoke ) 116 | Action(fun () -> func.Invoke()) 117 | 118 | static member CompileToParallel<'T>(queryExpr : QueryExpr, optimize : Func, allowNonPublicMemberAccess : bool ) : Func<'T> = 119 | if allowNonPublicMemberAccess then 120 | CoreHelpers.Compile(queryExpr, fun expr -> Compiler.compileToParallel expr optimize.Invoke) 121 | else 122 | CoreHelpers.CompileToMethod(queryExpr, fun expr -> Compiler.compileToParallel expr optimize.Invoke ) 123 | 124 | 125 | static member CompileTemplateVariadic<'R>(parameters : ParameterExpression [], template : QueryExpr, optimize : Func, allowNonPublicMemberAccess : bool) = 126 | let func = 127 | if allowNonPublicMemberAccess then 128 | CoreHelpers.Compile(template, 129 | fun query -> 130 | let expr = Compiler.compileToSequential query optimize.Invoke 131 | let lam = lambda parameters expr 132 | lam :> Expression) 133 | else 134 | CoreHelpers.CompileToMethod(template, 135 | fun query -> 136 | let expr = Compiler.compileToSequential query optimize.Invoke 137 | let lam = lambda parameters expr 138 | lam :> _) 139 | let template = func.Invoke() 140 | template 141 | 142 | static member CompileActionTemplateVariadic(parameters : ParameterExpression [], template : QueryExpr, optimize : Func, allowNonPublicMemberAccess : bool) : obj = 143 | let func = 144 | if allowNonPublicMemberAccess then 145 | CoreHelpers.Compile(template, 146 | fun query -> 147 | let expr = Compiler.compileToSequential query optimize.Invoke 148 | let lam = lambda parameters expr 149 | lam :> Expression) 150 | else 151 | CoreHelpers.CompileToMethod(template, 152 | fun query -> 153 | let expr = Compiler.compileToSequential query optimize.Invoke 154 | let lam = lambda parameters expr 155 | lam :> _) 156 | let template = func.Invoke() 157 | template 158 | 159 | static member CompileTemplateToParallelVariadic<'R>(parameters : ParameterExpression [], template : QueryExpr, optimize : Func, allowNonPublicMemberAccess : bool) = 160 | let func = 161 | if allowNonPublicMemberAccess then 162 | CoreHelpers.Compile(template, 163 | fun query -> 164 | let expr = Compiler.compileToParallel query optimize.Invoke 165 | let lam = lambda parameters expr 166 | lam :> Expression) 167 | else 168 | CoreHelpers.CompileToMethod(template, 169 | fun query -> 170 | let expr = Compiler.compileToParallel query optimize.Invoke 171 | let lam = lambda parameters expr 172 | lam :> _) 173 | let template = func.Invoke() 174 | template -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/FreeVariablesVisitor.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | /// Returns the free variables out of an expression. 12 | type private FreeVariablesVisitor () = 13 | inherit ExpressionVisitor() with 14 | 15 | let freeVars = HashSet() 16 | let localVars = HashSet() 17 | 18 | let addLocals(vars : seq) = 19 | vars |> Seq.iter (fun p -> localVars.Add(p) |> ignore) 20 | 21 | member this.Environment with get () = freeVars :> seq<_> 22 | 23 | override this.VisitParameter(expr : ParameterExpression) = 24 | if not <| localVars.Contains(expr) then 25 | freeVars.Add(expr) |> ignore 26 | expr :> _ 27 | 28 | override this.VisitLambda(expr : Expression<'T>) = 29 | addLocals expr.Parameters 30 | expr.Update(this.Visit(expr.Body), expr.Parameters) :> _ 31 | 32 | override this.VisitBlock(expr : BlockExpression) = 33 | addLocals expr.Variables 34 | expr.Update(expr.Variables, this.Visit(expr.Expressions)) :> _ 35 | 36 | override this.VisitMember(expr : MemberExpression) = 37 | // TransparentIdentifier's free variable 38 | if expr.Member.MemberType = MemberTypes.Property then 39 | let pi = expr.Member :?> PropertyInfo 40 | if isTransparentIdentifier expr.Expression then 41 | let p = Expression.Parameter(expr.Type, pi.Name) 42 | freeVars.Add(p) |> ignore 43 | p :> _ 44 | else 45 | expr :> _ 46 | else 47 | expr :> _ 48 | 49 | // override this.VisitMember(expr : MemberExpression) = 50 | // if expr.Expression :? ConstantExpression then 51 | // let obj = (expr.Expression :?> ConstantExpression).Value 52 | // 53 | // let (value, p) = 54 | // match expr.Member.MemberType with 55 | // | MemberTypes.Field -> 56 | // let fi = expr.Member :?> FieldInfo 57 | // fi.GetValue(obj), Expression.Parameter(expr.Type, sprintf "%s" fi.Name) 58 | // | MemberTypes.Property -> 59 | // let pi = expr.Member :?> PropertyInfo 60 | // let indexed = pi.GetIndexParameters() |> Seq.cast |> Seq.toArray 61 | // pi.GetValue(obj, indexed), Expression.Parameter(expr.Type, sprintf "%s" pi.Name) 62 | // | _ -> 63 | // failwithf "Internal error : Accessing non Field or Property from MemberExpression %A" expr 64 | // freeVars.Add(p) |> ignore 65 | // p :> _ 66 | // else 67 | // expr.Update(this.Visit expr.Expression) :> _ 68 | 69 | 70 | [] 71 | module FreeVariablesVisitor = 72 | let get(expr : Expression) = 73 | let fvv = new FreeVariablesVisitor() 74 | let expr = fvv.Visit(expr) 75 | fvv.Environment 76 | 77 | let getWithExpr(expr : Expression) = 78 | let fvv = new FreeVariablesVisitor() 79 | let expr = fvv.Visit(expr) 80 | expr, fvv.Environment -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/GroupingHelpers.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Threading; 10 | open System.Collections.Concurrent 11 | 12 | type Grouping<'Key, 'Value> = 13 | new(key, count, values) = { key = key; count = count; index = -1; position = -1; values = values } 14 | val private key : 'Key 15 | val private values : 'Value[] 16 | val mutable private count : int 17 | val mutable private index : int 18 | val mutable private position : int 19 | 20 | 21 | member self.IncrCount() = self.count <- self.count + 1 22 | member self.InterlockedIncrCount() = Interlocked.Increment(&self.count) 23 | member self.Count = self.count 24 | member self.Position = self.position 25 | member self.Index with get () = self.index 26 | and set (index) = self.index <- index 27 | member self.IncrPosition() = self.position <- self.position + 1 28 | member self.InterlockedIncrPosition() = Interlocked.Increment(&self.position) 29 | 30 | 31 | 32 | interface IGrouping<'Key, 'Value> with 33 | member self.Key = self.key 34 | 35 | member self.GetEnumerator() : IEnumerator<'Value> = 36 | let positionRef = ref self.index 37 | { new IEnumerator<'Value> with 38 | member __.Current = self.values.[!positionRef - 1] 39 | interface System.Collections.IEnumerator with 40 | member __.Current = self.values.[!positionRef] :> _ 41 | member __.MoveNext() = 42 | if !positionRef >= self.index + self.count then 43 | false 44 | else 45 | incr positionRef 46 | true 47 | member __.Reset() = positionRef := self.index 48 | interface IDisposable with 49 | member __.Dispose() = () } 50 | 51 | member self.GetEnumerator() : IEnumerator = 52 | let enumerator : IEnumerator<'Value> = (self :> IGrouping<'Key, 'Value>).GetEnumerator() 53 | enumerator :> _ 54 | 55 | type Grouping = 56 | 57 | static member GroupBy(keys : 'Key[], values : 'Value[]) : IEnumerable> = 58 | let values' : 'Value[] = Array.zeroCreate values.Length 59 | let dict = new Dictionary<'Key, Grouping<'Key, 'Value>>() 60 | let mutable grouping = Unchecked.defaultof> 61 | // grouping count 62 | for i = 0 to values.Length - 1 do 63 | let key = keys.[i] 64 | if not <| dict.TryGetValue(key, &grouping) then 65 | grouping <- new Grouping<'Key, 'Value>(key, 1, values') 66 | dict.Add(key, grouping) 67 | else 68 | grouping.IncrCount() 69 | // rearrange 70 | let mutable currentIndex = 0 71 | for i = 0 to values.Length - 1 do 72 | let key = keys.[i] 73 | let value = values.[i] 74 | dict.TryGetValue(key, &grouping) |> ignore 75 | if grouping.Index = -1 then 76 | grouping.Index <- currentIndex 77 | currentIndex <- grouping.Index + grouping.Count 78 | 79 | grouping.IncrPosition() 80 | values'.[grouping.Index + grouping.Position] <- value 81 | // collect results 82 | dict.Values |> Seq.map (fun value -> value :> _) 83 | 84 | static member ParallelGroupBy(keys : 'Key[], values : 'Value[]) : IEnumerable> = 85 | let values' : 'Value[] = Array.zeroCreate values.Length 86 | let dict = new ConcurrentDictionary<'Key, Grouping<'Key, 'Value>>() 87 | // grouping count 88 | ParallelismHelpers.ReduceCombine(keys, (fun () -> dict), 89 | (fun (dict : ConcurrentDictionary<'Key, Grouping<'Key, 'Value>>) key -> 90 | let mutable grouping = Unchecked.defaultof> 91 | if not <| dict.TryGetValue(key, &grouping) then 92 | grouping <- new Grouping<'Key, 'Value>(key, 1, values') 93 | if not <| dict.TryAdd(key, grouping) then 94 | dict.TryGetValue(key, &grouping) |> ignore 95 | grouping.InterlockedIncrCount() |> ignore 96 | else 97 | grouping.InterlockedIncrCount() |> ignore 98 | dict 99 | ), (fun dict _ -> dict), (fun x -> x)) |> ignore 100 | // fix grouping index 101 | let mutable currentIndex = 0 102 | for grouping in dict.Values do 103 | grouping.Index <- currentIndex 104 | currentIndex <- currentIndex + grouping.Count 105 | // rearrange 106 | ParallelismHelpers.ReduceCombine(keys.Length, (fun () -> dict), 107 | (fun (dict : ConcurrentDictionary<'Key, Grouping<'Key, 'Value>>) index -> 108 | let mutable grouping = Unchecked.defaultof> 109 | dict.TryGetValue(keys.[index], &grouping) |> ignore 110 | 111 | let position = grouping.InterlockedIncrPosition() 112 | values'.[grouping.Index + position] <- values.[index] 113 | dict 114 | ), (fun dict _ -> dict), (fun x -> x)) |> ignore 115 | // collect results 116 | dict.Values |> Seq.map (fun value -> value :> _) 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/LinqOptimizer.Core.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | AnyCPU 6 | false 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/ParallelismHelpers.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | open System 3 | open System.Collections.Generic 4 | open System.Linq 5 | open System.Linq.Expressions 6 | open System.Reflection 7 | open System.Collections.Concurrent 8 | open System.Threading.Tasks 9 | 10 | type ParallelismHelpers = 11 | static member TotalWorkers = int (2.0 ** float (int (Math.Log(float Environment.ProcessorCount, 2.0)))) 12 | 13 | static member GetPartitions (s : int, e : int) = 14 | let toSeq (enum : IEnumerator<_>)= 15 | seq { 16 | while enum.MoveNext() do 17 | yield enum.Current 18 | } 19 | let partitioner = Partitioner.Create(s, e) 20 | let partitions = partitioner.GetPartitions(ParallelismHelpers.TotalWorkers) |> Seq.collect toSeq |> Seq.toArray 21 | partitions 22 | 23 | static member ReduceCombine<'T, 'Acc, 'R>( array : 'T[], 24 | init : Func<'Acc>, 25 | reducer : Func<'T[], int, int, 'Acc, 'Acc>, 26 | combiner : Func<'Acc, 'Acc, 'Acc>, 27 | selector : Func<'Acc, 'R>) : 'R = 28 | if array.Length = 0 then 29 | selector.Invoke(init.Invoke()) 30 | else 31 | let partitions = ParallelismHelpers.GetPartitions(0, array.Length) 32 | let cells = partitions |> Array.map (fun _ -> ref Unchecked.defaultof<'Acc>) 33 | let tasks = partitions |> Array.mapi (fun index (s, e) -> 34 | Task.Factory.StartNew(fun () -> 35 | let result = reducer.Invoke(array, s - 1, e - 1, init.Invoke()) 36 | cells.[index] := result 37 | ())) 38 | Task.WaitAll(tasks) 39 | let result = cells |> Array.fold (fun acc cell -> combiner.Invoke(acc, cell.Value)) (init.Invoke()) 40 | selector.Invoke(result) 41 | 42 | static member ReduceCombine<'T, 'Acc, 'R>(values : IList<'T>, 43 | init : Func<'Acc>, 44 | reducer : Func<'Acc, 'T, 'Acc>, 45 | combiner : Func<'Acc, 'Acc, 'Acc>, 46 | selector : Func<'Acc, 'R>) : 'R = 47 | ParallelismHelpers.ReduceCombine(values.Count, init, (fun acc index -> reducer.Invoke(acc, values.[index])), combiner, selector) 48 | 49 | 50 | static member ReduceCombine<'T, 'Acc, 'R>(length : int, 51 | init : Func<'Acc>, 52 | reducer : Func<'Acc, int, 'Acc>, 53 | combiner : Func<'Acc, 'Acc, 'Acc>, 54 | selector : Func<'Acc, 'R>) : 'R = 55 | if length = 0 then 56 | selector.Invoke(init.Invoke()) 57 | else 58 | let partitions = ParallelismHelpers.GetPartitions(0, length) 59 | let cells = partitions |> Array.map (fun _ -> ref Unchecked.defaultof<'Acc>) 60 | let tasks = partitions |> Array.mapi (fun index (s, e) -> 61 | Task.Factory.StartNew(fun () -> 62 | let result = 63 | let mutable r = init.Invoke() 64 | for i = s to e - 1 do 65 | r <- reducer.Invoke(r, i) 66 | r 67 | cells.[index] := result 68 | ())) 69 | Task.WaitAll(tasks) 70 | let result = cells |> Array.fold (fun acc cell -> combiner.Invoke(acc, cell.Value)) (init.Invoke()) 71 | selector.Invoke(result) 72 | 73 | 74 | 75 | static member ReduceCombine<'T, 'Acc, 'R>( partitioner : Partitioner<'T>, 76 | init : Func<'Acc>, 77 | reducer : Func<'Acc, 'T, 'Acc>, 78 | combiner : Func<'Acc, 'Acc, 'Acc>, 79 | selector : Func<'Acc, 'R>) : 'R = 80 | let partitions = partitioner.GetPartitions(ParallelismHelpers.TotalWorkers).ToArray() 81 | let cells = partitions |> Array.map (fun _ -> ref Unchecked.defaultof<'Acc>) 82 | let tasks = partitions |> Array.mapi (fun index partition -> 83 | Task.Factory.StartNew(fun () -> 84 | let result = 85 | let mutable r = init.Invoke() 86 | while partition.MoveNext() do 87 | r <- reducer.Invoke(r, partition.Current) 88 | r 89 | cells.[index] := result 90 | ())) 91 | Task.WaitAll(tasks) 92 | let result = cells |> Array.fold (fun acc cell -> combiner.Invoke(acc, cell.Value)) (init.Invoke()) 93 | selector.Invoke(result) 94 | 95 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/QueryExpr.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Text 10 | open Nessos.LinqOptimizer.Core.Utils 11 | 12 | // // Typed Wrapper for QueryExpr 13 | // type QueryExpr<'T>(queryExpr : QueryExpr) = 14 | // member self.QueryExpr = queryExpr 15 | // type ParallelQueryExpr<'T>(queryExpr : QueryExpr) = 16 | // member self.QueryExpr = queryExpr 17 | // C# friendly QueryExpr 18 | // and QueryExprVoid(queryExpr : QueryExpr) = 19 | // member self.QueryExpr = queryExpr 20 | // and ParallelQueryExprVoid(queryExpr : QueryExpr) = 21 | // member self.QueryExpr = queryExpr 22 | // Main Query representation 23 | type QueryExprType = Sequential | Parallel | Gpu 24 | type Order = Ascending | Descending 25 | type ReductionType = Map | Filter | Count | Sum | Aggregate | ToList | ToArray | Iter | NestedQueryTransform 26 | /// The type representing an query expression. 27 | and QueryExpr = 28 | | Source of Expression * Type * QueryExprType 29 | | Generate of Expression * LambdaExpression * LambdaExpression * LambdaExpression 30 | | Transform of LambdaExpression * QueryExpr 31 | | TransformIndexed of LambdaExpression * QueryExpr 32 | | Filter of LambdaExpression * QueryExpr 33 | | FilterIndexed of LambdaExpression * QueryExpr 34 | | NestedQuery of (ParameterExpression * QueryExpr) * QueryExpr 35 | | NestedQueryTransform of (ParameterExpression * QueryExpr) * LambdaExpression * QueryExpr 36 | | Aggregate of Expression * LambdaExpression * QueryExpr 37 | | Sum of QueryExpr 38 | | Count of QueryExpr 39 | | Take of Expression * QueryExpr 40 | | TakeWhile of LambdaExpression * QueryExpr 41 | | SkipWhile of LambdaExpression * QueryExpr 42 | | Skip of Expression * QueryExpr 43 | | ForEach of LambdaExpression * QueryExpr 44 | | GroupBy of LambdaExpression * QueryExpr * Type 45 | | OrderBy of (LambdaExpression * Order) list * QueryExpr 46 | | ToList of QueryExpr 47 | | ToArray of QueryExpr 48 | | RangeGenerator of Expression * Expression 49 | | RepeatGenerator of Expression * Expression 50 | | ZipWith of Expression * Expression * LambdaExpression 51 | with 52 | 53 | member self.Type = 54 | match self with 55 | | Source (_, t, _) -> t 56 | | Generate(_,_,_, selector) -> selector.Body.Type 57 | | Transform (lambda, _) -> lambda.Body.Type 58 | | TransformIndexed (lambda, _) -> lambda.Body.Type 59 | | Filter (_, q) -> q.Type 60 | | FilterIndexed (_, q) -> q.Type 61 | | NestedQuery ((_, q), _) -> q.Type 62 | | NestedQueryTransform ((_, _), resultSelector, _) -> resultSelector.Body.Type 63 | | Aggregate (seed, _, _) -> seed.Type 64 | | Sum (q) -> q.Type 65 | | Count (q) -> q.Type 66 | | Take (_, q) -> q.Type 67 | | TakeWhile(_,q) -> q.Type 68 | | SkipWhile(_,q) -> q.Type 69 | | Skip (_, q) -> q.Type 70 | | ForEach (_, _) -> typeof 71 | | GroupBy (_, _, t) -> t 72 | | OrderBy (_, q) -> q.Type 73 | | ToList q -> q.Type 74 | | ToArray q -> q.Type 75 | | RangeGenerator _ -> typeof 76 | | RepeatGenerator (objExpr,_) -> objExpr.Type 77 | | ZipWith (_,_,f) -> f.ReturnType 78 | 79 | override self.ToString() = 80 | let str (expr : Expression ) = expr.ToString() 81 | let rec toString (query : QueryExpr) : string = 82 | match query with 83 | | Source (expr, t, queryExprType) -> 84 | match queryExprType with 85 | | Sequential -> sprintf' "Source (%s, %s, Sequential)" <| str expr <| t.ToString() 86 | | Parallel -> sprintf' "Source (%s, %s, Parallel)" <| str expr <| t.ToString() 87 | | Gpu -> sprintf' "Source (%s, %s, Gpu)" <| str expr <| t.ToString() 88 | | Generate(expr1, expr2, expr3, expr4) -> 89 | sprintf' "Generate (%s, %s, %s, %s)" <| str expr1 <| str expr2 <| str expr3 <| str expr4 90 | | Transform (lambda, query) -> 91 | sprintf' "Transform [%s](%s, %s)" <| lambda.Body.Type.ToString() <| str lambda <| toString query 92 | | TransformIndexed (lambda, query) -> 93 | sprintf' "TransformIndexed [%s](%s, %s)" <| lambda.Body.Type.ToString() <| str lambda <| toString query 94 | | Filter (lambda, query) -> 95 | sprintf' "Filter (%s, %s)" <| str lambda <| toString query 96 | | FilterIndexed (lambda, query) -> 97 | sprintf' "FilterIndexed (%s, %s)" <| str lambda <| toString query 98 | | NestedQuery ((param, query), query') -> 99 | sprintf' "NestedQuery [%s](%s, %s, %s)" <| query.Type.ToString() <| str param <| toString query <| toString query' 100 | | NestedQueryTransform ((param, query), resultSelector, query') -> 101 | sprintf' "NestedQueryTransform [%s](%s, %s, %s, %s)" <| query.Type.ToString() <| str param <| toString query <| str resultSelector <| toString query' 102 | | Aggregate (seed, acc, query) -> 103 | sprintf' "Aggregate [%s](%s, %s, %s)" <| acc.Body.Type.ToString() <| str seed <| str acc <| toString query 104 | | Sum (query) -> 105 | sprintf' "Sum (%s, %s)" <| toString query <| query.Type.ToString() 106 | | Count (query) -> 107 | sprintf' "Count (%s)" <| toString query 108 | | Take (takeCount, query) -> 109 | sprintf' "Take (%s, %s)" <| str takeCount <| toString query 110 | | TakeWhile(lambda, query) -> 111 | sprintf' "TakeWhile (%s, %s)" <| str lambda <| toString query 112 | | SkipWhile(lambda, query) -> 113 | sprintf' "SkipWhile (%s, %s)" <| str lambda <| toString query 114 | | Skip (skipCount, query) -> 115 | sprintf' "Skip (%s, %s)" <| str skipCount <| toString query 116 | | ForEach (lambda, query) -> 117 | sprintf' "ForEach (%s, %s)" <| str lambda <| toString query 118 | | GroupBy (lambda, query, _) -> 119 | sprintf' "GroupBy (%s, %s)" <| str lambda <| toString query 120 | | OrderBy (lambdaOrderPairs, query) -> 121 | List.foldBack 122 | (fun (lambda, order) s -> 123 | match order with 124 | | Ascending -> sprintf' "OrderBy (%s, Asc, %s)" <| str lambda <| s 125 | | Descending -> sprintf' "OrderBy (%s, Desc, %s)" <| str lambda <| s) 126 | lambdaOrderPairs (toString query) 127 | | ToList query -> 128 | sprintf' "ToList [%s](%s)" <| query.Type.ToString() <| toString query 129 | | ToArray query -> 130 | sprintf' "ToArray [%s](%s)" <| query.Type.ToString() <| toString query 131 | | RangeGenerator (expr1, expr2) -> 132 | sprintf' "RangeGenerator (%s, %s)" <| str expr1 <| str expr2 133 | | RepeatGenerator (expr1, expr2) -> 134 | sprintf' "RepeatGenerator (%s, %s)" <| str expr1 <| str expr2 135 | | ZipWith(l,r,f) -> 136 | sprintf' "ZipWith (%s, %s, %s)" <| str l <| str r <| str f 137 | 138 | toString self 139 | 140 | static member AddOrderBy(keySelector : LambdaExpression, order : Order, queryExpr : QueryExpr) = 141 | match queryExpr with 142 | | OrderBy (keySelectorOrderPairs, queryExpr') -> 143 | OrderBy ((keySelector, order) :: keySelectorOrderPairs, queryExpr') 144 | | _ -> OrderBy ([keySelector, order], queryExpr) 145 | 146 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/ReflectionHelpers.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | open System 3 | open System.Linq.Expressions 4 | open System.Reflection 5 | 6 | [] 7 | module ReflectionHelpers = 8 | 9 | let (|MethodName|_|) (methodName : string) (methodInfo : MethodInfo) = 10 | if methodInfo.Name = methodName then 11 | Some <| methodInfo.GetParameters() 12 | else None 13 | 14 | 15 | let (|ParameterName|_|) (parameterName : string) (parameterInfo : ParameterInfo) = 16 | if parameterInfo.Name = parameterName then 17 | Some parameterInfo 18 | else None 19 | 20 | let (|TypeCheck|_|) (paramTypeDef : Type) (typeDef : Type) = 21 | if paramTypeDef = typeDef then 22 | Some typeDef 23 | else None 24 | 25 | let (|ParamType|) (paramInfo : ParameterInfo) = paramInfo.ParameterType 26 | 27 | let (|Named|Array|Ptr|Param|) (t : System.Type) = 28 | if t.IsGenericType 29 | then Named(t.GetGenericTypeDefinition(), t.GetGenericArguments()) 30 | elif t.IsGenericParameter 31 | then Param(t.GenericParameterPosition) 32 | elif not t.HasElementType 33 | then Named(t, [| |]) 34 | elif t.IsArray 35 | then Array(t.GetElementType(), t.GetArrayRank()) 36 | elif t.IsByRef 37 | then Ptr(true, t.GetElementType()) 38 | elif t.IsPointer 39 | then Ptr(false, t.GetElementType()) 40 | else failwith "MSDN says this can’t happen" 41 | 42 | let getIEnumerableType (ty : Type) = 43 | ty.GetInterface("IEnumerable`1").GetGenericArguments().[0] 44 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/Session.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Reflection.Emit 10 | 11 | type internal Session private () = 12 | static member private GetNewMethodName () = "LinqOptMethod" 13 | static member private GetNewTypeName () = "LinqOptTy_" + Guid.NewGuid().ToString("N") 14 | static member private GetNewAssemblyName () = "LinqOptAsm_" + Guid.NewGuid().ToString("N") 15 | static member private ModuleName = "Module" 16 | 17 | 18 | static member private ModuleBuilder 19 | with get () = 20 | lazy ( let asmBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName(Session.GetNewAssemblyName()), AssemblyBuilderAccess.Run) 21 | let moduleBuilder = asmBuilder.DefineDynamicModule(Session.ModuleName) 22 | moduleBuilder ) 23 | 24 | static member internal Compile(expr : LambdaExpression) = 25 | let methodName = Session.GetNewMethodName() 26 | let typeBuilder = Session.ModuleBuilder.Value.DefineType(Session.GetNewTypeName(), TypeAttributes.Public) 27 | let methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public ||| MethodAttributes.Static) 28 | expr.Compile() -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/SortingHelpers.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Threading.Tasks 8 | open System.Threading 9 | 10 | 11 | type private MergeArrayType = 12 | | FromArrayType 13 | | ToArrayType 14 | 15 | 16 | 17 | type Sort = 18 | 19 | static member SequentialSort(keys : 'Key[], values : 'Value[], orders : Order[]) = 20 | Array.Sort(keys, values) 21 | if orders.Length = 1 then 22 | match orders.[0] with 23 | | Descending -> 24 | Array.Reverse(values) 25 | | _ -> () 26 | 27 | 28 | 29 | static member ParallelSort<'Key, 'Value when 'Key :> IComparable<'Key>>(keys : 'Key[], array : 'Value[], orders : Order[]) = 30 | // Taken from Carl Nolan's parallel inplace merge 31 | // The merge of the two array 32 | let merge (toArray: 'Value []) (toKeys : 'Key[]) (fromArray: 'Value []) (fromKeys : 'Key[]) (low1: int) (low2: int) (high1: int) (high2: int) = 33 | let mutable ptr1 = low1 34 | let mutable ptr2 = high1 35 | 36 | for ptr = low1 to high2 do 37 | if (ptr1 > low2) then 38 | toArray.[ptr] <- fromArray.[ptr2] 39 | toKeys.[ptr] <- fromKeys.[ptr2] 40 | ptr2 <- ptr2 + 1 41 | elif (ptr2 > high2) then 42 | toArray.[ptr] <- fromArray.[ptr1] 43 | toKeys.[ptr] <- fromKeys.[ptr1] 44 | ptr1 <- ptr1 + 1 45 | elif (fromKeys.[ptr1].CompareTo(fromKeys.[ptr2]) <= 0) then 46 | toArray.[ptr] <- fromArray.[ptr1] 47 | toKeys.[ptr] <- fromKeys.[ptr1] 48 | ptr1 <- ptr1 + 1 49 | else 50 | toArray.[ptr] <- fromArray.[ptr2] 51 | toKeys.[ptr] <- fromKeys.[ptr2] 52 | ptr2 <- ptr2 + 1 53 | 54 | // define the sort operation 55 | let parallelSort () = 56 | 57 | // control flow parameters 58 | let totalWorkers = int (2.0 ** float (int (Math.Log(float Environment.ProcessorCount, 2.0)))) 59 | let auxArray : 'Value array = Array.zeroCreate array.Length 60 | let auxKeys : 'Key array = Array.zeroCreate array.Length 61 | let workers : Task array = Array.zeroCreate (totalWorkers - 1) 62 | let iterations = int (Math.Log((float totalWorkers), 2.0)) 63 | 64 | 65 | 66 | // Number of elements for each array, if the elements number is not divisible by the workers 67 | // the remainders will be added to the first worker (the main thread) 68 | let partitionSize = ref (int (array.Length / totalWorkers)) 69 | let remainder = array.Length % totalWorkers 70 | 71 | // Define the arrays references for processing as they are swapped during each iteration 72 | let swapped = ref false 73 | 74 | let inline getMergeArray (arrayType: MergeArrayType) = 75 | match (arrayType, !swapped) with 76 | | (FromArrayType, true) -> (auxArray, auxKeys) 77 | | (FromArrayType, false) -> (array, keys) 78 | | (ToArrayType, true) -> (array, keys) 79 | | (ToArrayType, false) -> (auxArray, auxKeys) 80 | 81 | use barrier = new Barrier(totalWorkers, fun (b) -> 82 | partitionSize := !partitionSize <<< 1 83 | swapped := not !swapped) 84 | 85 | // action to perform the sort an merge steps 86 | let action (index: int) = 87 | 88 | //calculate the partition boundary 89 | let low = index * !partitionSize + match index with | 0 -> 0 | _ -> remainder 90 | let high = (index + 1) * !partitionSize - 1 + remainder 91 | 92 | // Sort the specified range - could implement QuickSort here 93 | let sortLen = high - low + 1 94 | Array.Sort(keys, array, low, sortLen) 95 | 96 | barrier.SignalAndWait() 97 | 98 | let rec loopArray loopIdx actionIdx loopHigh = 99 | if loopIdx < iterations then 100 | if (actionIdx % 2 = 1) then 101 | barrier.RemoveParticipant() 102 | else 103 | let newHigh = loopHigh + !partitionSize / 2 104 | let toArray, toKeys = getMergeArray FromArrayType 105 | let fromArray, fromKeys = getMergeArray ToArrayType 106 | merge toArray toKeys fromArray fromKeys low loopHigh (loopHigh + 1) newHigh 107 | barrier.SignalAndWait() 108 | loopArray (loopIdx + 1) (actionIdx >>> 1) newHigh 109 | loopArray 0 index high 110 | 111 | for index in 1 .. workers.Length do 112 | workers.[index - 1] <- Task.Factory.StartNew(fun() -> action index) 113 | 114 | action 0 115 | 116 | // if odd iterations return auxArray otherwise array (swapped will be false) 117 | if not (iterations % 2 = 0) then 118 | Array.blit auxArray 0 array 0 array.Length 119 | 120 | parallelSort() 121 | if orders.Length = 1 then 122 | match orders.[0] with 123 | | Descending -> 124 | Array.Reverse(array) 125 | | _ -> () 126 | 127 | // Composite keys for sorting, used only for code generation 128 | [] 129 | [] 130 | [] 131 | type Keys<'T1, 'T2 when 'T1 :> IComparable<'T1> and 'T2 :> IComparable<'T2>> 132 | (t1 : 'T1, t2 : 'T2, o1 : Order, o2 : Order) = 133 | member self.T1 = t1 134 | member self.T2 = t2 135 | 136 | override self.Equals(_) = 137 | raise <| new NotImplementedException() 138 | override self.GetHashCode() = 139 | raise <| new NotImplementedException() 140 | 141 | interface IComparable> with 142 | 143 | member self.CompareTo(keys : Keys<'T1, 'T2>) = 144 | let cmpt1 = t1.CompareTo(keys.T1) 145 | if cmpt1 = 0 then 146 | if o2 = Order.Ascending then 147 | t2.CompareTo(keys.T2) 148 | else 149 | -t2.CompareTo(keys.T2) 150 | else 151 | if o1 = Order.Ascending then 152 | cmpt1 153 | else 154 | -cmpt1 155 | 156 | interface IComparable with 157 | member self.CompareTo(_) = 158 | raise <| new NotImplementedException() 159 | 160 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/TypeCollectorVisitor.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | open System.Collections.Concurrent 10 | 11 | type private TypeCollectorVisitor () = 12 | inherit ExpressionVisitor() with 13 | 14 | let types = List() 15 | 16 | member this.Types with get () = types 17 | 18 | override this.VisitNew(expr : NewExpression) = 19 | types.Add(expr.Type) 20 | expr.Update(this.Visit expr.Arguments) :> _ 21 | 22 | override this.VisitParameter(expr : ParameterExpression) = 23 | types.Add(expr.Type) 24 | expr :> _ 25 | 26 | override this.VisitMember(expr : MemberExpression) = 27 | match expr.Member with 28 | | :? PropertyInfo as pi -> 29 | if pi.GetGetMethod() = null then types.Add(expr.Member.DeclaringType) 30 | | :? FieldInfo as fi -> 31 | types.Add(expr.Member.DeclaringType) 32 | | _ -> () 33 | 34 | expr.Update(this.Visit expr.Expression) :> _ 35 | 36 | override this.VisitMethodCall(expr : MethodCallExpression) = 37 | types.Add(expr.Method.DeclaringType) 38 | expr.Update(this.Visit expr.Object, this.Visit expr.Arguments) :> _ 39 | 40 | // cast 41 | override this.VisitUnary(expr : UnaryExpression) = 42 | types.Add(expr.Type) 43 | expr.Update(this.Visit expr.Operand) :> _ 44 | 45 | module TypeCollector = 46 | let getTypes(exprs : seq) : seq = 47 | exprs 48 | |> Seq.collect (fun expr -> 49 | let tg = new TypeCollectorVisitor() 50 | tg.Visit(expr) |> ignore 51 | tg.Types) 52 | -------------------------------------------------------------------------------- /src/LinqOptimizer.Core/Utils.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Core 2 | open System 3 | open System.Collections.Concurrent 4 | 5 | // anton tayanovskyy's fast sprintf' 6 | type Cache<'T> private () = 7 | static let d = ConcurrentDictionary() 8 | 9 | static member Format(format: Printf.StringFormat<'T>) : 'T = 10 | let key = format.Value 11 | match d.TryGetValue(key) with 12 | | true, r -> r 13 | | _ -> 14 | let r = sprintf format 15 | d.TryAdd(key, r) |> ignore 16 | r 17 | 18 | module Utils = 19 | 20 | 21 | let sprintf' fmt = 22 | Cache<_>.Format(fmt) 23 | 24 | -------------------------------------------------------------------------------- /src/LinqOptimizer.FSharp/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | do () 11 | 12 | module internal AssemblyVersionInformation = 13 | let [] AssemblyTitle = "LinqOptimizer" 14 | let [] AssemblyProduct = "LinqOptimizer" 15 | let [] AssemblyCompany = "Nessos Information Technologies" 16 | let [] AssemblyVersion = "0.7.0" 17 | let [] AssemblyFileVersion = "0.7.0" 18 | -------------------------------------------------------------------------------- /src/LinqOptimizer.FSharp/LinqOptimizer.FSharp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | AnyCPU 6 | false 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/LinqOptimizer.FSharp/QueryBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.FSharp 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Linq 7 | open System.Linq.Expressions 8 | open System.Reflection 9 | 10 | open Microsoft.FSharp.Linq 11 | open Microsoft.FSharp.Quotations 12 | open Microsoft.FSharp.Quotations.Patterns 13 | open Microsoft.FSharp.Quotations.DerivedPatterns 14 | 15 | open Nessos.LinqOptimizer.Base 16 | 17 | type private QueryBuilder () = 18 | 19 | member __.Quote (expr : Expr<'T>) = expr 20 | member __.Source(source:IEnumerable<'T>) : IQueryExpr> = raise <| NotImplementedException() 21 | 22 | [] 23 | member __.Select(source : QueryExpr>, [] projection:('T -> 'R)) : QueryExpr> = raise <| NotImplementedException() 24 | 25 | member __.For(source:QueryExpr>, body:('T -> QueryExpr<'R>)) : QueryExpr<'R> = raise <| NotImplementedException() 26 | 27 | [] 28 | member __.Where(source:QueryExpr<'T>,[] selector: 'T -> bool) : QueryExpr<'T> = raise <| NotImplementedException() 29 | 30 | member __.Yield (x : 'T) : QueryExpr> = raise <| NotImplementedException() 31 | 32 | member __.Zero () : QueryExpr<_> = raise <| NotImplementedException() 33 | 34 | member __.Run (expr : Expr<'T>) = expr 35 | 36 | module private QueryBuilderCompiler = 37 | 38 | let toExpression (expr : Expr) : Expression = 39 | Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToExpression(expr) 40 | 41 | let evalExpr<'T> (expr : Expr) = 42 | Linq.RuntimeHelpers.LeafExpressionConverter.EvaluateQuotation(expr) :?> 'T 43 | 44 | // let rec transform (expr : Expr>>) : Expr>> = 45 | // match expr with 46 | // | Call (o ,m, [seqExpr]) when m.Name = "Source" -> 47 | // let source = evalExpr> seqExpr 48 | // <@ ExtensionMethods.AsQueryExpr(source) @> 49 | // Expr.Call() 50 | // | Call (o ,m, [source; func]) when m.Name = "Select" -> 51 | // let source = source :?> Expr>> 52 | // let func = func :?> Expr> 53 | // 54 | // let func = toExpression func 55 | // let source = source :?> Expr 56 | // let source = toExpression (transform source) 57 | // <@ Query.map func source @> 58 | // | _ -> 59 | // expr 60 | // 61 | // [] 62 | // module Extensions = 63 | // let query = new QueryBuilder () 64 | // 65 | // let x = 66 | // (query { for x in 1..10 do select x }) 67 | // 68 | // QueryBuilderCompiler.transform x 69 | // 70 | // match x with 71 | // | Call (_,m, [source; func]) when m.Name = "For" -> 72 | // func.Type :> obj 73 | // | Call (o ,m, [source; func]) when m.Name = "Select" -> 74 | // (source.Type.ToString(), func.Type.ToString()) :> obj 75 | // 76 | // |> ignore 77 | // 78 | // Call (Some (Value (MBI_0024+QueryBuilder)), For, 79 | // [), 80 | // Lambda (_arg1, 81 | // Let (x, _arg1, 82 | // Call (Some (Value (MBI_0024+QueryBuilder)), Yield, [x])))]) 83 | // 84 | // Lambda (x, x) 85 | // 86 | //"MBI_0010+QueryExpr`1[System.Collections.Generic.IEnumerable`1[System.Int32]]", 87 | //"Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32]" 88 | // 89 | //Call (Some (Value (MBI_0024+QueryBuilder)), Source, [Call (None, op_Range, [Value (1), Value (10)])] 90 | // 91 | // 92 | //let x = 93 | // Linq.RuntimeHelpers.LeafExpressionConverter.EvaluateQuotation <@@ (fun x -> x + 1) @@> :?> (int -> int) 94 | // 95 | // 96 | // module F = 97 | // open System.Linq 98 | // 99 | // let x = Enumerable.Repeat(1,10) 100 | // let ty = x.GetType() 101 | // ty.FullName = "System.Linq.Enumerable+d__b8" 102 | // ty.GetFields() |> Array.iter (fun m -> printfn "%A" m.Name) 103 | // ty.GetField("<>3__start").GetValue(x) 104 | // ty.GetField("<>3__count").GetValue(x) 105 | // 106 | -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.CSharp/LinqOptimizer.Tests.CSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2;net461 5 | AnyCPU 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.CSharp/ParallelQueryTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Nessos.LinqOptimizer.CSharp; 7 | using FsCheck; 8 | 9 | namespace Nessos.LinqOptimizer.Tests 10 | { 11 | [TestFixture] 12 | class ParallelQueryTests 13 | { 14 | [Test] 15 | public void Select() 16 | { 17 | Prop.ForAll(xs => 18 | { 19 | var x = xs.AsParallelQueryExpr().Select(n => n * 2).Sum().Run(); 20 | var y = xs.AsParallel().Select(n => n * 2).Sum(); 21 | return x == y; 22 | }).QuickCheckThrowOnFailure(); 23 | } 24 | 25 | [Test] 26 | public void Where() 27 | { 28 | Prop.ForAll(xs => 29 | { 30 | var x = (from n in xs.AsParallelQueryExpr() 31 | where n % 2 == 0 32 | select n) 33 | .Sum() 34 | .Run(); 35 | var y = (from n in xs.AsParallel() 36 | where n % 2 == 0 37 | select n) 38 | .Sum(); 39 | 40 | return x == y; 41 | }).QuickCheckThrowOnFailure(); 42 | } 43 | 44 | [Test] 45 | public void Pipelined() 46 | { 47 | Prop.ForAll(xs => 48 | { 49 | var x = xs.AsParallelQueryExpr() 50 | .Where(n => n % 2 == 0) 51 | .Select(n => n * 2) 52 | .Select(n => n * n) 53 | .Sum() 54 | .Run(); 55 | 56 | var y = xs 57 | .AsParallel() 58 | .Where(n => n % 2 == 0) 59 | .Select(n => n * 2) 60 | .Select(n => n * n) 61 | .Sum(); 62 | 63 | return x == y; 64 | }).QuickCheckThrowOnFailure(); 65 | } 66 | 67 | [Test] 68 | public void SumInt() 69 | { 70 | Prop.ForAll(xs => 71 | { 72 | var x = (from n in xs.AsParallelQueryExpr() 73 | select n * 2).Sum().Run(); 74 | var y = (from n in xs.AsParallel() 75 | select n * 2).Sum(); 76 | 77 | return x == y; 78 | }).QuickCheckThrowOnFailure(); 79 | } 80 | 81 | [Test] 82 | public void SumDouble() 83 | { 84 | Prop.ForAll(xs => 85 | { 86 | var x = (from n in xs.AsParallelQueryExpr() 87 | select n * 2).Sum().Run(); 88 | var y = (from n in xs.AsParallel() 89 | select n * 2).Sum(); 90 | 91 | return (Double.IsNaN(x) && Double.IsNaN(y)) || Math.Ceiling(x) == Math.Ceiling(y); 92 | }).QuickCheckThrowOnFailure(); 93 | } 94 | 95 | [Test] 96 | public void SumLong() 97 | { 98 | Prop.ForAll(xs => 99 | { 100 | var x = (from n in xs.AsParallelQueryExpr() 101 | select n * 2).Sum().Run(); 102 | var y = (from n in xs.AsParallel() 103 | select n * 2).Sum(); 104 | 105 | return x == y; 106 | }).QuickCheckThrowOnFailure(); 107 | } 108 | 109 | [Test] 110 | public void SelectMany() 111 | { 112 | Prop.ForAll(xs => 113 | { 114 | var x = xs.AsParallelQueryExpr() 115 | .SelectMany(n => xs.Select(_n => n * _n)) 116 | .Sum().Run(); 117 | var y = xs.AsParallel() 118 | .SelectMany(n => xs.Select(_n => n * _n)) 119 | .Sum(); 120 | 121 | return x == y; 122 | }).QuickCheckThrowOnFailure(); 123 | } 124 | 125 | [Test] 126 | public void SelectManyNested() 127 | { 128 | Prop.ForAll(xs => 129 | { 130 | var x = xs.AsParallelQueryExpr() 131 | .SelectMany(num => xs.SelectMany(_num => new[] { num * _num })) 132 | .Sum().Run(); 133 | var y = xs 134 | .AsParallel() 135 | .SelectMany(num => xs.SelectMany(_num => new[] { num * _num })) 136 | .Sum(); 137 | 138 | return x == y; 139 | }).QuickCheckThrowOnFailure(); 140 | } 141 | 142 | [Test] 143 | public void SelectManyPipeline() 144 | { 145 | Prop.ForAll(xs => 146 | { 147 | var x = xs.AsParallelQueryExpr() 148 | .Where(num => num % 2 == 0) 149 | .SelectMany(num => xs.Select(_num => num * _num)) 150 | .Select(i => i + 1) 151 | .Sum() 152 | .Run(); 153 | 154 | var y = xs 155 | .AsParallel() 156 | .Where(num => num % 2 == 0) 157 | .SelectMany(num => xs.Select(_num => num * _num)) 158 | .Select(i => i + 1) 159 | .Sum(); 160 | 161 | return x == y; 162 | }).QuickCheckThrowOnFailure(); 163 | } 164 | 165 | [Test] 166 | public void SelectManyCompehension() 167 | { 168 | Prop.ForAll(xs => 169 | { 170 | var x = (from num in xs.AsParallelQueryExpr() 171 | from _num in xs 172 | select num + " " + _num).Run(); 173 | var y = (from num in xs.AsParallel().AsOrdered() 174 | from _num in xs 175 | select num + " " + _num); 176 | 177 | return x.SequenceEqual(y); 178 | }).QuickCheckThrowOnFailure(); 179 | } 180 | 181 | [Test] 182 | public void GroupBy() 183 | { 184 | Prop.ForAll(xs => 185 | { 186 | #if MONO_BUILD 187 | var x = xs.AsParallelQueryExpr().GroupBy(num => num.ToString()).Select(g => g.Count()).Sum().Run(); 188 | #else 189 | var x = (from num in xs.AsParallelQueryExpr() 190 | group num by num.ToString() into g 191 | select g.Count()).Sum() 192 | .Run(); 193 | #endif 194 | 195 | var y = (from num in xs.AsParallel() 196 | group num by num.ToString() into g 197 | select g.Count()).Sum(); 198 | 199 | return x == y; 200 | }).QuickCheckThrowOnFailure(); 201 | } 202 | 203 | [Test] 204 | public void OrderBy() 205 | { 206 | Prop.ForAll(xs => 207 | { 208 | var x = (from num in xs.AsParallelQueryExpr() 209 | orderby num 210 | select num * 2) 211 | .Run(); 212 | 213 | var y = from num in xs.AsParallel() 214 | orderby num 215 | select num * 2; 216 | 217 | return x.SequenceEqual(y); 218 | }).QuickCheckThrowOnFailure(); 219 | } 220 | 221 | [Test] 222 | public void OrderByDescending() 223 | { 224 | Prop.ForAll(xs => 225 | { 226 | var x = (from num in xs.AsParallelQueryExpr() 227 | orderby num descending 228 | select num * 2) 229 | .Run(); 230 | 231 | var y = from num in xs.AsParallel() 232 | orderby num descending 233 | select num * 2; 234 | 235 | return x.SequenceEqual(y); 236 | }).QuickCheckThrowOnFailure(); 237 | } 238 | 239 | 240 | [Test] 241 | public void Count() 242 | { 243 | Prop.ForAll(xs => 244 | { 245 | var x = xs.AsParallelQueryExpr() 246 | .Select(i => i) 247 | .Count() 248 | .Run(); 249 | 250 | var y = xs.AsParallel() 251 | .Select(i => i) 252 | .Count(); 253 | 254 | return x == y; 255 | }).QuickCheckThrowOnFailure(); 256 | } 257 | 258 | [Test] 259 | public void ToList() 260 | { 261 | Prop.ForAll(xs => 262 | { 263 | var x = xs.AsParallelQueryExpr() 264 | .ToList() 265 | .Run(); 266 | 267 | var y = xs.AsParallel() 268 | .ToList(); 269 | 270 | return x.OrderBy(i => i).SequenceEqual(y.OrderBy(i => i)); 271 | }).QuickCheckThrowOnFailure(); 272 | } 273 | 274 | [Test] 275 | public void ToArray() 276 | { 277 | Prop.ForAll>(xs => 278 | { 279 | var x = xs.AsParallelQueryExpr() 280 | .ToArray() 281 | .Run(); 282 | 283 | var y = xs.AsParallel() 284 | .ToArray(); 285 | 286 | return x.OrderBy(i => i).SequenceEqual(y.OrderBy(i => i)); 287 | }).QuickCheckThrowOnFailure(); 288 | } 289 | 290 | [Test] 291 | public void LinqLetTest() 292 | { 293 | Prop.ForAll>(nums => 294 | { 295 | var x = 296 | (from num in nums.AsParallelQueryExpr() 297 | let r1 = num * 2 298 | let r2 = r1 * 2 299 | select r2 * num * 2).Sum().Run(); 300 | 301 | var y = 302 | (from num in nums.AsParallel() 303 | let r1 = num * 2 304 | let r2 = r1 * 2 305 | select r2 * num * 2).Sum(); 306 | 307 | return x == y; 308 | }).QuickCheckThrowOnFailure(); 309 | } 310 | 311 | [Test] 312 | public void ThenBy() 313 | { 314 | Prop.ForAll(ds => 315 | { 316 | var x = ds.AsParallelQueryExpr() 317 | .OrderBy(d => d.Year) 318 | .ThenBy(d => d.Month) 319 | .ThenBy(d => d.Day) 320 | .ThenBy(d => d) 321 | .Run(); 322 | 323 | var y = ds.AsParallel().AsOrdered() 324 | .OrderBy(d => d.Year) 325 | .ThenBy(d => d.Month) 326 | .ThenBy(d => d.Day) 327 | .ThenBy(d => d); 328 | 329 | return x.SequenceEqual(y); 330 | }).QuickCheckThrowOnFailure(); 331 | } 332 | 333 | [Test] 334 | public void ThenByDescending() 335 | { 336 | Prop.ForAll(ds => 337 | { 338 | var x = (ds.AsParallelQueryExpr() 339 | .OrderByDescending(d => d.Year) 340 | .ThenBy(d => d.Month) 341 | .ThenByDescending(d => d.Day) 342 | .Select(d => d.Year + ":" + d.Month + ":" + d.Day)).Run(); 343 | 344 | var y = ds.AsParallel() 345 | .OrderByDescending(d => d.Year) 346 | .ThenBy(d => d.Month) 347 | .ThenByDescending(d => d.Day) 348 | .Select(d => d.Year + ":" + d.Month + ":" + d.Day); 349 | 350 | return x.SequenceEqual(y); 351 | }).QuickCheckThrowOnFailure(); 352 | } 353 | 354 | [Test] 355 | public void PreCompileFunc() 356 | { 357 | Prop.ForAll(i => 358 | { 359 | if (i < 1) return true; 360 | 361 | var t = ParallelExtensions.Compile(m => 362 | Enumerable.Range(1, m).AsParallelQueryExpr().Sum()); 363 | 364 | var x = t(i); 365 | 366 | var y = Enumerable.Range(1, i).AsParallel().Sum(); 367 | 368 | return x == y; 369 | }).QuickCheckThrowOnFailure(); 370 | } 371 | 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.CSharp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.FSharp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.FSharp/LinqOptimizer.Tests.FSharp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2;net461 5 | AnyCPU 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.FSharp/PQuery.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Tests 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Linq 6 | open System.Text 7 | open FsCheck 8 | open NUnit.Framework 9 | open Nessos.LinqOptimizer.FSharp 10 | 11 | 12 | [] 13 | type ``F# Parallel Query tests`` () = 14 | 15 | let equal x y = Enumerable.SequenceEqual(x,y) 16 | 17 | [] 18 | member __.``map`` () = 19 | let test (xs : seq) = 20 | let x = xs |> PQuery.ofSeq |> PQuery.map (fun n -> 2 * n) |> PQuery.run 21 | let y = xs |> Seq.map (fun n -> 2 * n) 22 | Seq.sum x = Seq.sum y 23 | Check.QuickThrowOnFailure (TestInput.RunTest test) 24 | 25 | [] 26 | member __.``filter`` () = 27 | let test (xs : seq) = 28 | let x = xs |> PQuery.ofSeq |> PQuery.filter (fun n -> n % 2 = 0) |> PQuery.run 29 | let y = xs |> Seq.filter (fun n -> n % 2 = 0) 30 | Seq.sum x = Seq.sum y 31 | Check.QuickThrowOnFailure (TestInput.RunTest test) 32 | 33 | [] 34 | member __.``pipelined`` () = 35 | let test (xs : seq) = 36 | let x = xs |> PQuery.ofSeq 37 | |> PQuery.filter (fun n -> n % 2 = 0) 38 | |> PQuery.map (fun n -> n * 2) 39 | |> PQuery.run 40 | 41 | let y = xs |> Seq.filter (fun n -> n % 2 = 0) 42 | |> Seq.map (fun n -> n * 2) 43 | Seq.sum x = Seq.sum y 44 | Check.QuickThrowOnFailure (TestInput.RunTest test) 45 | 46 | [] 47 | member __.``sum int`` () = 48 | let test (xs : seq) = 49 | let x = xs |> PQuery.ofSeq |> PQuery.map (fun x -> x * x) |> PQuery.sum |> PQuery.run 50 | let y = xs |> Seq.map (fun x -> x * x) |> Seq.sum 51 | x = y 52 | Check.QuickThrowOnFailure (TestInput.RunTest test) 53 | 54 | [] 55 | member __.``sum double`` () = 56 | let test (xs : seq) = 57 | let x = xs |> PQuery.ofSeq |> PQuery.map (fun x -> x * x) |> PQuery.sum |> PQuery.run 58 | let y = xs |> Seq.map (fun x -> x * x) |> Seq.sum 59 | (Double.IsNaN x && Double.IsNaN y) || Math.Ceiling(x) = Math.Ceiling(y) 60 | Check.QuickThrowOnFailure (TestInput.RunTest test) 61 | 62 | [] 63 | member __.``sum int64`` () = 64 | let test (xs : seq) = 65 | let x = xs |> PQuery.ofSeq |> PQuery.map (fun x -> x * x) |> PQuery.sum |> PQuery.run 66 | let y = xs |> Seq.map (fun x -> x * x) |> Seq.sum 67 | x = y 68 | Check.QuickThrowOnFailure (TestInput.RunTest test) 69 | 70 | [] 71 | member __.``collect`` () = 72 | let test (xs : seq<'T>) = 73 | let x = xs |> PQuery.ofSeq 74 | |> PQuery.collect (fun n -> Seq.map (fun n' -> n' * n) xs) 75 | |> PQuery.run 76 | let y = xs |> Seq.collect (fun n -> Seq.map (fun n' -> n' * n) xs ) 77 | Seq.sum x = Seq.sum y 78 | Check.QuickThrowOnFailure (TestInput.RunTest test) 79 | Check.QuickThrowOnFailure (TestInput.RunTest test) 80 | 81 | [] 82 | member __.``collect (nested pipe)`` () = 83 | let test (xs : seq<'T>) = 84 | let x = xs |> PQuery.ofSeq 85 | |> PQuery.collect (fun n -> xs |> Seq.map (fun n' -> n' * n) ) 86 | |> PQuery.run 87 | let y = xs |> Seq.collect (fun n -> Seq.map (fun n' -> n' * n) xs ) 88 | Seq.sum x = Seq.sum y 89 | Check.QuickThrowOnFailure (TestInput.RunTest test) 90 | Check.QuickThrowOnFailure (TestInput.RunTest test) 91 | 92 | [] 93 | member __.``collect (nested groupBy)`` () = 94 | let test (xs : seq) = 95 | let x = xs |> PQuery.ofSeq 96 | |> PQuery.collect (fun n -> xs |> Seq.groupBy (fun x -> x)) 97 | |> PQuery.map (fun (a,x) -> x |> Seq.sum) 98 | |> PQuery.run 99 | let y = xs |> Seq.collect (fun n -> xs |> Seq.groupBy (fun x -> x)) 100 | |> Seq.map (fun (a,x) -> x |> Seq.sum) 101 | Seq.sum x = Seq.sum y 102 | Check.QuickThrowOnFailure (TestInput.RunTest test) 103 | 104 | [] 105 | member __.``groupBy`` () = 106 | let test (xs : seq) = 107 | let x = xs |> PQuery.ofSeq 108 | |> PQuery.groupBy (fun x -> string x) 109 | |> PQuery.map (fun (a,x) -> Seq.sum x) 110 | |> PQuery.run 111 | let y = xs |> Seq.groupBy (fun x -> string x) 112 | |> Seq.map (fun (a,x) -> Seq.sum x) 113 | 114 | Seq.sum x = Seq.sum y 115 | Check.QuickThrowOnFailure (TestInput.RunTest test) 116 | 117 | [] 118 | member __.``sort`` () = 119 | let test (xs : seq) = 120 | let x = xs |> PQuery.ofSeq 121 | |> PQuery.sort 122 | |> PQuery.run 123 | let y = xs |> Seq.sort 124 | equal x y 125 | Check.QuickThrowOnFailure (TestInput.RunTest test) 126 | 127 | [] 128 | member __.``sortBy`` () = 129 | let test (xs : seq) = 130 | let x = xs |> PQuery.ofSeq 131 | |> PQuery.sortBy (fun x -> -x) 132 | |> PQuery.run 133 | let y = xs |> Seq.sortBy (fun x -> -x) 134 | equal x y 135 | Check.QuickThrowOnFailure (TestInput.RunTest test) 136 | 137 | [] 138 | member __.``toArray`` () = 139 | let test (xs : seq) = 140 | let x = xs |> PQuery.ofSeq 141 | |> PQuery.toArray 142 | |> PQuery.run 143 | let y = xs |> Seq.toArray 144 | equal (Seq.sort x) (Seq.sort y) 145 | Check.QuickThrowOnFailure (TestInput.RunTest test) 146 | 147 | [] 148 | member __.``precompile function``() = 149 | let test (xs : seq) = 150 | let t = PQuery.compile,int>(PrecompileHelpers.``fun x -> PQuery.length (PQuery.ofSeq x)``) 151 | let x = t(xs) 152 | let y = xs |> Seq.length 153 | x = y 154 | Check.QuickThrowOnFailure (TestInput.RunTest test) -------------------------------------------------------------------------------- /tests/LinqOptimizer.Tests.FSharp/PrecompileHelpers.fs: -------------------------------------------------------------------------------- 1 | namespace Nessos.LinqOptimizer.Tests 2 | 3 | module PrecompileHelpers = 4 | 5 | open System 6 | open System.Collections 7 | open System.Collections.Generic 8 | open System.Linq 9 | open System.Linq.Expressions 10 | open Nessos.LinqOptimizer.Base 11 | open Nessos.LinqOptimizer.Core 12 | open Nessos.LinqOptimizer.FSharp 13 | open Microsoft.FSharp.Linq 14 | open Microsoft.FSharp.Linq.RuntimeHelpers 15 | open Microsoft.FSharp.Quotations 16 | 17 | let private queryType = typeof 18 | let private ofSeq = queryType.GetMethod("ofSeq") 19 | let private length = queryType.GetMethod("length") 20 | let private iter = queryType.GetMethod("iter") 21 | 22 | let private pQueryType = typeof 23 | let private pOfSeq = pQueryType.GetMethod("ofSeq") 24 | let private pLength = pQueryType.GetMethod("length") 25 | 26 | let ``fun x -> Query.length(Query.ofSeq x))`` = 27 | let x = Expression.Parameter(typeof>, "x") 28 | let ofSeq = Expression.Call(ofSeq.MakeGenericMethod [| typeof |], x) 29 | let length = Expression.Call(length.MakeGenericMethod [| typeof |], ofSeq) 30 | Expression.Lambda, Nessos.LinqOptimizer.Base.IQueryExpr>>(length, x) 31 | 32 | 33 | let ``fun x -> PQuery.length (PQuery.ofSeq x)`` = 34 | let x = Expression.Parameter(typeof>, "x") 35 | let ofSeq = Expression.Call(pOfSeq.MakeGenericMethod [| typeof |], x) 36 | let length = Expression.Call(pLength.MakeGenericMethod [| typeof |], ofSeq) 37 | Expression.Lambda, Nessos.LinqOptimizer.Base.IParallelQueryExpr>>(length, x) 38 | 39 | // let ``fun x -> Query.iter (fun m -> a.Add(m)) (Query.ofSeq x)`` = 40 | // let a = Var("a", typeof>) 41 | // let m = Var("m", typeof) 42 | // let x = Var("x", typeof>) 43 | // let add = typeof>.GetMethod("Add") //.MakeGenericMethod [| typeof |] 44 | // let ofSeq = Expr.Call(ofSeq.MakeGenericMethod [| typeof |], [Expr.Var x]) 45 | // let action = 46 | // let action = Expr.Lambda(m, Expr.Call(Expr.Var a, add, [Expr.Var m])) 47 | // <@ LeafExpressionConverter.QuotationToLambdaExpression %%action @> 48 | // 49 | // let iter = iter.MakeGenericMethod [| typeof |] 50 | // let iter1 = Expr.Call(iter, [action]) 51 | // 52 | // () --------------------------------------------------------------------------------