├── .config └── dotnet-tools.json ├── .github └── workflows │ └── dotnetcore.yml ├── .gitignore ├── .paket └── Paket.Restore.targets ├── FSharp.HashCollections.sln ├── LICENSE ├── README.md ├── benchmarks ├── FSharp.HashCollections.Benchmarks.AddBenchmark-report-github.md ├── FSharp.HashCollections.Benchmarks.AddBenchmark-report.csv ├── FSharp.HashCollections.Benchmarks.AddBenchmark-report.html ├── FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report-github.md ├── FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report.csv ├── FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report.html ├── FSharp.HashCollections.Benchmarks.ReadBenchmarks-report-github.md ├── FSharp.HashCollections.Benchmarks.ReadBenchmarks-report.csv └── FSharp.HashCollections.Benchmarks.ReadBenchmarks-report.html ├── global.json ├── paket.dependencies ├── paket.lock ├── src └── FSharp.HashCollections │ ├── CompressedArray.fs │ ├── FSharp.HashCollections.fsproj │ ├── HamtImpl.fs │ ├── HashMap.fs │ ├── HashSet.fs │ ├── InternalTypes.fs │ └── paket.references └── test ├── FSharp.HashCollections.Benchmarks ├── AddBenchmark.fs ├── BenchmarkProgram.fs ├── FSharp.HashCollections.Benchmarks.fsproj ├── OfSeqBenchmark.fs ├── ReadBenchmark.fs └── paket.references ├── FSharp.HashCollections.Unit ├── FSharp.HashCollections.Unit.fsproj ├── HashMapTest.fs ├── HashSetTest.fs ├── Program.fs └── paket.references └── PlatformComparison ├── FSharpTest ├── FSharpTest.fsproj ├── Program.fs └── paket.references ├── ScalaTest ├── Program.scala └── run.sh └── runAll.sh /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "7.2.0", 7 | "commands": [ 8 | "paket" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Setup .NET Core 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: 6.0.403 16 | - name: Build Nuget package 17 | run: dotnet tool restore && dotnet pack ./src/FSharp.HashCollections --configuration Release -o ./output 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # Ionide (cross platform F# VS Code tools) working folder 362 | .ionide/ 363 | 364 | # Fody - auto-generated XML schema 365 | FodyWeavers.xsd 366 | 367 | ## 368 | ## Visual studio for Mac 369 | ## 370 | 371 | 372 | # globs 373 | Makefile.in 374 | *.userprefs 375 | *.usertasks 376 | config.make 377 | config.status 378 | aclocal.m4 379 | install-sh 380 | autom4te.cache/ 381 | *.tar.gz 382 | tarballs/ 383 | test-results/ 384 | 385 | # Mac bundle stuff 386 | *.dmg 387 | *.app 388 | 389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 390 | # General 391 | .DS_Store 392 | .AppleDouble 393 | .LSOverride 394 | 395 | # Icon must end with two \r 396 | Icon 397 | 398 | 399 | # Thumbnails 400 | ._* 401 | 402 | # Files that might appear in the root of a volume 403 | .DocumentRevisions-V100 404 | .fseventsd 405 | .Spotlight-V100 406 | .TemporaryItems 407 | .Trashes 408 | .VolumeIcon.icns 409 | .com.apple.timemachine.donotpresent 410 | 411 | # Directories potentially created on remote AFP share 412 | .AppleDB 413 | .AppleDesktop 414 | Network Trash Folder 415 | Temporary Items 416 | .apdisk 417 | 418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 419 | # Windows thumbnail cache files 420 | Thumbs.db 421 | ehthumbs.db 422 | ehthumbs_vista.db 423 | 424 | # Dump file 425 | *.stackdump 426 | 427 | # Folder config file 428 | [Dd]esktop.ini 429 | 430 | # Recycle Bin used on file shares 431 | $RECYCLE.BIN/ 432 | 433 | # Windows Installer files 434 | *.cab 435 | *.msi 436 | *.msix 437 | *.msm 438 | *.msp 439 | 440 | # Windows shortcuts 441 | *.lnk 442 | 443 | # JetBrains Rider 444 | .idea/ 445 | *.sln.iml 446 | 447 | ## 448 | ## Visual Studio Code 449 | ## 450 | .vscode/* 451 | !.vscode/settings.json 452 | !.vscode/tasks.json 453 | !.vscode/launch.json 454 | !.vscode/extensions.json 455 | -------------------------------------------------------------------------------- /FSharp.HashCollections.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{38C559CF-7B80-49F2-82D1-CD72AE22D3C8}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{583DE1CB-1CFA-4EEE-8FB7-D4A9A7ED0055}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.HashCollections", "src\FSharp.HashCollections\FSharp.HashCollections.fsproj", "{B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CFAEBB99-A9FC-4FE4-8451-E78FE86A742E}" 16 | EndProject 17 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "HashMap.Benchmarks", "test\FSharp.HashCollections.Benchmarks\FSharp.HashCollections.Benchmarks.fsproj", "{ED18B561-D268-4FF4-93EE-A6CAD874D310}" 18 | EndProject 19 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.HashCollections.Unit", "test\FSharp.HashCollections.Unit\FSharp.HashCollections.Unit.fsproj", "{BD1D802A-5C2C-4788-B03C-89C94291BF28}" 20 | EndProject 21 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpTest", "test\PlatformComparison\FSharpTest\FSharpTest.fsproj", "{4AE298CA-2AD1-4458-A842-551D90DCAFF0}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Debug|x64 = Debug|x64 27 | Debug|x86 = Debug|x86 28 | Release|Any CPU = Release|Any CPU 29 | Release|x64 = Release|x64 30 | Release|x86 = Release|x86 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Debug|x64.Build.0 = Debug|Any CPU 40 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Debug|x86.Build.0 = Debug|Any CPU 42 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Release|x64.ActiveCfg = Release|Any CPU 45 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Release|x64.Build.0 = Release|Any CPU 46 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Release|x86.ActiveCfg = Release|Any CPU 47 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD}.Release|x86.Build.0 = Release|Any CPU 48 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Debug|x64.Build.0 = Debug|Any CPU 52 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Debug|x86.Build.0 = Debug|Any CPU 54 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Release|x64.ActiveCfg = Release|Any CPU 57 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Release|x64.Build.0 = Release|Any CPU 58 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Release|x86.ActiveCfg = Release|Any CPU 59 | {ED18B561-D268-4FF4-93EE-A6CAD874D310}.Release|x86.Build.0 = Release|Any CPU 60 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Debug|x64.ActiveCfg = Debug|Any CPU 63 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Debug|x64.Build.0 = Debug|Any CPU 64 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Debug|x86.Build.0 = Debug|Any CPU 66 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Release|x64.ActiveCfg = Release|Any CPU 69 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Release|x64.Build.0 = Release|Any CPU 70 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Release|x86.ActiveCfg = Release|Any CPU 71 | {BD1D802A-5C2C-4788-B03C-89C94291BF28}.Release|x86.Build.0 = Release|Any CPU 72 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Debug|x64.ActiveCfg = Debug|Any CPU 75 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Debug|x64.Build.0 = Debug|Any CPU 76 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Debug|x86.ActiveCfg = Debug|Any CPU 77 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Debug|x86.Build.0 = Debug|Any CPU 78 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Release|x64.ActiveCfg = Release|Any CPU 81 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Release|x64.Build.0 = Release|Any CPU 82 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Release|x86.ActiveCfg = Release|Any CPU 83 | {4AE298CA-2AD1-4458-A842-551D90DCAFF0}.Release|x86.Build.0 = Release|Any CPU 84 | EndGlobalSection 85 | GlobalSection(NestedProjects) = preSolution 86 | {B8CD14E2-5DC9-4A58-8271-E21F8DB6DCAD} = {583DE1CB-1CFA-4EEE-8FB7-D4A9A7ED0055} 87 | {ED18B561-D268-4FF4-93EE-A6CAD874D310} = {CFAEBB99-A9FC-4FE4-8451-E78FE86A742E} 88 | {BD1D802A-5C2C-4788-B03C-89C94291BF28} = {CFAEBB99-A9FC-4FE4-8451-E78FE86A742E} 89 | EndGlobalSection 90 | EndGlobal 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mark Karatovic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FSharp.HashCollections 2 | 3 | Persistent hash based map implementation 4 | 5 | Made for my own purposes the goal is to allow faster lookup table performance in F#. After testing some the built in F# Map and other collection implementations across the .NET ecosystem and not being totally satisfied with either the performance and/or the API compromises exposed I decided to code my own for both fun and profit. This is the end result implemented as a customised/optimised HAMT (Hash Mapped Array Trie) for the .NET runtime. 6 | 7 | [![NuGet Badge](http://img.shields.io/nuget/v/FSharp.HashCollections.svg?style=flat)](https://www.nuget.org/packages/FSharp.HashCollections) 8 | 9 | ## Goals 10 | 11 | 1) More efficient persistent collection type where F#'s Map type isn't fast enough. 12 | - At time of writing this was the fastest immutable collections library in the .NET ecosystem I could find (including BCL classes). See [Performance Benchmarks](#performance-benchmarks) for details. 13 | - Tailor the algorithms used to the .NET ecosystem to achieve better performance. 14 | 2) Allow a range of key types and equality logic to be used even if provided by consumers without sacrificing performance (e.g. no need for keys to be comparable). 15 | - Custom equality comparers can be provided, unlike the standard F# Map/Set data types. 16 | 3) Provide an idiomatic API to F#. The library ideally should allow C# usage/interop if required. 17 | - HashMap and HashSet static classes are usable from C# as wrappers. Performance optimisations (e.g. inlining) are applied at compile time where possible. 18 | 4) Maintainable to an average F# developer. 19 | - For example minimising inheritance/object hierarchies and casting (unlike some other impl's I've seen), performant whilst still idiomatic code, etc. 20 | - Use F# strengths to increase performance further (e.g. inlining + DU's to avoid method calls and copying overhead affecting struct key performance). 21 | 5) Performance at least at the same scale as other languages of the same class/abstraction level (e.g JVM, etc). 22 | 23 | **TLDR; Benefits of immutable collections while minimising the cost (e.g. performance, maintainable code, idiomatic code, etc).** 24 | 25 | ## Use Cases 26 | 27 | - Large collections at acceptable performance (e.g. 500,000+ elements). 28 | - Immutability of large/deep object graphs without the associated performance cost of changing data deep in the hierarchy. 29 | - A common pattern when changing data deep in nested records. Instead of using the record copy syntax to change these flatten out of object and use HashMaps instead joining by key. Often useful to store a large hierarchy of state and update it in an atomic fashion. 30 | - Where the key type of the Map would otherwise not work with standard F# collections since it does not implement IComparable. 31 | 32 | ## Collection Types Provided 33 | 34 | All collections are persisted/immutable by nature so any Add/Remove operation produces a new collection instance. Most methods mirror the F# built in Map and Set module (e.g. Map.tryFind vs HashMap.tryFind) allowing in many cases this library to be used as a drop in replacement where appropriate. 35 | 36 | - HashMap (Similar to F#'s Map in behaviour). 37 | 38 | | Operation | Complexity | 39 | | --- | --- | 40 | | TryFind | O(log32n) | 41 | | Add | O(log32n) | 42 | | Remove | O(log32n) | 43 | | Count | O(1) | 44 | | Equals | O(n) | 45 | 46 | Example Usage: 47 | ```fs 48 | open FSharp.HashCollections 49 | let hashMapResult = HashMap.empty |> HashMap.add k v |> HashMay.tryFind k // Result is ValueSome(v) 50 | ``` 51 | 52 | - HashSet (Similar to F#'s Set in behaviour). 53 | 54 | | Operation | Complexity | 55 | | --- | --- | 56 | | Contains | O(log32n) | 57 | | Add | O(log32n) | 58 | | Remove | O(log32n) | 59 | | Count | O(1) | 60 | | Equals | O(n) | 61 | 62 | Example Usage: 63 | ```fs 64 | open FSharp.HashCollections 65 | let hashMapResult = HashSet.empty |> HashSet.add k |> HashSet.contains k // Result is true 66 | ``` 67 | 68 | ## Equality customisation 69 | 70 | By default any "empty" seeded HashSet/Map uses F# HashIdentity.Structural comparison to determine equality and calculate hashcodes. This is in line with the F# collection types. 71 | 72 | In addition all collection types allow custom equality to be assigned if required. The equality is encoded as a type on the collection so equality and hashing operations are consistent. Same collection types with different equality comparers can not be used interchangeably by operations provided. To use: 73 | 74 | ```fs 75 | // Uses the default equality template provided. 76 | let defaultIntHashMap : HashMap = HashMap.empty 77 | 78 | // Uses a custom equality template provided by the type parameter. 79 | let customEqIntHashMap : HashMap = HashMap.emptyWithComparer 80 | ``` 81 | 82 | Any equality comparer specified in the type signature must: 83 | 84 | - Have a parameterless public constructor. 85 | - Implement IEqualityComparer<> for either the contents (HashSet) or the key (HashMap). 86 | - (Optional): Be a struct type. This is recommended for performance as it produces more optimised equality checking code at runtime. 87 | 88 | An example with the type "Int" for the custom equality comparer (which in testing exhibits slightly faster perf than the default): 89 | 90 | ```fs 91 | type IntEqualityTemplate = 92 | struct end 93 | interface System.Collections.Generic.IEqualityComparer with 94 | member __.Equals(x: int, y: int): bool = x = y 95 | member __.GetHashCode(obj: int): int = hash obj // Or just obj 96 | 97 | module Usage = 98 | // Type is HashMap 99 | let empty = HashMap.emptyWithComparer<_, int64, IntEqualityTemplate> 100 | ``` 101 | 102 | ## Performance Benchmarks 103 | 104 | ### TryFind on HashMap 105 | 106 | Keys are of type int32 where "GetHashMap" represents this library's HashMap collection. 107 | 108 | All are using F# HashIdentity.Structural comparison. 109 | 110 | See [ReadBenchmark](benchmarks/FSharp.HashCollections.Benchmarks.ReadBenchmarks-report-github.md) 111 | 112 | ``` ini 113 | 114 | BenchmarkDotNet=v0.13.1, OS=arch 115 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores 116 | .NET SDK=6.0.403 117 | [Host] : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT DEBUG 118 | DefaultJob : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT 119 | 120 | 121 | ``` 122 | | Method | CollectionSize | Mean | Error | StdDev | 123 | |--------------------------------- |--------------- |------------:|----------:|----------:| 124 | | **GetHashMap** | **10** | **11.49 ns** | **0.091 ns** | **0.081 ns** | 125 | | GetImToolsHashMap | 10 | 31.58 ns | 0.190 ns | 0.169 ns | 126 | | GetFSharpMap | 10 | 26.97 ns | 0.208 ns | 0.194 ns | 127 | | GetFSharpXHashMap | 10 | 129.21 ns | 0.806 ns | 0.754 ns | 128 | | GetSystemCollectionsImmutableMap | 10 | 21.80 ns | 0.325 ns | 0.304 ns | 129 | | GetFSharpDataAdaptiveMap | 10 | 22.95 ns | 0.234 ns | 0.219 ns | 130 | | GetFSharpxChampMap | 10 | 72.75 ns | 0.439 ns | 0.367 ns | 131 | | GetLangExtMap | 10 | 24.83 ns | 0.218 ns | 0.193 ns | 132 | | **GetHashMap** | **100** | **11.75 ns** | **0.120 ns** | **0.112 ns** | 133 | | GetImToolsHashMap | 100 | 44.99 ns | 0.358 ns | 0.317 ns | 134 | | GetFSharpMap | 100 | 47.45 ns | 0.418 ns | 0.391 ns | 135 | | GetFSharpXHashMap | 100 | 148.55 ns | 0.919 ns | 0.815 ns | 136 | | GetSystemCollectionsImmutableMap | 100 | 33.26 ns | 0.246 ns | 0.230 ns | 137 | | GetFSharpDataAdaptiveMap | 100 | 44.84 ns | 0.400 ns | 0.374 ns | 138 | | GetFSharpxChampMap | 100 | 83.54 ns | 1.017 ns | 0.951 ns | 139 | | GetLangExtMap | 100 | 31.26 ns | 0.264 ns | 0.247 ns | 140 | | **GetHashMap** | **1000** | **11.67 ns** | **0.090 ns** | **0.084 ns** | 141 | | GetImToolsHashMap | 1000 | 69.63 ns | 0.974 ns | 0.911 ns | 142 | | GetFSharpMap | 1000 | 71.16 ns | 0.762 ns | 0.713 ns | 143 | | GetFSharpXHashMap | 1000 | 161.89 ns | 0.539 ns | 0.450 ns | 144 | | GetSystemCollectionsImmutableMap | 1000 | 49.93 ns | 0.489 ns | 0.458 ns | 145 | | GetFSharpDataAdaptiveMap | 1000 | 67.05 ns | 0.725 ns | 0.678 ns | 146 | | GetFSharpxChampMap | 1000 | 84.44 ns | 1.061 ns | 0.992 ns | 147 | | GetLangExtMap | 1000 | 31.00 ns | 0.312 ns | 0.292 ns | 148 | | **GetHashMap** | **100000** | **33.47 ns** | **0.376 ns** | **0.314 ns** | 149 | | GetImToolsHashMap | 100000 | 216.79 ns | 4.285 ns | 4.009 ns | 150 | | GetFSharpMap | 100000 | 177.11 ns | 2.074 ns | 1.940 ns | 151 | | GetFSharpXHashMap | 100000 | 231.84 ns | 4.178 ns | 6.627 ns | 152 | | GetSystemCollectionsImmutableMap | 100000 | 162.46 ns | 2.537 ns | 2.374 ns | 153 | | GetFSharpDataAdaptiveMap | 100000 | 154.96 ns | 2.743 ns | 2.566 ns | 154 | | GetFSharpxChampMap | 100000 | 114.73 ns | 1.243 ns | 1.163 ns | 155 | | GetLangExtMap | 100000 | 61.52 ns | 0.609 ns | 0.570 ns | 156 | | **GetHashMap** | **500000** | **35.67 ns** | **0.622 ns** | **0.582 ns** | 157 | | GetImToolsHashMap | 500000 | 528.33 ns | 10.475 ns | 12.063 ns | 158 | | GetFSharpMap | 500000 | 327.39 ns | 6.410 ns | 11.881 ns | 159 | | GetFSharpXHashMap | 500000 | 336.89 ns | 5.012 ns | 4.688 ns | 160 | | GetSystemCollectionsImmutableMap | 500000 | 359.41 ns | 7.174 ns | 11.787 ns | 161 | | GetFSharpDataAdaptiveMap | 500000 | 324.83 ns | 6.091 ns | 5.982 ns | 162 | | GetFSharpxChampMap | 500000 | 122.62 ns | 1.997 ns | 2.137 ns | 163 | | GetLangExtMap | 500000 | 67.00 ns | 1.128 ns | 1.055 ns | 164 | | **GetHashMap** | **750000** | **38.42 ns** | **0.513 ns** | **0.428 ns** | 165 | | GetImToolsHashMap | 750000 | 639.63 ns | 12.341 ns | 14.691 ns | 166 | | GetFSharpMap | 750000 | 429.89 ns | 8.344 ns | 10.247 ns | 167 | | GetFSharpXHashMap | 750000 | 446.19 ns | 5.108 ns | 4.778 ns | 168 | | GetSystemCollectionsImmutableMap | 750000 | 440.76 ns | 8.714 ns | 10.035 ns | 169 | | GetFSharpDataAdaptiveMap | 750000 | 364.25 ns | 7.225 ns | 6.758 ns | 170 | | GetFSharpxChampMap | 750000 | 128.73 ns | 2.571 ns | 3.848 ns | 171 | | GetLangExtMap | 750000 | 72.10 ns | 1.426 ns | 1.334 ns | 172 | | **GetHashMap** | **1000000** | **41.58 ns** | **0.804 ns** | **0.752 ns** | 173 | | GetImToolsHashMap | 1000000 | 716.12 ns | 13.576 ns | 12.699 ns | 174 | | GetFSharpMap | 1000000 | 478.64 ns | 9.391 ns | 11.877 ns | 175 | | GetFSharpXHashMap | 1000000 | 426.63 ns | 4.339 ns | 4.059 ns | 176 | | GetSystemCollectionsImmutableMap | 1000000 | 506.48 ns | 9.872 ns | 9.695 ns | 177 | | GetFSharpDataAdaptiveMap | 1000000 | 395.59 ns | 5.966 ns | 5.581 ns | 178 | | GetFSharpxChampMap | 1000000 | 132.05 ns | 2.585 ns | 4.319 ns | 179 | | GetLangExtMap | 1000000 | 79.30 ns | 1.577 ns | 2.408 ns | 180 | | **GetHashMap** | **5000000** | **155.94 ns** | **0.957 ns** | **0.799 ns** | 181 | | GetImToolsHashMap | 5000000 | 1,138.25 ns | 18.869 ns | 17.650 ns | 182 | | GetFSharpMap | 5000000 | 823.41 ns | 10.984 ns | 10.274 ns | 183 | | GetFSharpXHashMap | 5000000 | 475.15 ns | 6.462 ns | 6.044 ns | 184 | | GetSystemCollectionsImmutableMap | 5000000 | 826.09 ns | 15.213 ns | 14.230 ns | 185 | | GetFSharpDataAdaptiveMap | 5000000 | 579.94 ns | 6.645 ns | 6.216 ns | 186 | | GetFSharpxChampMap | 5000000 | 285.59 ns | 3.235 ns | 3.026 ns | 187 | | GetLangExtMap | 5000000 | 234.92 ns | 3.571 ns | 3.341 ns | 188 | | **GetHashMap** | **10000000** | **159.87 ns** | **2.199 ns** | **1.950 ns** | 189 | | GetImToolsHashMap | 10000000 | 1,345.84 ns | 26.844 ns | 26.364 ns | 190 | | GetFSharpMap | 10000000 | 957.82 ns | 14.825 ns | 13.868 ns | 191 | | GetFSharpXHashMap | 10000000 | 509.87 ns | 9.244 ns | 8.647 ns | 192 | | GetSystemCollectionsImmutableMap | 10000000 | 960.39 ns | 18.921 ns | 17.699 ns | 193 | | GetFSharpDataAdaptiveMap | 10000000 | 672.11 ns | 9.232 ns | 8.636 ns | 194 | | GetFSharpxChampMap | 10000000 | 291.37 ns | 3.615 ns | 3.381 ns | 195 | | GetLangExtMap | 10000000 | 237.09 ns | 3.173 ns | 2.968 ns | 196 | 197 | ### Add on HashMap 198 | 199 | Scenario: Adding 50 elements on top of a pre-defined collection with a collection size as specified, average time of each of the 50 inserts: 200 | 201 | See [AddBenchmark](benchmarks/FSharp.HashCollections.Benchmarks.AddBenchmark-report-github.md) 202 | 203 | ### OfSeq on HashMap 204 | 205 | See [OfSeq Benchmark](benchmarks/FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report-github.md) 206 | 207 | 208 | ## An outside .NET ecosystem comparison. 209 | 210 | This section is simply a guide to give a ballpark comparison figure on performance with implementations from other languages that have standard HAMT implementations for my own technical selection evaluation. 211 | 212 | **TL;DR**: The "Get" method where performance is significantly better as the collection scales up. For example at 10,000,000 FSharp collection is approx 3.59 faster, and 1.73x faster at building the initial hashmap. 213 | 214 | - TryFind: Measures fetching every key inside a collection of a given size (total milliseconds). 215 | - OfSeq: Measures building a HashMap of the given collection size (total milliseconds). 216 | 217 | | Lang | Operation | 100 | 1000 | 10000 | 100000 | 500000 | 1000000 | 5000000 | 10000000 | 50000000 | 218 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 219 | | F# | TryFind | 0 | 0 | 0 | 5 | 37 | 94 | 495 | 1142 | 9346 | 220 | | Scala | TryFind | 0 | 0 | 3 | 17 | 111 | 256 | 1863 | 4111 | 32318 | 221 | | F# | OfSeq | 0 | 0 | 3 | 30 | 146 | 219 | 1321 | 3080 | 19160 | 222 | | Scala | OfSeq | 0 | 0 | 5 | 49 | 163 | 387 | 2516 | 5347 | 43827 | 223 | 224 | Platforms tested: Dotnet version: 5.0.301, Scala code runner version 2.13.6-20210529-211702. 225 | 226 | Note that most of the optimisation work I have done is around the "Get" method given my use case (lots of reads, fewer but still significant write load with large collections 500,000+ items). 227 | 228 | ## Design decisions that may affect consumers of this library 229 | 230 | Any of these decisions may change in the future as I gain knowledge, change my mind, etc. It doesn't list all the tweaks, and changes caused by benchmarking just the things that affect consumers. Many besides equality checking shouldn't affect the API dramatically; and if they do it should remain easy to port code to the new API as appropriate. 231 | 232 | 1) Writing in F# vs C# 233 | - Performance tweaks found in my trial and error experiments (structs, inlining, etc) are easier to unlock and use in F# requiring less code. Inlining is used for algorithm sharing across collection types, avoiding struct copying with returned results, etc. 234 | 235 | 2) Count is a O(1) operation. This requires an extra bit of space per tree and minor overhead during insert and removal but allows other operations on the read side to be faster (e.g isEmpty, count, intersect, etc.). 236 | - ✅ Lower time complexity for existence and count operations. 237 | - ❌ Slightly more work required when inserting and removing elements keeping track of addition or removal success. 238 | 239 | 3) NetCoreApp3.1 only or greater. This allows the use of .NET intrinsics and other performance enhancements. 240 | - ✅ Faster implementation. 241 | - ❌ Only Net Core 3.1 compatible or greater. 242 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.AddBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` ini 2 | 3 | BenchmarkDotNet=v0.13.1, OS=arch 4 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores 5 | .NET SDK=5.0.301 6 | [Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT DEBUG 7 | DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT 8 | 9 | 10 | ``` 11 | | Method | CollectionSize | Mean | Error | StdDev | 12 | |----------------------------------- |--------------- |-----------:|---------:|---------:| 13 | | **AddHashMap** | **1000** | **200.2 ns** | **1.87 ns** | **1.75 ns** | 14 | | AddToFSharpMap | 1000 | 510.8 ns | 7.52 ns | 7.04 ns | 15 | | AddToFSharpAdaptiveMap | 1000 | 212.6 ns | 1.08 ns | 1.01 ns | 16 | | AddToFSharpXMap | 1000 | 489.5 ns | 3.63 ns | 3.40 ns | 17 | | AddToSystemCollectionsImmutableMap | 1000 | 596.4 ns | 6.52 ns | 6.10 ns | 18 | | AddToFsharpxChampMap | 1000 | 261.0 ns | 3.03 ns | 2.83 ns | 19 | | **AddHashMap** | **100000** | **308.4 ns** | **2.89 ns** | **2.56 ns** | 20 | | AddToFSharpMap | 100000 | 991.1 ns | 10.59 ns | 9.91 ns | 21 | | AddToFSharpAdaptiveMap | 100000 | 338.9 ns | 4.93 ns | 4.61 ns | 22 | | AddToFSharpXMap | 100000 | 677.0 ns | 6.28 ns | 5.88 ns | 23 | | AddToSystemCollectionsImmutableMap | 100000 | 871.3 ns | 7.18 ns | 6.72 ns | 24 | | AddToFsharpxChampMap | 100000 | 280.6 ns | 2.47 ns | 2.31 ns | 25 | | **AddHashMap** | **500000** | **379.1 ns** | **3.99 ns** | **3.73 ns** | 26 | | AddToFSharpMap | 500000 | 950.5 ns | 13.09 ns | 12.24 ns | 27 | | AddToFSharpAdaptiveMap | 500000 | 384.4 ns | 4.19 ns | 3.92 ns | 28 | | AddToFSharpXMap | 500000 | 814.9 ns | 10.85 ns | 10.15 ns | 29 | | AddToSystemCollectionsImmutableMap | 500000 | 1,097.2 ns | 9.44 ns | 8.83 ns | 30 | | AddToFsharpxChampMap | 500000 | 237.7 ns | 2.19 ns | 2.05 ns | 31 | | **AddHashMap** | **750000** | **381.0 ns** | **3.16 ns** | **2.96 ns** | 32 | | AddToFSharpMap | 750000 | 990.5 ns | 7.37 ns | 6.54 ns | 33 | | AddToFSharpAdaptiveMap | 750000 | 383.0 ns | 2.92 ns | 2.59 ns | 34 | | AddToFSharpXMap | 750000 | 798.4 ns | 6.80 ns | 6.02 ns | 35 | | AddToSystemCollectionsImmutableMap | 750000 | 1,044.3 ns | 11.12 ns | 10.40 ns | 36 | | AddToFsharpxChampMap | 750000 | 302.1 ns | 2.67 ns | 2.50 ns | 37 | | **AddHashMap** | **1000000** | **382.3 ns** | **3.61 ns** | **3.37 ns** | 38 | | AddToFSharpMap | 1000000 | 1,022.0 ns | 12.34 ns | 11.54 ns | 39 | | AddToFSharpAdaptiveMap | 1000000 | 410.8 ns | 6.81 ns | 6.37 ns | 40 | | AddToFSharpXMap | 1000000 | 810.5 ns | 9.86 ns | 9.23 ns | 41 | | AddToSystemCollectionsImmutableMap | 1000000 | 1,140.3 ns | 8.67 ns | 8.11 ns | 42 | | AddToFsharpxChampMap | 1000000 | 298.6 ns | 2.54 ns | 2.37 ns | 43 | | **AddHashMap** | **5000000** | **402.9 ns** | **3.63 ns** | **3.39 ns** | 44 | | AddToFSharpMap | 5000000 | 1,195.5 ns | 15.27 ns | 13.54 ns | 45 | | AddToFSharpAdaptiveMap | 5000000 | 489.2 ns | 5.38 ns | 5.03 ns | 46 | | AddToFSharpXMap | 5000000 | 878.5 ns | 9.37 ns | 8.77 ns | 47 | | AddToSystemCollectionsImmutableMap | 5000000 | 1,247.1 ns | 13.09 ns | 12.24 ns | 48 | | AddToFsharpxChampMap | 5000000 | 286.8 ns | 2.55 ns | 2.39 ns | 49 | | **AddHashMap** | **10000000** | **429.6 ns** | **3.06 ns** | **2.87 ns** | 50 | | AddToFSharpMap | 10000000 | 1,269.8 ns | 19.55 ns | 17.33 ns | 51 | | AddToFSharpAdaptiveMap | 10000000 | 511.8 ns | 6.01 ns | 5.62 ns | 52 | | AddToFSharpXMap | 10000000 | 913.2 ns | 6.76 ns | 6.32 ns | 53 | | AddToSystemCollectionsImmutableMap | 10000000 | 1,392.1 ns | 15.02 ns | 14.05 ns | 54 | | AddToFsharpxChampMap | 10000000 | 229.3 ns | 2.28 ns | 2.13 ns | 55 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.AddBenchmark-report.csv: -------------------------------------------------------------------------------- 1 | Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,CollectionSize,Mean,Error,StdDev 2 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,200.2 ns,1.87 ns,1.75 ns 3 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,510.8 ns,7.52 ns,7.04 ns 4 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,212.6 ns,1.08 ns,1.01 ns 5 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,489.5 ns,3.63 ns,3.40 ns 6 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,596.4 ns,6.52 ns,6.10 ns 7 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,261.0 ns,3.03 ns,2.83 ns 8 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,308.4 ns,2.89 ns,2.56 ns 9 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,991.1 ns,10.59 ns,9.91 ns 10 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,338.9 ns,4.93 ns,4.61 ns 11 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,677.0 ns,6.28 ns,5.88 ns 12 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,871.3 ns,7.18 ns,6.72 ns 13 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,280.6 ns,2.47 ns,2.31 ns 14 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,379.1 ns,3.99 ns,3.73 ns 15 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,950.5 ns,13.09 ns,12.24 ns 16 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,384.4 ns,4.19 ns,3.92 ns 17 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,814.9 ns,10.85 ns,10.15 ns 18 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,"1,097.2 ns",9.44 ns,8.83 ns 19 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,237.7 ns,2.19 ns,2.05 ns 20 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,381.0 ns,3.16 ns,2.96 ns 21 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,990.5 ns,7.37 ns,6.54 ns 22 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,383.0 ns,2.92 ns,2.59 ns 23 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,798.4 ns,6.80 ns,6.02 ns 24 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,"1,044.3 ns",11.12 ns,10.40 ns 25 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,302.1 ns,2.67 ns,2.50 ns 26 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,382.3 ns,3.61 ns,3.37 ns 27 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"1,022.0 ns",12.34 ns,11.54 ns 28 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,410.8 ns,6.81 ns,6.37 ns 29 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,810.5 ns,9.86 ns,9.23 ns 30 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"1,140.3 ns",8.67 ns,8.11 ns 31 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,298.6 ns,2.54 ns,2.37 ns 32 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,402.9 ns,3.63 ns,3.39 ns 33 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"1,195.5 ns",15.27 ns,13.54 ns 34 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,489.2 ns,5.38 ns,5.03 ns 35 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,878.5 ns,9.37 ns,8.77 ns 36 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"1,247.1 ns",13.09 ns,12.24 ns 37 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,286.8 ns,2.55 ns,2.39 ns 38 | AddHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,429.6 ns,3.06 ns,2.87 ns 39 | AddToFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"1,269.8 ns",19.55 ns,17.33 ns 40 | AddToFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,511.8 ns,6.01 ns,5.62 ns 41 | AddToFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,913.2 ns,6.76 ns,6.32 ns 42 | AddToSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"1,392.1 ns",15.02 ns,14.05 ns 43 | AddToFsharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,229.3 ns,2.28 ns,2.13 ns 44 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.AddBenchmark-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FSharp.HashCollections.Benchmarks.AddBenchmark-20211026-094853 6 | 7 | 13 | 14 | 15 |

16 | BenchmarkDotNet=v0.13.1, OS=arch 
17 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
18 | .NET SDK=5.0.301
19 |   [Host]     : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT DEBUG
20 |   DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
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 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
MethodCollectionSizeMeanErrorStdDev
AddHashMap1000200.2 ns1.87 ns1.75 ns
AddToFSharpMap1000510.8 ns7.52 ns7.04 ns
AddToFSharpAdaptiveMap1000212.6 ns1.08 ns1.01 ns
AddToFSharpXMap1000489.5 ns3.63 ns3.40 ns
AddToSystemCollectionsImmutableMap1000596.4 ns6.52 ns6.10 ns
AddToFsharpxChampMap1000261.0 ns3.03 ns2.83 ns
AddHashMap100000308.4 ns2.89 ns2.56 ns
AddToFSharpMap100000991.1 ns10.59 ns9.91 ns
AddToFSharpAdaptiveMap100000338.9 ns4.93 ns4.61 ns
AddToFSharpXMap100000677.0 ns6.28 ns5.88 ns
AddToSystemCollectionsImmutableMap100000871.3 ns7.18 ns6.72 ns
AddToFsharpxChampMap100000280.6 ns2.47 ns2.31 ns
AddHashMap500000379.1 ns3.99 ns3.73 ns
AddToFSharpMap500000950.5 ns13.09 ns12.24 ns
AddToFSharpAdaptiveMap500000384.4 ns4.19 ns3.92 ns
AddToFSharpXMap500000814.9 ns10.85 ns10.15 ns
AddToSystemCollectionsImmutableMap5000001,097.2 ns9.44 ns8.83 ns
AddToFsharpxChampMap500000237.7 ns2.19 ns2.05 ns
AddHashMap750000381.0 ns3.16 ns2.96 ns
AddToFSharpMap750000990.5 ns7.37 ns6.54 ns
AddToFSharpAdaptiveMap750000383.0 ns2.92 ns2.59 ns
AddToFSharpXMap750000798.4 ns6.80 ns6.02 ns
AddToSystemCollectionsImmutableMap7500001,044.3 ns11.12 ns10.40 ns
AddToFsharpxChampMap750000302.1 ns2.67 ns2.50 ns
AddHashMap1000000382.3 ns3.61 ns3.37 ns
AddToFSharpMap10000001,022.0 ns12.34 ns11.54 ns
AddToFSharpAdaptiveMap1000000410.8 ns6.81 ns6.37 ns
AddToFSharpXMap1000000810.5 ns9.86 ns9.23 ns
AddToSystemCollectionsImmutableMap10000001,140.3 ns8.67 ns8.11 ns
AddToFsharpxChampMap1000000298.6 ns2.54 ns2.37 ns
AddHashMap5000000402.9 ns3.63 ns3.39 ns
AddToFSharpMap50000001,195.5 ns15.27 ns13.54 ns
AddToFSharpAdaptiveMap5000000489.2 ns5.38 ns5.03 ns
AddToFSharpXMap5000000878.5 ns9.37 ns8.77 ns
AddToSystemCollectionsImmutableMap50000001,247.1 ns13.09 ns12.24 ns
AddToFsharpxChampMap5000000286.8 ns2.55 ns2.39 ns
AddHashMap10000000429.6 ns3.06 ns2.87 ns
AddToFSharpMap100000001,269.8 ns19.55 ns17.33 ns
AddToFSharpAdaptiveMap10000000511.8 ns6.01 ns5.62 ns
AddToFSharpXMap10000000913.2 ns6.76 ns6.32 ns
AddToSystemCollectionsImmutableMap100000001,392.1 ns15.02 ns14.05 ns
AddToFsharpxChampMap10000000229.3 ns2.28 ns2.13 ns
70 | 71 | 72 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` ini 2 | 3 | BenchmarkDotNet=v0.13.1, OS=arch 4 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores 5 | .NET SDK=5.0.301 6 | [Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT DEBUG 7 | DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT 8 | 9 | 10 | ``` 11 | | Method | CollectionSize | Mean | Error | StdDev | 12 | |----------------------------------- |--------------- |----------------:|--------------:|--------------:| 13 | | **OfSeqHashMap** | **1000** | **110.17 μs** | **1.038 μs** | **0.971 μs** | 14 | | OfSeqFSharpXMap | 1000 | 330.68 μs | 1.873 μs | 1.752 μs | 15 | | OfSeqFSharpAdaptiveMap | 1000 | 89.84 μs | 0.679 μs | 0.636 μs | 16 | | OfSeqSystemCollectionsImmutableMap | 1000 | 256.79 μs | 4.808 μs | 4.722 μs | 17 | | OfSeqFSharpxChampMap | 1000 | 286.88 μs | 2.472 μs | 2.313 μs | 18 | | **OfSeqHashMap** | **100000** | **13,449.39 μs** | **151.712 μs** | **141.911 μs** | 19 | | OfSeqFSharpXMap | 100000 | 37,750.52 μs | 574.083 μs | 536.997 μs | 20 | | OfSeqFSharpAdaptiveMap | 100000 | 10,982.73 μs | 120.187 μs | 112.423 μs | 21 | | OfSeqSystemCollectionsImmutableMap | 100000 | 39,097.60 μs | 524.268 μs | 490.401 μs | 22 | | OfSeqFSharpxChampMap | 100000 | 37,379.44 μs | 361.439 μs | 338.090 μs | 23 | | **OfSeqHashMap** | **500000** | **96,185.59 μs** | **529.517 μs** | **442.170 μs** | 24 | | OfSeqFSharpXMap | 500000 | 291,985.04 μs | 4,210.226 μs | 3,938.248 μs | 25 | | OfSeqFSharpAdaptiveMap | 500000 | 60,188.35 μs | 729.702 μs | 682.564 μs | 26 | | OfSeqSystemCollectionsImmutableMap | 500000 | 216,527.30 μs | 2,839.435 μs | 2,656.009 μs | 27 | | OfSeqFSharpxChampMap | 500000 | 195,841.18 μs | 1,429.687 μs | 1,267.380 μs | 28 | | **OfSeqHashMap** | **750000** | **156,793.76 μs** | **1,340.004 μs** | **1,253.441 μs** | 29 | | OfSeqFSharpXMap | 750000 | 424,483.55 μs | 4,242.442 μs | 3,760.815 μs | 30 | | OfSeqFSharpAdaptiveMap | 750000 | 88,950.26 μs | 985.962 μs | 922.270 μs | 31 | | OfSeqSystemCollectionsImmutableMap | 750000 | 332,289.77 μs | 5,000.057 μs | 4,677.057 μs | 32 | | OfSeqFSharpxChampMap | 750000 | 331,999.09 μs | 1,807.221 μs | 1,509.112 μs | 33 | | **OfSeqHashMap** | **1000000** | **218,414.38 μs** | **1,928.154 μs** | **1,803.596 μs** | 34 | | OfSeqFSharpXMap | 1000000 | 560,848.87 μs | 4,607.935 μs | 4,084.814 μs | 35 | | OfSeqFSharpAdaptiveMap | 1000000 | 118,026.31 μs | 704.415 μs | 624.445 μs | 36 | | OfSeqSystemCollectionsImmutableMap | 1000000 | 446,268.12 μs | 6,774.194 μs | 6,336.586 μs | 37 | | OfSeqFSharpxChampMap | 1000000 | 483,023.41 μs | 5,186.810 μs | 4,851.745 μs | 38 | | **OfSeqHashMap** | **5000000** | **1,412,598.17 μs** | **21,065.035 μs** | **19,704.246 μs** | 39 | | OfSeqFSharpXMap | 5000000 | 3,259,079.75 μs | 21,505.015 μs | 19,063.636 μs | 40 | | OfSeqFSharpAdaptiveMap | 5000000 | 633,888.01 μs | 7,738.250 μs | 7,238.364 μs | 41 | | OfSeqSystemCollectionsImmutableMap | 5000000 | 2,471,130.55 μs | 22,772.691 μs | 21,301.589 μs | 42 | | OfSeqFSharpxChampMap | 5000000 | 2,926,849.53 μs | 25,341.168 μs | 23,704.144 μs | 43 | | **OfSeqHashMap** | **10000000** | **3,299,878.09 μs** | **18,616.826 μs** | **17,414.191 μs** | 44 | | OfSeqFSharpXMap | 10000000 | 8,251,430.10 μs | 53,425.476 μs | 47,360.293 μs | 45 | | OfSeqFSharpAdaptiveMap | 10000000 | 1,355,405.41 μs | 8,122.114 μs | 7,200.042 μs | 46 | | OfSeqSystemCollectionsImmutableMap | 10000000 | 5,228,771.15 μs | 52,710.431 μs | 49,305.370 μs | 47 | | OfSeqFSharpxChampMap | 10000000 | 6,699,184.76 μs | 39,367.783 μs | 34,898.515 μs | 48 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report.csv: -------------------------------------------------------------------------------- 1 | Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,CollectionSize,Mean,Error,StdDev 2 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,110.17 μs,1.038 μs,0.971 μs 3 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,330.68 μs,1.873 μs,1.752 μs 4 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,89.84 μs,0.679 μs,0.636 μs 5 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,256.79 μs,4.808 μs,4.722 μs 6 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,286.88 μs,2.472 μs,2.313 μs 7 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,"13,449.39 μs",151.712 μs,141.911 μs 8 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,"37,750.52 μs",574.083 μs,536.997 μs 9 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,"10,982.73 μs",120.187 μs,112.423 μs 10 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,"39,097.60 μs",524.268 μs,490.401 μs 11 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,"37,379.44 μs",361.439 μs,338.090 μs 12 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,"96,185.59 μs",529.517 μs,442.170 μs 13 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,"291,985.04 μs","4,210.226 μs","3,938.248 μs" 14 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,"60,188.35 μs",729.702 μs,682.564 μs 15 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,"216,527.30 μs","2,839.435 μs","2,656.009 μs" 16 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,"195,841.18 μs","1,429.687 μs","1,267.380 μs" 17 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,"156,793.76 μs","1,340.004 μs","1,253.441 μs" 18 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,"424,483.55 μs","4,242.442 μs","3,760.815 μs" 19 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,"88,950.26 μs",985.962 μs,922.270 μs 20 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,"332,289.77 μs","5,000.057 μs","4,677.057 μs" 21 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,"331,999.09 μs","1,807.221 μs","1,509.112 μs" 22 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"218,414.38 μs","1,928.154 μs","1,803.596 μs" 23 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"560,848.87 μs","4,607.935 μs","4,084.814 μs" 24 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"118,026.31 μs",704.415 μs,624.445 μs 25 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"446,268.12 μs","6,774.194 μs","6,336.586 μs" 26 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,"483,023.41 μs","5,186.810 μs","4,851.745 μs" 27 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"1,412,598.17 μs","21,065.035 μs","19,704.246 μs" 28 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"3,259,079.75 μs","21,505.015 μs","19,063.636 μs" 29 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"633,888.01 μs","7,738.250 μs","7,238.364 μs" 30 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"2,471,130.55 μs","22,772.691 μs","21,301.589 μs" 31 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"2,926,849.53 μs","25,341.168 μs","23,704.144 μs" 32 | OfSeqHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"3,299,878.09 μs","18,616.826 μs","17,414.191 μs" 33 | OfSeqFSharpXMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"8,251,430.10 μs","53,425.476 μs","47,360.293 μs" 34 | OfSeqFSharpAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"1,355,405.41 μs","8,122.114 μs","7,200.042 μs" 35 | OfSeqSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"5,228,771.15 μs","52,710.431 μs","49,305.370 μs" 36 | OfSeqFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 5.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"6,699,184.76 μs","39,367.783 μs","34,898.515 μs" 37 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.OfSeqBenchmark-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FSharp.HashCollections.Benchmarks.OfSeqBenchmark-20211026-101553 6 | 7 | 13 | 14 | 15 |

16 | BenchmarkDotNet=v0.13.1, OS=arch 
17 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
18 | .NET SDK=5.0.301
19 |   [Host]     : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT DEBUG
20 |   DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
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 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
MethodCollectionSize Mean Error StdDev
OfSeqHashMap1000110.17 μs1.038 μs0.971 μs
OfSeqFSharpXMap1000330.68 μs1.873 μs1.752 μs
OfSeqFSharpAdaptiveMap100089.84 μs0.679 μs0.636 μs
OfSeqSystemCollectionsImmutableMap1000256.79 μs4.808 μs4.722 μs
OfSeqFSharpxChampMap1000286.88 μs2.472 μs2.313 μs
OfSeqHashMap10000013,449.39 μs151.712 μs141.911 μs
OfSeqFSharpXMap10000037,750.52 μs574.083 μs536.997 μs
OfSeqFSharpAdaptiveMap10000010,982.73 μs120.187 μs112.423 μs
OfSeqSystemCollectionsImmutableMap10000039,097.60 μs524.268 μs490.401 μs
OfSeqFSharpxChampMap10000037,379.44 μs361.439 μs338.090 μs
OfSeqHashMap50000096,185.59 μs529.517 μs442.170 μs
OfSeqFSharpXMap500000291,985.04 μs4,210.226 μs3,938.248 μs
OfSeqFSharpAdaptiveMap50000060,188.35 μs729.702 μs682.564 μs
OfSeqSystemCollectionsImmutableMap500000216,527.30 μs2,839.435 μs2,656.009 μs
OfSeqFSharpxChampMap500000195,841.18 μs1,429.687 μs1,267.380 μs
OfSeqHashMap750000156,793.76 μs1,340.004 μs1,253.441 μs
OfSeqFSharpXMap750000424,483.55 μs4,242.442 μs3,760.815 μs
OfSeqFSharpAdaptiveMap75000088,950.26 μs985.962 μs922.270 μs
OfSeqSystemCollectionsImmutableMap750000332,289.77 μs5,000.057 μs4,677.057 μs
OfSeqFSharpxChampMap750000331,999.09 μs1,807.221 μs1,509.112 μs
OfSeqHashMap1000000218,414.38 μs1,928.154 μs1,803.596 μs
OfSeqFSharpXMap1000000560,848.87 μs4,607.935 μs4,084.814 μs
OfSeqFSharpAdaptiveMap1000000118,026.31 μs704.415 μs624.445 μs
OfSeqSystemCollectionsImmutableMap1000000446,268.12 μs6,774.194 μs6,336.586 μs
OfSeqFSharpxChampMap1000000483,023.41 μs5,186.810 μs4,851.745 μs
OfSeqHashMap50000001,412,598.17 μs21,065.035 μs19,704.246 μs
OfSeqFSharpXMap50000003,259,079.75 μs21,505.015 μs19,063.636 μs
OfSeqFSharpAdaptiveMap5000000633,888.01 μs7,738.250 μs7,238.364 μs
OfSeqSystemCollectionsImmutableMap50000002,471,130.55 μs22,772.691 μs21,301.589 μs
OfSeqFSharpxChampMap50000002,926,849.53 μs25,341.168 μs23,704.144 μs
OfSeqHashMap100000003,299,878.09 μs18,616.826 μs17,414.191 μs
OfSeqFSharpXMap100000008,251,430.10 μs53,425.476 μs47,360.293 μs
OfSeqFSharpAdaptiveMap100000001,355,405.41 μs8,122.114 μs7,200.042 μs
OfSeqSystemCollectionsImmutableMap100000005,228,771.15 μs52,710.431 μs49,305.370 μs
OfSeqFSharpxChampMap100000006,699,184.76 μs39,367.783 μs34,898.515 μs
63 | 64 | 65 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.ReadBenchmarks-report-github.md: -------------------------------------------------------------------------------- 1 | ``` ini 2 | 3 | BenchmarkDotNet=v0.13.1, OS=arch 4 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores 5 | .NET SDK=6.0.403 6 | [Host] : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT DEBUG 7 | DefaultJob : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT 8 | 9 | 10 | ``` 11 | | Method | CollectionSize | Mean | Error | StdDev | 12 | |--------------------------------- |--------------- |------------:|----------:|----------:| 13 | | **GetHashMap** | **10** | **11.49 ns** | **0.091 ns** | **0.081 ns** | 14 | | GetImToolsHashMap | 10 | 31.58 ns | 0.190 ns | 0.169 ns | 15 | | GetFSharpMap | 10 | 26.97 ns | 0.208 ns | 0.194 ns | 16 | | GetFSharpXHashMap | 10 | 129.21 ns | 0.806 ns | 0.754 ns | 17 | | GetSystemCollectionsImmutableMap | 10 | 21.80 ns | 0.325 ns | 0.304 ns | 18 | | GetFSharpDataAdaptiveMap | 10 | 22.95 ns | 0.234 ns | 0.219 ns | 19 | | GetFSharpxChampMap | 10 | 72.75 ns | 0.439 ns | 0.367 ns | 20 | | GetLangExtMap | 10 | 24.83 ns | 0.218 ns | 0.193 ns | 21 | | **GetHashMap** | **100** | **11.75 ns** | **0.120 ns** | **0.112 ns** | 22 | | GetImToolsHashMap | 100 | 44.99 ns | 0.358 ns | 0.317 ns | 23 | | GetFSharpMap | 100 | 47.45 ns | 0.418 ns | 0.391 ns | 24 | | GetFSharpXHashMap | 100 | 148.55 ns | 0.919 ns | 0.815 ns | 25 | | GetSystemCollectionsImmutableMap | 100 | 33.26 ns | 0.246 ns | 0.230 ns | 26 | | GetFSharpDataAdaptiveMap | 100 | 44.84 ns | 0.400 ns | 0.374 ns | 27 | | GetFSharpxChampMap | 100 | 83.54 ns | 1.017 ns | 0.951 ns | 28 | | GetLangExtMap | 100 | 31.26 ns | 0.264 ns | 0.247 ns | 29 | | **GetHashMap** | **1000** | **11.67 ns** | **0.090 ns** | **0.084 ns** | 30 | | GetImToolsHashMap | 1000 | 69.63 ns | 0.974 ns | 0.911 ns | 31 | | GetFSharpMap | 1000 | 71.16 ns | 0.762 ns | 0.713 ns | 32 | | GetFSharpXHashMap | 1000 | 161.89 ns | 0.539 ns | 0.450 ns | 33 | | GetSystemCollectionsImmutableMap | 1000 | 49.93 ns | 0.489 ns | 0.458 ns | 34 | | GetFSharpDataAdaptiveMap | 1000 | 67.05 ns | 0.725 ns | 0.678 ns | 35 | | GetFSharpxChampMap | 1000 | 84.44 ns | 1.061 ns | 0.992 ns | 36 | | GetLangExtMap | 1000 | 31.00 ns | 0.312 ns | 0.292 ns | 37 | | **GetHashMap** | **100000** | **33.47 ns** | **0.376 ns** | **0.314 ns** | 38 | | GetImToolsHashMap | 100000 | 216.79 ns | 4.285 ns | 4.009 ns | 39 | | GetFSharpMap | 100000 | 177.11 ns | 2.074 ns | 1.940 ns | 40 | | GetFSharpXHashMap | 100000 | 231.84 ns | 4.178 ns | 6.627 ns | 41 | | GetSystemCollectionsImmutableMap | 100000 | 162.46 ns | 2.537 ns | 2.374 ns | 42 | | GetFSharpDataAdaptiveMap | 100000 | 154.96 ns | 2.743 ns | 2.566 ns | 43 | | GetFSharpxChampMap | 100000 | 114.73 ns | 1.243 ns | 1.163 ns | 44 | | GetLangExtMap | 100000 | 61.52 ns | 0.609 ns | 0.570 ns | 45 | | **GetHashMap** | **500000** | **35.67 ns** | **0.622 ns** | **0.582 ns** | 46 | | GetImToolsHashMap | 500000 | 528.33 ns | 10.475 ns | 12.063 ns | 47 | | GetFSharpMap | 500000 | 327.39 ns | 6.410 ns | 11.881 ns | 48 | | GetFSharpXHashMap | 500000 | 336.89 ns | 5.012 ns | 4.688 ns | 49 | | GetSystemCollectionsImmutableMap | 500000 | 359.41 ns | 7.174 ns | 11.787 ns | 50 | | GetFSharpDataAdaptiveMap | 500000 | 324.83 ns | 6.091 ns | 5.982 ns | 51 | | GetFSharpxChampMap | 500000 | 122.62 ns | 1.997 ns | 2.137 ns | 52 | | GetLangExtMap | 500000 | 67.00 ns | 1.128 ns | 1.055 ns | 53 | | **GetHashMap** | **750000** | **38.42 ns** | **0.513 ns** | **0.428 ns** | 54 | | GetImToolsHashMap | 750000 | 639.63 ns | 12.341 ns | 14.691 ns | 55 | | GetFSharpMap | 750000 | 429.89 ns | 8.344 ns | 10.247 ns | 56 | | GetFSharpXHashMap | 750000 | 446.19 ns | 5.108 ns | 4.778 ns | 57 | | GetSystemCollectionsImmutableMap | 750000 | 440.76 ns | 8.714 ns | 10.035 ns | 58 | | GetFSharpDataAdaptiveMap | 750000 | 364.25 ns | 7.225 ns | 6.758 ns | 59 | | GetFSharpxChampMap | 750000 | 128.73 ns | 2.571 ns | 3.848 ns | 60 | | GetLangExtMap | 750000 | 72.10 ns | 1.426 ns | 1.334 ns | 61 | | **GetHashMap** | **1000000** | **41.58 ns** | **0.804 ns** | **0.752 ns** | 62 | | GetImToolsHashMap | 1000000 | 716.12 ns | 13.576 ns | 12.699 ns | 63 | | GetFSharpMap | 1000000 | 478.64 ns | 9.391 ns | 11.877 ns | 64 | | GetFSharpXHashMap | 1000000 | 426.63 ns | 4.339 ns | 4.059 ns | 65 | | GetSystemCollectionsImmutableMap | 1000000 | 506.48 ns | 9.872 ns | 9.695 ns | 66 | | GetFSharpDataAdaptiveMap | 1000000 | 395.59 ns | 5.966 ns | 5.581 ns | 67 | | GetFSharpxChampMap | 1000000 | 132.05 ns | 2.585 ns | 4.319 ns | 68 | | GetLangExtMap | 1000000 | 79.30 ns | 1.577 ns | 2.408 ns | 69 | | **GetHashMap** | **5000000** | **155.94 ns** | **0.957 ns** | **0.799 ns** | 70 | | GetImToolsHashMap | 5000000 | 1,138.25 ns | 18.869 ns | 17.650 ns | 71 | | GetFSharpMap | 5000000 | 823.41 ns | 10.984 ns | 10.274 ns | 72 | | GetFSharpXHashMap | 5000000 | 475.15 ns | 6.462 ns | 6.044 ns | 73 | | GetSystemCollectionsImmutableMap | 5000000 | 826.09 ns | 15.213 ns | 14.230 ns | 74 | | GetFSharpDataAdaptiveMap | 5000000 | 579.94 ns | 6.645 ns | 6.216 ns | 75 | | GetFSharpxChampMap | 5000000 | 285.59 ns | 3.235 ns | 3.026 ns | 76 | | GetLangExtMap | 5000000 | 234.92 ns | 3.571 ns | 3.341 ns | 77 | | **GetHashMap** | **10000000** | **159.87 ns** | **2.199 ns** | **1.950 ns** | 78 | | GetImToolsHashMap | 10000000 | 1,345.84 ns | 26.844 ns | 26.364 ns | 79 | | GetFSharpMap | 10000000 | 957.82 ns | 14.825 ns | 13.868 ns | 80 | | GetFSharpXHashMap | 10000000 | 509.87 ns | 9.244 ns | 8.647 ns | 81 | | GetSystemCollectionsImmutableMap | 10000000 | 960.39 ns | 18.921 ns | 17.699 ns | 82 | | GetFSharpDataAdaptiveMap | 10000000 | 672.11 ns | 9.232 ns | 8.636 ns | 83 | | GetFSharpxChampMap | 10000000 | 291.37 ns | 3.615 ns | 3.381 ns | 84 | | GetLangExtMap | 10000000 | 237.09 ns | 3.173 ns | 2.968 ns | 85 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.ReadBenchmarks-report.csv: -------------------------------------------------------------------------------- 1 | Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,CollectionSize,Mean,Error,StdDev 2 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,11.49 ns,0.091 ns,0.081 ns 3 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,31.58 ns,0.190 ns,0.169 ns 4 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,26.97 ns,0.208 ns,0.194 ns 5 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,129.21 ns,0.806 ns,0.754 ns 6 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,21.80 ns,0.325 ns,0.304 ns 7 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,22.95 ns,0.234 ns,0.219 ns 8 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,72.75 ns,0.439 ns,0.367 ns 9 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10,24.83 ns,0.218 ns,0.193 ns 10 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,11.75 ns,0.120 ns,0.112 ns 11 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,44.99 ns,0.358 ns,0.317 ns 12 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,47.45 ns,0.418 ns,0.391 ns 13 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,148.55 ns,0.919 ns,0.815 ns 14 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,33.26 ns,0.246 ns,0.230 ns 15 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,44.84 ns,0.400 ns,0.374 ns 16 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,83.54 ns,1.017 ns,0.951 ns 17 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100,31.26 ns,0.264 ns,0.247 ns 18 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,11.67 ns,0.090 ns,0.084 ns 19 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,69.63 ns,0.974 ns,0.911 ns 20 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,71.16 ns,0.762 ns,0.713 ns 21 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,161.89 ns,0.539 ns,0.450 ns 22 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,49.93 ns,0.489 ns,0.458 ns 23 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,67.05 ns,0.725 ns,0.678 ns 24 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,84.44 ns,1.061 ns,0.992 ns 25 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000,31.00 ns,0.312 ns,0.292 ns 26 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,33.47 ns,0.376 ns,0.314 ns 27 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,216.79 ns,4.285 ns,4.009 ns 28 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,177.11 ns,2.074 ns,1.940 ns 29 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,231.84 ns,4.178 ns,6.627 ns 30 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,162.46 ns,2.537 ns,2.374 ns 31 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,154.96 ns,2.743 ns,2.566 ns 32 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,114.73 ns,1.243 ns,1.163 ns 33 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,100000,61.52 ns,0.609 ns,0.570 ns 34 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,35.67 ns,0.622 ns,0.582 ns 35 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,528.33 ns,10.475 ns,12.063 ns 36 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,327.39 ns,6.410 ns,11.881 ns 37 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,336.89 ns,5.012 ns,4.688 ns 38 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,359.41 ns,7.174 ns,11.787 ns 39 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,324.83 ns,6.091 ns,5.982 ns 40 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,122.62 ns,1.997 ns,2.137 ns 41 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,500000,67.00 ns,1.128 ns,1.055 ns 42 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,38.42 ns,0.513 ns,0.428 ns 43 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,639.63 ns,12.341 ns,14.691 ns 44 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,429.89 ns,8.344 ns,10.247 ns 45 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,446.19 ns,5.108 ns,4.778 ns 46 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,440.76 ns,8.714 ns,10.035 ns 47 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,364.25 ns,7.225 ns,6.758 ns 48 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,128.73 ns,2.571 ns,3.848 ns 49 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,750000,72.10 ns,1.426 ns,1.334 ns 50 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,41.58 ns,0.804 ns,0.752 ns 51 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,716.12 ns,13.576 ns,12.699 ns 52 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,478.64 ns,9.391 ns,11.877 ns 53 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,426.63 ns,4.339 ns,4.059 ns 54 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,506.48 ns,9.872 ns,9.695 ns 55 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,395.59 ns,5.966 ns,5.581 ns 56 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,132.05 ns,2.585 ns,4.319 ns 57 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,1000000,79.30 ns,1.577 ns,2.408 ns 58 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,155.94 ns,0.957 ns,0.799 ns 59 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,"1,138.25 ns",18.869 ns,17.650 ns 60 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,823.41 ns,10.984 ns,10.274 ns 61 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,475.15 ns,6.462 ns,6.044 ns 62 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,826.09 ns,15.213 ns,14.230 ns 63 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,579.94 ns,6.645 ns,6.216 ns 64 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,285.59 ns,3.235 ns,3.026 ns 65 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,5000000,234.92 ns,3.571 ns,3.341 ns 66 | GetHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,159.87 ns,2.199 ns,1.950 ns 67 | GetImToolsHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,"1,345.84 ns",26.844 ns,26.364 ns 68 | GetFSharpMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,957.82 ns,14.825 ns,13.868 ns 69 | GetFSharpXHashMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,509.87 ns,9.244 ns,8.647 ns 70 | GetSystemCollectionsImmutableMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,960.39 ns,18.921 ns,17.699 ns 71 | GetFSharpDataAdaptiveMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,672.11 ns,9.232 ns,8.636 ns 72 | GetFSharpxChampMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,291.37 ns,3.615 ns,3.381 ns 73 | GetLangExtMap,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111111111111111,Empty,RyuJit,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 6.0,False,True,False,True,Default,Default,False,False,True,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,10000000,237.09 ns,3.173 ns,2.968 ns 74 | -------------------------------------------------------------------------------- /benchmarks/FSharp.HashCollections.Benchmarks.ReadBenchmarks-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FSharp.HashCollections.Benchmarks.ReadBenchmarks-20231011-211438 6 | 7 | 13 | 14 | 15 |

 16 | BenchmarkDotNet=v0.13.1, OS=arch 
 17 | AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
 18 | .NET SDK=6.0.403
 19 |   [Host]     : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT DEBUG
 20 |   DefaultJob : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT
 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 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
MethodCollectionSize MeanErrorStdDev
GetHashMap1011.49 ns0.091 ns0.081 ns
GetImToolsHashMap1031.58 ns0.190 ns0.169 ns
GetFSharpMap1026.97 ns0.208 ns0.194 ns
GetFSharpXHashMap10129.21 ns0.806 ns0.754 ns
GetSystemCollectionsImmutableMap1021.80 ns0.325 ns0.304 ns
GetFSharpDataAdaptiveMap1022.95 ns0.234 ns0.219 ns
GetFSharpxChampMap1072.75 ns0.439 ns0.367 ns
GetLangExtMap1024.83 ns0.218 ns0.193 ns
GetHashMap10011.75 ns0.120 ns0.112 ns
GetImToolsHashMap10044.99 ns0.358 ns0.317 ns
GetFSharpMap10047.45 ns0.418 ns0.391 ns
GetFSharpXHashMap100148.55 ns0.919 ns0.815 ns
GetSystemCollectionsImmutableMap10033.26 ns0.246 ns0.230 ns
GetFSharpDataAdaptiveMap10044.84 ns0.400 ns0.374 ns
GetFSharpxChampMap10083.54 ns1.017 ns0.951 ns
GetLangExtMap10031.26 ns0.264 ns0.247 ns
GetHashMap100011.67 ns0.090 ns0.084 ns
GetImToolsHashMap100069.63 ns0.974 ns0.911 ns
GetFSharpMap100071.16 ns0.762 ns0.713 ns
GetFSharpXHashMap1000161.89 ns0.539 ns0.450 ns
GetSystemCollectionsImmutableMap100049.93 ns0.489 ns0.458 ns
GetFSharpDataAdaptiveMap100067.05 ns0.725 ns0.678 ns
GetFSharpxChampMap100084.44 ns1.061 ns0.992 ns
GetLangExtMap100031.00 ns0.312 ns0.292 ns
GetHashMap10000033.47 ns0.376 ns0.314 ns
GetImToolsHashMap100000216.79 ns4.285 ns4.009 ns
GetFSharpMap100000177.11 ns2.074 ns1.940 ns
GetFSharpXHashMap100000231.84 ns4.178 ns6.627 ns
GetSystemCollectionsImmutableMap100000162.46 ns2.537 ns2.374 ns
GetFSharpDataAdaptiveMap100000154.96 ns2.743 ns2.566 ns
GetFSharpxChampMap100000114.73 ns1.243 ns1.163 ns
GetLangExtMap10000061.52 ns0.609 ns0.570 ns
GetHashMap50000035.67 ns0.622 ns0.582 ns
GetImToolsHashMap500000528.33 ns10.475 ns12.063 ns
GetFSharpMap500000327.39 ns6.410 ns11.881 ns
GetFSharpXHashMap500000336.89 ns5.012 ns4.688 ns
GetSystemCollectionsImmutableMap500000359.41 ns7.174 ns11.787 ns
GetFSharpDataAdaptiveMap500000324.83 ns6.091 ns5.982 ns
GetFSharpxChampMap500000122.62 ns1.997 ns2.137 ns
GetLangExtMap50000067.00 ns1.128 ns1.055 ns
GetHashMap75000038.42 ns0.513 ns0.428 ns
GetImToolsHashMap750000639.63 ns12.341 ns14.691 ns
GetFSharpMap750000429.89 ns8.344 ns10.247 ns
GetFSharpXHashMap750000446.19 ns5.108 ns4.778 ns
GetSystemCollectionsImmutableMap750000440.76 ns8.714 ns10.035 ns
GetFSharpDataAdaptiveMap750000364.25 ns7.225 ns6.758 ns
GetFSharpxChampMap750000128.73 ns2.571 ns3.848 ns
GetLangExtMap75000072.10 ns1.426 ns1.334 ns
GetHashMap100000041.58 ns0.804 ns0.752 ns
GetImToolsHashMap1000000716.12 ns13.576 ns12.699 ns
GetFSharpMap1000000478.64 ns9.391 ns11.877 ns
GetFSharpXHashMap1000000426.63 ns4.339 ns4.059 ns
GetSystemCollectionsImmutableMap1000000506.48 ns9.872 ns9.695 ns
GetFSharpDataAdaptiveMap1000000395.59 ns5.966 ns5.581 ns
GetFSharpxChampMap1000000132.05 ns2.585 ns4.319 ns
GetLangExtMap100000079.30 ns1.577 ns2.408 ns
GetHashMap5000000155.94 ns0.957 ns0.799 ns
GetImToolsHashMap50000001,138.25 ns18.869 ns17.650 ns
GetFSharpMap5000000823.41 ns10.984 ns10.274 ns
GetFSharpXHashMap5000000475.15 ns6.462 ns6.044 ns
GetSystemCollectionsImmutableMap5000000826.09 ns15.213 ns14.230 ns
GetFSharpDataAdaptiveMap5000000579.94 ns6.645 ns6.216 ns
GetFSharpxChampMap5000000285.59 ns3.235 ns3.026 ns
GetLangExtMap5000000234.92 ns3.571 ns3.341 ns
GetHashMap10000000159.87 ns2.199 ns1.950 ns
GetImToolsHashMap100000001,345.84 ns26.844 ns26.364 ns
GetFSharpMap10000000957.82 ns14.825 ns13.868 ns
GetFSharpXHashMap10000000509.87 ns9.244 ns8.647 ns
GetSystemCollectionsImmutableMap10000000960.39 ns18.921 ns17.699 ns
GetFSharpDataAdaptiveMap10000000672.11 ns9.232 ns8.636 ns
GetFSharpxChampMap10000000291.37 ns3.615 ns3.381 ns
GetLangExtMap10000000237.09 ns3.173 ns2.968 ns
100 | 101 | 102 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.403", 4 | "rollForward": "latestFeature" 5 | } 6 | } -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | 3 | nuget FSharp.Core >= 6 4 | nuget BenchmarkDotNet 0.13.1 5 | nuget Expecto 8.13.1 6 | nuget Expecto.FsCheck 8.13.1 7 | nuget FsCheck 2.14.0 8 | nuget FSharp.Data.Adaptive 0.0.13 9 | nuget FSharpX.Collections 3.0.1 10 | nuget FSharpx.Collections.Experimental 3.0.1 11 | nuget ImTools.dll 1.0.0 12 | nuget LanguageExt.FSharp 13 | nuget System.Collections.Immutable ~> 6 -------------------------------------------------------------------------------- /src/FSharp.HashCollections/CompressedArray.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections 2 | 3 | open System 4 | open System.Runtime.CompilerServices 5 | 6 | type BitMaskType = uint32 7 | 8 | /// A fixed length array like structure. Only allocates as many entries as required to store elements with a maximum of sizeof(BitMap) elements. 9 | type [] internal CompressedArray<'t> = { BitMap: BitMaskType; Content: 't array } 10 | 11 | module internal ArrayHelpers = 12 | 13 | let inline arrayCreate<'t> length = 14 | Array.zeroCreate<'t> length 15 | 16 | let inline copyArray (sourceArray: 't[]) : 't[] = 17 | sourceArray.AsSpan().ToArray() 18 | 19 | let inline copyArrayInsertInMiddle (insertPos : int) (itemToInsert: 't) (sourceArray: 't[]) : 't[] = 20 | let result = arrayCreate (sourceArray.Length + 1) 21 | System.Array.Copy (sourceArray, result, insertPos) 22 | System.Array.Copy (sourceArray, insertPos, result, insertPos + 1, sourceArray.Length - insertPos) 23 | result.[insertPos] <- itemToInsert 24 | result 25 | 26 | let inline copyArrayWithIndexRemoved (indexToRemove: int) (sourceArray: 't[]) : 't[] = 27 | let result = arrayCreate (sourceArray.Length - 1) 28 | System.Array.Copy (sourceArray, result, indexToRemove) 29 | System.Array.Copy (sourceArray, indexToRemove + 1, result, indexToRemove, sourceArray.Length - indexToRemove - 1) 30 | result 31 | 32 | open ArrayHelpers 33 | 34 | /// Module for handling fixed compressed bitmap array. 35 | /// Many operations in this module aren't checked and if not used properly could lead to data corruption. Use with caution. 36 | module internal CompressedArray = 37 | 38 | let [] MaxSize = 32 39 | let [] AllNodesSetBitMap = BitMaskType.MaxValue 40 | let [] Zero : BitMaskType = 0u 41 | let [] One : BitMaskType = 1u 42 | let [] LeastSigBitSet = One 43 | 44 | /// Has a software fallback if not supported built inside with an IF statement. 45 | let inline popCount (x: BitMaskType) = System.Numerics.BitOperations.PopCount x 46 | 47 | let [] empty<'t> : CompressedArray<'t> = { BitMap = Zero; Content = Array.Empty() } 48 | 49 | let inline getBitMapForIndex index = LeastSigBitSet <<< index 50 | 51 | let inline boundsCheckIfSetForBitMapIndex bitMap indexBitMap = (indexBitMap &&& bitMap) = indexBitMap 52 | 53 | let inline boundsCheckIfSet bitMap index = boundsCheckIfSetForBitMapIndex bitMap (getBitMapForIndex index) 54 | 55 | let inline getCompressedIndexForIndexBitmap bitMap bitMapIndex = 56 | (bitMap &&& (bitMapIndex - One)) |> popCount 57 | 58 | let inline getCompressedIndex bitMap index = 59 | getCompressedIndexForIndexBitmap bitMap (getBitMapForIndex index) 60 | 61 | let inline replaceNoCheck compressedIndex value ca = 62 | let newContent = copyArray ca.Content 63 | newContent.[compressedIndex] <- value 64 | { BitMap = ca.BitMap; Content = newContent } 65 | 66 | let set index value ca = 67 | let bit = getBitMapForIndex index 68 | let compressedIndex = getCompressedIndex ca.BitMap index 69 | if (bit &&& ca.BitMap) <> Zero 70 | then replaceNoCheck compressedIndex value ca 71 | else 72 | // Add new entry 73 | let newBitMap = ca.BitMap ||| bit 74 | let newContent = copyArrayInsertInMiddle compressedIndex value ca.Content 75 | { BitMap = newBitMap; Content = newContent } 76 | 77 | /// Unsafe. Allows element in position to be replaced. Element must of previously been set. 78 | let inline replaceAtIndexNoCheck index value ca = 79 | let compressedIndex = getCompressedIndex ca.BitMap index 80 | replaceNoCheck compressedIndex value ca 81 | 82 | // NOTE: This function when non-inlined after testing can cause performance regressions due to struct copying. 83 | let inline get index ca = 84 | let bitPos = getBitMapForIndex index 85 | if boundsCheckIfSetForBitMapIndex ca.BitMap bitPos // This checks if the bit was set in the first place. 86 | then (true, ca.Content.[getCompressedIndexForIndexBitmap ca.BitMap bitPos]) 87 | else (false, Unchecked.defaultof<_>) 88 | 89 | /// Get the amount of set positions in the compressed array. 90 | let count ca = ca.Content.Length 91 | 92 | /// Creates a new compressed array with the index unset. 93 | let inline unset index ca = 94 | if boundsCheckIfSet ca.BitMap index 95 | then 96 | let compressedIndex = getCompressedIndex ca.BitMap index 97 | let bitToUnsetMask = getBitMapForIndex index 98 | let newBitMap = ca.BitMap ^^^ bitToUnsetMask 99 | let newContent = copyArrayWithIndexRemoved compressedIndex ca.Content 100 | { BitMap = newBitMap; Content = newContent } 101 | else ca // Do nothing; not set. 102 | 103 | let ofArray (a: _ array) = 104 | let mutable r = empty 105 | for i = 0 to a.Length - 1 do 106 | r <- r |> set i a.[i] 107 | r 108 | 109 | /// Given an array of MaxSize in length (not checked) produces a CompressedArray in O(1) time. 110 | /// Not a checked operation. 111 | let inline ofFullArrayAsTransient (a: _ array) = { BitMap = AllNodesSetBitMap; Content = a } 112 | 113 | /// Creates a compressed array of length = 1 with the supplied element in the index specified. 114 | let inline ofSingleElement index element = { BitMap = LeastSigBitSet <<< index; Content = [| element |] } 115 | 116 | /// Creates a compressed array of two elements. 117 | let inline ofTwoElements index1 element1 index2 element2 = 118 | let a = if index1 < index2 then [| element1; element2 |] else [| element2; element1 |] 119 | let newBitMap = Zero ||| (getBitMapForIndex index1) ||| (getBitMapForIndex index2) 120 | { BitMap = newBitMap; Content = a } -------------------------------------------------------------------------------- /src/FSharp.HashCollections/FSharp.HashCollections.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net5.0;net6.0 5 | 6 | 7 | FSharp.HashCollections 8 | FSharp.HashCollections 9 | Immutable hash collections 10 | 0.3.4 11 | mvkara 12 | fsharp;collections;hamt;hashmap;hashset;map;set 13 | https://github.com/mvkara/fsharp-hashcollections 14 | MIT 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/FSharp.HashCollections/HamtImpl.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Linq 6 | 7 | /// Underlying Hash Trie implementation for other collections. 8 | module internal HashTrie = 9 | 10 | let [] PartitionSize = 5 11 | let [] PartitionMask = 0b11111 12 | let [] MaxShiftValue = 32 // Partition Size amount of 1 bits 13 | 14 | let inline getIndexNoShift shiftedHash = shiftedHash &&& PartitionMask 15 | let inline getIndex keyHash shift = getIndexNoShift (keyHash >>> shift) 16 | 17 | let inline tryFind 18 | ([] keyExtractor: 'tknode -> 'tk) 19 | ([] valueExtractor: 'tknode -> 'tv) 20 | (eqTemplate: 'teq when 'teq :> IEqualityComparer<'tk>) 21 | (k: 'tk) 22 | (hashMap: HashTrieRoot<'tknode>) : 'tv voption = 23 | 24 | let keyHash = eqTemplate.GetHashCode(k) 25 | 26 | let handleHashCollisionList (l : _ list) = 27 | let rec findInList currentList = 28 | match currentList with 29 | | entry :: tail -> 30 | let extractedKey = keyExtractor entry 31 | if eqTemplate.Equals(extractedKey, k) 32 | then ValueSome (valueExtractor entry) 33 | else findInList tail 34 | | [] -> ValueNone 35 | findInList l 36 | 37 | let rec getRec content remainderHash = 38 | let index = getIndexNoShift remainderHash 39 | 40 | if content.Nodes.BitMap = BitMaskType.MaxValue 41 | then 42 | match content.Nodes.Content.[index] with 43 | | TrieNode content -> getRec content (remainderHash >>> PartitionSize) 44 | | HashCollisionNode hcn -> handleHashCollisionList hcn 45 | else 46 | match CompressedArray.get index content.Nodes with // This checks if the bit was set in the first place. 47 | | (true, content) -> 48 | match content with 49 | | TrieNode content -> getRec content (remainderHash >>> PartitionSize) 50 | | HashCollisionNode hcn -> handleHashCollisionList hcn 51 | | _ -> 52 | match CompressedArray.get index content.Entries with 53 | | (true, node) -> 54 | if eqTemplate.Equals(keyExtractor node, k) 55 | then valueExtractor node |> ValueSome 56 | else ValueNone 57 | | _ -> ValueNone 58 | 59 | getRec hashMap.RootData keyHash 60 | 61 | let inline buildTrieNodeContent(nodes, entries) = {Nodes = nodes; Entries = entries} 62 | let inline buildTrieNode(nodes, entries) = TrieNode(buildTrieNodeContent(nodes, entries)) 63 | 64 | let inline addNodesToResolveConflict oldNodeMap oldValuesMap existingEntry newEntry existingEntryHash currentKeyHash shift = 65 | let rec createRequiredDepthNodes (first: bool) shift = 66 | let existingEntryIndex = getIndex existingEntryHash shift 67 | let currentEntryIndex = getIndex currentKeyHash shift 68 | 69 | if existingEntryIndex <> currentEntryIndex 70 | then 71 | let ca = 72 | CompressedArray.ofTwoElements 73 | existingEntryIndex existingEntry 74 | currentEntryIndex newEntry 75 | (CompressedArray.empty, ca) 76 | else 77 | let newShift = shift + PartitionSize 78 | let subNode = 79 | if newShift >= MaxShiftValue 80 | then HashCollisionNode([ existingEntry; newEntry] ) 81 | else buildTrieNode <| createRequiredDepthNodes false newShift 82 | if first 83 | then (CompressedArray.set existingEntryIndex subNode oldNodeMap, CompressedArray.unset currentEntryIndex oldValuesMap) // Not sure about empty here. 84 | else (CompressedArray.ofSingleElement existingEntryIndex subNode, CompressedArray.empty) // Not sure about empty here. 85 | createRequiredDepthNodes true shift 86 | 87 | let inline add 88 | ([] keyExtractor: 'tknode -> 'tk) 89 | (eqTemplate: 'teq when 'teq :> IEqualityComparer<'tk>) 90 | (knode: 'tknode) 91 | (hashMap: HashTrieRoot<'tknode>) : HashTrieRoot<'tknode> = 92 | 93 | let inline equals x y = eqTemplate.Equals(x, y) 94 | let inline hash o = eqTemplate.GetHashCode(o) 95 | 96 | let key = keyExtractor knode 97 | let keyHash = hash key 98 | 99 | let handleHashCollisionNode entries = 100 | let rec replaceElementIfExists previouslySeen tailList = 101 | match tailList with 102 | | entryNode :: tail -> 103 | let extractedKey = keyExtractor entryNode 104 | if equals extractedKey key 105 | then (List.append (knode :: previouslySeen) tail, true) 106 | else replaceElementIfExists (entryNode :: previouslySeen) tail 107 | | [] -> ([], false) 108 | 109 | let (newList, replaced) = replaceElementIfExists [] entries 110 | if replaced 111 | then struct (HashCollisionNode(newList), false) 112 | else struct (HashCollisionNode(knode :: entries), true) 113 | 114 | let rec handleNodeContent ({ Nodes = nodes; Entries = values }: TrieNodeContent<_>) shift : struct (TrieNodeContent<_> * bool)= 115 | let index = getIndex keyHash shift 116 | if nodes.BitMap = BitMaskType.MaxValue 117 | then 118 | let nodeAtPos = nodes.Content.[index] 119 | let (newPosNode, isAdded) = 120 | match nodeAtPos with 121 | | TrieNode(nodeContent) -> 122 | let struct (d, isAdded) = handleNodeContent nodeContent (shift + PartitionSize) 123 | (TrieNode d, isAdded) 124 | | HashCollisionNode entries -> 125 | let struct (r, isAdded) = handleHashCollisionNode entries 126 | (r, isAdded) 127 | struct (buildTrieNodeContent (nodes |> CompressedArray.replaceNoCheck index newPosNode, values), isAdded) 128 | else 129 | let indexBit = CompressedArray.getBitMapForIndex index 130 | if CompressedArray.boundsCheckIfSetForBitMapIndex nodes.BitMap indexBit then // Case where node is already present, just recursively update it and replace. 131 | let compressedIndex = CompressedArray.getCompressedIndexForIndexBitmap nodes.BitMap indexBit 132 | let nodeAtPos = nodes.Content.[compressedIndex] 133 | let (newPosNode, isAdded) = 134 | match nodeAtPos with 135 | | TrieNode(nodeContent) -> 136 | let struct (d, isAdded) = handleNodeContent nodeContent (shift + PartitionSize) 137 | (TrieNode d, isAdded) 138 | | HashCollisionNode entries -> 139 | let struct (r, isAdded) = handleHashCollisionNode entries 140 | (r, isAdded) 141 | struct (buildTrieNodeContent (nodes |> CompressedArray.replaceNoCheck compressedIndex newPosNode, values), isAdded) 142 | elif CompressedArray.boundsCheckIfSetForBitMapIndex values.BitMap indexBit then // Where there is a value with that current one. 143 | let compressedIndex = CompressedArray.getCompressedIndexForIndexBitmap values.BitMap indexBit 144 | let valueAtPos = values.Content.[compressedIndex] 145 | let extractedKey = keyExtractor valueAtPos 146 | if equals extractedKey key 147 | then struct (buildTrieNodeContent (nodes, CompressedArray.replaceNoCheck compressedIndex knode values), false) // Replace existing value 148 | else struct (buildTrieNodeContent <| addNodesToResolveConflict nodes values valueAtPos knode (hash extractedKey) keyHash shift, true) // Move value to node list and create nodes as appropriate. 149 | else 150 | // Add new entry 151 | let newBitMap = values.BitMap ||| indexBit 152 | let compressedIndex = CompressedArray.getCompressedIndexForIndexBitmap values.BitMap indexBit 153 | let newContent = ArrayHelpers.copyArrayInsertInMiddle compressedIndex knode values.Content 154 | let newCa = { BitMap = newBitMap; Content = newContent } 155 | struct (buildTrieNodeContent(nodes, newCa), true) 156 | 157 | let struct (c, isAdded) = handleNodeContent hashMap.RootData 0 158 | 159 | { CurrentCount = if isAdded then hashMap.CurrentCount + 1 else hashMap.CurrentCount 160 | RootData = c } 161 | 162 | let inline ofSeq 163 | ([] keyExtractor: 'tknode -> 'tk) 164 | (eqTemplate: 'teq when 'teq :> IEqualityComparer<'tk>) 165 | (nodeList: #seq<'tknode>) 166 | (hashMap: HashTrieRoot<'tknode>) : HashTrieRoot<'tknode> = 167 | 168 | let inline equals x y = eqTemplate.Equals(x, y) 169 | let inline hash o = eqTemplate.GetHashCode(o) 170 | 171 | let inline addMutable knode content = 172 | let key = keyExtractor knode 173 | let keyHash = hash key 174 | 175 | let handleHashCollisionNode entries = 176 | let rec replaceElementIfExists previouslySeen tailList = 177 | match tailList with 178 | | entryNode :: tail -> 179 | let extractedKey = keyExtractor entryNode 180 | if equals extractedKey key 181 | then (List.append (knode :: previouslySeen) tail, true) 182 | else replaceElementIfExists (entryNode :: previouslySeen) tail 183 | | [] -> ([], false) 184 | 185 | let (newList, replaced) = replaceElementIfExists [] entries 186 | if replaced 187 | then struct (HashCollisionNode(newList), false) 188 | else struct (HashCollisionNode(knode :: entries), true) 189 | 190 | let rec handleNodeContent ({ Nodes = nodes; Entries = values } as trieNodeContent: TrieNodeContent<_>) shift : struct (TrieNodeContent<_> * bool)= 191 | let index = getIndex keyHash shift 192 | if nodes.BitMap = BitMaskType.MaxValue 193 | then 194 | let (newPosNode, isAdded) = 195 | match nodes.Content.[index] with 196 | | TrieNode(nodeContent) -> 197 | let struct (d, isAdded) = handleNodeContent nodeContent (shift + PartitionSize) 198 | (TrieNode d, isAdded) 199 | | HashCollisionNode entries -> 200 | let struct (d, isAdded) = handleHashCollisionNode entries 201 | (d, isAdded) 202 | nodes.Content[index] <- newPosNode 203 | struct (trieNodeContent, isAdded) 204 | else 205 | let indexBit = CompressedArray.getBitMapForIndex index 206 | if CompressedArray.boundsCheckIfSetForBitMapIndex nodes.BitMap indexBit then // Case where node is already present, just recursively update it and replace. 207 | let compressedIndex = CompressedArray.getCompressedIndexForIndexBitmap nodes.BitMap indexBit 208 | let nodeAtPos = nodes.Content.[compressedIndex] 209 | let (newPosNode, isAdded) = 210 | match nodeAtPos with 211 | | TrieNode(nodeContent) -> 212 | let struct (d, isAdded) = handleNodeContent nodeContent (shift + PartitionSize) 213 | (TrieNode d, isAdded) 214 | | HashCollisionNode entries -> 215 | let struct (r, isAdded) = handleHashCollisionNode entries 216 | (r, isAdded) 217 | nodes.Content[compressedIndex] <- newPosNode 218 | struct (trieNodeContent, isAdded) 219 | elif CompressedArray.boundsCheckIfSetForBitMapIndex values.BitMap indexBit then // Where there is a value with that current one. 220 | let compressedIndex = CompressedArray.getCompressedIndexForIndexBitmap values.BitMap indexBit 221 | let valueAtPos = values.Content.[compressedIndex] 222 | let extractedKey = keyExtractor valueAtPos 223 | if equals extractedKey key 224 | then 225 | values.Content[compressedIndex] <- knode 226 | struct (trieNodeContent, false) // Replace existing value 227 | else struct (buildTrieNodeContent <| addNodesToResolveConflict nodes values valueAtPos knode (hash extractedKey) keyHash shift, true) // Move value to node list and create nodes as appropriate. 228 | else 229 | // Add new entry 230 | let newBitMap = values.BitMap ||| indexBit 231 | let compressedIndex = CompressedArray.getCompressedIndexForIndexBitmap values.BitMap indexBit 232 | let newContent = ArrayHelpers.copyArrayInsertInMiddle compressedIndex knode values.Content 233 | let newCa = { BitMap = newBitMap; Content = newContent } 234 | struct (buildTrieNodeContent(nodes, newCa), true) 235 | 236 | handleNodeContent content 0 237 | 238 | let mutable count = hashMap.CurrentCount 239 | let mutable rootData = hashMap.RootData 240 | 241 | for i in nodeList do 242 | let struct (newRootData, isAdded) = addMutable i rootData 243 | if isAdded then count <- count + 1 244 | rootData <- newRootData 245 | 246 | { CurrentCount = count; RootData = rootData } 247 | 248 | [] 249 | type internal SubNodeChange<'t> = 250 | | RemoveChildNode 251 | | RemoveChildNodeAndPreserveSingleValue of childValueToPromote: 't 252 | | NewChildNode of trieNode: HashTrieNode<'t> 253 | | NoChange 254 | 255 | let inline remove 256 | ([] keyExtractor) 257 | (eqTemplate: 'teq when 'teq :> IEqualityComparer<'tk>) 258 | (k: 'tk) 259 | (hashMap: HashTrieRoot< 'tknode>) = 260 | 261 | let inline equals (x: 'tk) (y: 'tk) = eqTemplate.Equals(x, y) 262 | let inline hash (o: 'tk) = eqTemplate.GetHashCode(o) 263 | 264 | let keyHash = hash k 265 | let rec traverseNodes first nodes values shift = 266 | 267 | let index = getIndex keyHash shift 268 | match nodes |> CompressedArray.get index with 269 | | (false, _) -> 270 | match values |> CompressedArray.get index with 271 | | (true, entry) -> 272 | if equals (keyExtractor entry) k 273 | then 274 | let newValues = CompressedArray.unset index values 275 | match CompressedArray.count nodes, CompressedArray.count newValues with 276 | | (0, 1) when not first -> struct (RemoveChildNodeAndPreserveSingleValue newValues.Content.[0], true) 277 | | (_, _) -> struct (NewChildNode (buildTrieNode (nodes, values |> CompressedArray.unset index)), true) 278 | else struct (NoChange, false) 279 | | _ -> struct (NoChange, false) 280 | | (true, subNode) -> 281 | let struct (nodeValueList, newValuesList, didWeRemove) = 282 | match subNode with 283 | | TrieNode({Nodes = subNodes; Entries = subValues }) -> 284 | let subIndex = getIndex keyHash (shift + PartitionSize) 285 | let bit = CompressedArray.getBitMapForIndex subIndex 286 | if ((bit &&& subNodes.BitMap) <> CompressedArray.Zero) || ((bit &&& subValues.BitMap <> CompressedArray.Zero)) 287 | then // Case where node is already present, just recursively update it and replace. 288 | let (struct (childNodeOpt, didWeRemove)) = traverseNodes false subNodes subValues (shift + PartitionSize) 289 | match childNodeOpt with 290 | | NewChildNode(childNode) -> struct (nodes |> CompressedArray.set index childNode, values, didWeRemove) 291 | | RemoveChildNode -> struct (nodes |> CompressedArray.unset index, values, didWeRemove) 292 | | RemoveChildNodeAndPreserveSingleValue(promotedNode) -> 293 | struct (nodes |> CompressedArray.unset index, values |> CompressedArray.set index promotedNode, didWeRemove) 294 | | NoChange -> struct (nodes, values, didWeRemove) 295 | else struct (nodes, values, false) 296 | | HashCollisionNode collisions -> 297 | // TODO: This could be further optimised but hash collisions should be unlikely. 298 | let newList = collisions |> List.filter (fun x -> equals (keyExtractor x) k |> not) 299 | let didWeRemove = collisions |> List.exists (fun x -> equals (keyExtractor x) k) 300 | match newList with 301 | | [ entry ] -> struct (nodes |> CompressedArray.unset index, values |> CompressedArray.set index entry, didWeRemove) // Project parent node to hash collision and unset where this node was. 302 | | _ :: _ -> struct (nodes |> CompressedArray.set index (HashCollisionNode(newList)), values, didWeRemove) 303 | | [] -> failwithf "This should never happen; hash collision nodes should always have more than one entry" 304 | 305 | // This decides the new parent node minimally required based on child node end state. 306 | match nodeValueList |> CompressedArray.count, newValuesList |> CompressedArray.count with 307 | | (0, 1) when not first -> struct (RemoveChildNodeAndPreserveSingleValue newValuesList.Content.[0], didWeRemove) 308 | | (0, 1) when first -> struct (NewChildNode (buildTrieNode(nodeValueList, newValuesList)), didWeRemove) // Root node should always be TrieNode 309 | | (0, 0) -> struct (RemoveChildNode, didWeRemove) 310 | | (_, _) when not didWeRemove -> struct (NoChange, didWeRemove) 311 | | (_, _) -> struct (NewChildNode (buildTrieNode(nodeValueList, newValuesList)), didWeRemove) 312 | 313 | // Need to start the chain. This is harder since we need knowledge of two levels at once (current + sublevel) 314 | // This is because deletions on a sub-level can mean we create a different node on the current level. 315 | let changeAndRemovalStatus = traverseNodes true hashMap.RootData.Nodes hashMap.RootData.Entries 0 316 | 317 | match changeAndRemovalStatus with 318 | | struct (NewChildNode (TrieNode newRootNode), true) -> { CurrentCount = hashMap.CurrentCount - 1; RootData = newRootNode; } 319 | | struct (NewChildNode _, true) -> failwithf "Only expect trie nodes at root" 320 | | struct (RemoveChildNode, true) -> { CurrentCount = 0; RootData = { Nodes = CompressedArray.empty; Entries = CompressedArray.empty }; } 321 | | struct (NoChange, false) -> hashMap 322 | | struct (_, false) -> hashMap // If no removal then no change required (unlike Add where replace could occur). 323 | | struct (NoChange, true) -> failwithf "Should never occur" 324 | | struct (RemoveChildNodeAndPreserveSingleValue _, _) -> failwith "Should never occur at root node" 325 | 326 | let public count hashMap = hashMap.CurrentCount 327 | 328 | let public toSeq (hashMap: HashTrieRoot<_>) = 329 | let rec yieldNodes node = seq { 330 | match node with 331 | | TrieNode({Nodes = nodes; Entries = values }) -> 332 | for values in values.Content do yield values 333 | for node in nodes.Content do yield! yieldNodes node 334 | | HashCollisionNode entries -> for entry in entries do yield entry 335 | } 336 | 337 | yieldNodes (TrieNode hashMap.RootData) 338 | 339 | let isEmpty hashMap = hashMap.CurrentCount = 0 340 | 341 | [] 342 | let empty< 'tk, 'eq when 'eq : (new : unit -> 'eq)> : HashTrieRoot< ^tk> = 343 | { CurrentCount = 0; RootData = { Nodes = CompressedArray.empty; Entries = CompressedArray.empty } } 344 | 345 | [] 346 | type private ItemToCheckForEquality<'t> = 347 | | SingleItem of singleItem: 't 348 | | ListOfItems of listOfItems: 't list 349 | 350 | /// Given the right key and extra check function (for value checking above key) we can determine whether the given HashTrie is equal 351 | /// for the specialised implementation above (HashMap or HashSet). This is a O(n) implementation except for HashCollectionNodes. 352 | let inline equals 353 | (eqTemplate: 'teq when 'teq :> IEqualityComparer<_>) 354 | equalKeyExtractor 355 | ([] extraCheck) 356 | ([] hashFunction) 357 | (h1: HashTrieRoot<'n>) 358 | (h2: HashTrieRoot<'n>) = 359 | 360 | // Used only for HashCollision checks. 361 | let customEqComparer = { 362 | new IEqualityComparer<_> with 363 | override _.Equals(x, y) = eqTemplate.Equals(equalKeyExtractor x, equalKeyExtractor y) && extraCheck x y 364 | override _.GetHashCode(obj: _) = hashFunction obj 365 | } 366 | 367 | // Custom recursive that allows us to be O(n) for the equality check mostly, with the exception of the hash check. 368 | let rec equalsSpecificSeq node = seq { 369 | match node with 370 | | TrieNode({Nodes = nodes; Entries = values }) -> 371 | for value in values.Content do yield SingleItem value 372 | for node in nodes.Content do yield! equalsSpecificSeq node 373 | | HashCollisionNode entries -> yield ListOfItems entries 374 | } 375 | 376 | let inline recurse (enumerable1: seq<_>) (enumerable2: seq<_>) = 377 | 378 | use enum1 = enumerable1.GetEnumerator() 379 | use enum2 = enumerable2.GetEnumerator() 380 | 381 | let rec recurseRec() = 382 | let enum1V = enum1.MoveNext() 383 | let _ = enum2.MoveNext() 384 | if enum1V // Then not at end of list. 385 | then 386 | let currentIsEqual = 387 | match enum1.Current, enum2.Current with 388 | | (SingleItem x, SingleItem y) -> customEqComparer.Equals(x, y) 389 | | (ListOfItems x, ListOfItems y) -> System.Linq.Enumerable.Except(x, y, customEqComparer) |> Seq.isEmpty 390 | | _ -> false 391 | if currentIsEqual then recurseRec() else false 392 | else true 393 | 394 | recurseRec() 395 | 396 | h1.CurrentCount = h2.CurrentCount && recurse (equalsSpecificSeq (TrieNode h1.RootData)) (equalsSpecificSeq (TrieNode h2.RootData)) -------------------------------------------------------------------------------- /src/FSharp.HashCollections/HashMap.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections 2 | 3 | open System.Collections 4 | open System.Collections.Generic 5 | open System.Runtime.CompilerServices 6 | open System 7 | 8 | [] 9 | module internal HashMapInternalSettings = 10 | let inline internal keyExtractor (hme: KeyValuePair<_, _>) = hme.Key 11 | let inline internal valueExtractor (hme: KeyValuePair<_, _>) = hme.Value 12 | 13 | /// Immutable hash map. 14 | type [] HashMap<'tk, 'tv, 'teq when 'teq :> IEqualityComparer<'tk>> = 15 | val internal HashTrieRoot: HashTrieRoot> 16 | val internal EqualityComparer: 'teq 17 | 18 | internal new(d, eq) = { HashTrieRoot = d; EqualityComparer = eq } 19 | 20 | // Taken from https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/map.fs#L631-L636 to keep to standard. (MIT License - https://github.com/fsharp/fsharp/blob/master/License.txt) 21 | override this.ToString() = 22 | match List.ofSeq (Seq.truncate 4 this) with 23 | | [] -> "hashMap []" 24 | | [KeyValue h1] -> System.Text.StringBuilder().Append("hashMap [").Append(HelperFunctions.anyToStringShowingNull h1).Append("]").ToString() 25 | | [KeyValue h1;KeyValue h2] -> System.Text.StringBuilder().Append("hashMap [").Append(HelperFunctions.anyToStringShowingNull h1).Append("; ").Append(HelperFunctions.anyToStringShowingNull h2).Append("]").ToString() 26 | | [KeyValue h1;KeyValue h2;KeyValue h3] -> System.Text.StringBuilder().Append("hashMap [").Append(HelperFunctions.anyToStringShowingNull h1).Append("; ").Append(HelperFunctions.anyToStringShowingNull h2).Append("; ").Append(HelperFunctions.anyToStringShowingNull h3).Append("]").ToString() 27 | | KeyValue h1 :: KeyValue h2 :: KeyValue h3 :: _ -> System.Text.StringBuilder().Append("hashMap [").Append(HelperFunctions.anyToStringShowingNull h1).Append("; ").Append(HelperFunctions.anyToStringShowingNull h2).Append("; ").Append(HelperFunctions.anyToStringShowingNull h3).Append("; ... ]").ToString() 28 | 29 | member this.Equals(other: HashMap<'tk, 'tv, 'teq>): bool = 30 | let eqComparer = this.EqualityComparer 31 | HashTrie.equals 32 | this.EqualityComparer 33 | keyExtractor 34 | (fun x y -> Unchecked.equals x.Value y.Value) 35 | (fun o -> HashCode.Combine(eqComparer.GetHashCode(keyExtractor o), Unchecked.hash (valueExtractor o))) 36 | this.HashTrieRoot 37 | other.HashTrieRoot 38 | 39 | override this.Equals(other: obj) = 40 | match other with 41 | | :? HashMap<'tk, 'tv, 'teq> as otherTyped -> this.Equals(otherTyped) 42 | | _ -> false 43 | 44 | interface IEquatable> with member this.Equals(other) = this.Equals(other) 45 | 46 | override this.GetHashCode() = 47 | let inline combineHash x y = (x <<< 1) + y + 631 48 | let mutable res = 0 49 | for x in HashTrie.toSeq this.HashTrieRoot do 50 | res <- combineHash res (this.EqualityComparer.GetHashCode(keyExtractor x)) 51 | // NOTE: This Unchecked.hash could result in perf penalities since it isn't statically determined I believe (inlined to caller site). 52 | // The key isn't affected by this issue since the equality comparer is pre-calc'ed for this. 53 | res <- combineHash res (Unchecked.hash x.Value) 54 | abs res 55 | 56 | member this.GetEnumerator() = (this.HashTrieRoot |> HashTrie.toSeq).GetEnumerator() 57 | interface IEnumerable> with 58 | member this.GetEnumerator() = this.GetEnumerator() 59 | interface IEnumerable with 60 | member this.GetEnumerator() = this.GetEnumerator() :> IEnumerator 61 | 62 | member this.Item key = 63 | let inline tryFind (k: 'tk) (hashMap: HashMap<'tk, 'tv, 'teq>) : 'tv voption = 64 | HashTrie.tryFind keyExtractor valueExtractor hashMap.EqualityComparer k hashMap.HashTrieRoot 65 | 66 | 67 | match this |> tryFind key with | ValueSome(v) -> v | ValueNone -> raise (KeyNotFoundException()) 68 | 69 | interface IReadOnlyDictionary<'tk, 'tv> with 70 | member this.Item with get(key) = this.Item key 71 | member this.Keys = 72 | let this = this 73 | seq { for kvp in this do yield kvp.Key } 74 | member this.TryGetValue(key, value:byref<'tv>) = 75 | let inline tryFind (k: 'tk) (hashMap: HashMap<'tk, 'tv, 'teq>) : 'tv voption = 76 | HashTrie.tryFind keyExtractor valueExtractor hashMap.EqualityComparer k hashMap.HashTrieRoot 77 | match this |> tryFind key with 78 | | ValueSome(v) -> value <- v; true 79 | | ValueNone -> false 80 | member this.Values = 81 | let this = this 82 | seq { for kvp in this do yield kvp.Value } 83 | member this.ContainsKey key = 84 | let inline tryFind (k: 'tk) (hashMap: HashMap<'tk, 'tv, 'teq>) : 'tv voption = 85 | HashTrie.tryFind keyExtractor valueExtractor hashMap.EqualityComparer k hashMap.HashTrieRoot 86 | this |> tryFind key |> ValueOption.isSome 87 | member this.Count = HashTrie.count this.HashTrieRoot 88 | 89 | /// Immutable hash map with default structural comparison. 90 | type HashMap<'tk, 'tv> = HashMap<'tk, 'tv, IEqualityComparer<'tk>> 91 | 92 | module HashMap = 93 | 94 | let inline internal keyExtractor (hme: KeyValuePair<_, _>) = hme.Key 95 | let inline internal valueExtractor (hme: KeyValuePair<_, _>) = hme.Value 96 | 97 | [] 98 | let tryFind (k: 'tk) (hashMap: HashMap<'tk, 'tv, 'teq>) : 'tv voption = 99 | HashTrie.tryFind keyExtractor valueExtractor hashMap.EqualityComparer k hashMap.HashTrieRoot 100 | 101 | [] 102 | let add (k: 'tk) (v: 'tv) (hashMap: HashMap<'tk, 'tv, 'teq>) = 103 | HashMap<_, _, _>( 104 | HashTrie.add keyExtractor hashMap.EqualityComparer (KeyValuePair<_, _>(k, v)) hashMap.HashTrieRoot, 105 | hashMap.EqualityComparer) 106 | 107 | [] 108 | let remove (k: 'tk) (hashMap: HashMap<'tk, 'tv, 'teq>) = 109 | HashMap<_, _, _>( 110 | HashTrie.remove keyExtractor hashMap.EqualityComparer k hashMap.HashTrieRoot, 111 | hashMap.EqualityComparer) 112 | 113 | [] 114 | let count (h: HashMap<_, _, _>) = HashTrie.count h.HashTrieRoot 115 | 116 | let emptyWithComparer<'tk, 'tv, 'teq when 'teq :> IEqualityComparer<'tk> and 'teq : (new : unit -> 'teq)> : HashMap<'tk, 'tv, 'teq> = 117 | HashMap<_, _, _>(HashTrie.empty, new 'teq()) 118 | 119 | let empty<'tk, 'tv when 'tk : equality> : HashMap<'tk, 'tv> = 120 | HashMap<_, _>(HashTrie.empty, HashIdentity.Structural) 121 | 122 | let toSeq (h: HashMap<'tk, 'tv, _>) : (struct ('tk * 'tv) seq) = 123 | seq { for i in h do yield struct (i.Key, i.Value) } 124 | 125 | let isEmpty (h: HashMap<_, _, _>) = h.HashTrieRoot |> HashTrie.isEmpty 126 | 127 | let ofSeq (s: #seq>) : HashMap<'k, 'v> = 128 | let eqComparer = HashIdentity.Structural 129 | HashMap<_, _>( 130 | HashTrie.ofSeq keyExtractor eqComparer s empty.HashTrieRoot, 131 | eqComparer) 132 | 133 | [] 134 | let containsKey k hm = hm |> tryFind k |> ValueOption.isSome 135 | 136 | [] 137 | let keys (hm: HashMap<_, _, _>) = seq { for kvp in hm do yield kvp.Key } 138 | 139 | [] 140 | let values (hm: HashMap<_, _, _>) = seq { for kvp in hm do yield kvp.Value } 141 | 142 | [] 143 | module AlwaysOpenHashMap = 144 | let hashMap (s: #seq<'k * 'v>) = s |> Seq.map KeyValuePair.Create |> HashMap.ofSeq -------------------------------------------------------------------------------- /src/FSharp.HashCollections/HashSet.fs: -------------------------------------------------------------------------------- 1 | namespace rec FSharp.HashCollections 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | open System.Runtime.CompilerServices 7 | open System.Text 8 | 9 | [] 10 | module internal HashSetInternalSettings = 11 | let inline internal keyExtractor i = i 12 | let inline internal valueExtractor i = i 13 | 14 | /// Immutable hash set. 15 | type [] HashSet<'tk, 'teq when 'teq :> IEqualityComparer<'tk>> = 16 | val internal HashTrieRoot: HashTrieRoot<'tk> 17 | val internal EqualityComparer: 'teq 18 | internal new(d, eq) = { HashTrieRoot = d; EqualityComparer = eq } 19 | 20 | // Taken from https://github.com/dotnet/fsharp/blob/main/src/fsharp/FSharp.Core/set.fs#L788-L806 for consistency with idiomatic set. (MIT License - see https://github.com/dotnet/fsharp/blob/main/License.txt) 21 | override this.ToString() = 22 | match List.ofSeq (Seq.truncate 4 this) with 23 | | [] -> "set []" 24 | | [h1] -> 25 | let txt1 = HelperFunctions.anyToStringShowingNull h1 26 | StringBuilder().Append("hashSet [").Append(txt1).Append("]").ToString() 27 | | [h1; h2] -> 28 | let txt1 = HelperFunctions.anyToStringShowingNull h1 29 | let txt2 = HelperFunctions.anyToStringShowingNull h2 30 | StringBuilder().Append("hashSet [").Append(txt1).Append("; ").Append(txt2).Append("]").ToString() 31 | | [h1; h2; h3] -> 32 | let txt1 = HelperFunctions.anyToStringShowingNull h1 33 | let txt2 = HelperFunctions.anyToStringShowingNull h2 34 | let txt3 = HelperFunctions.anyToStringShowingNull h3 35 | StringBuilder().Append("hashSet [").Append(txt1).Append("; ").Append(txt2).Append("; ").Append(txt3).Append("]").ToString() 36 | | h1 :: h2 :: h3 :: _ -> 37 | let txt1 = HelperFunctions.anyToStringShowingNull h1 38 | let txt2 = HelperFunctions.anyToStringShowingNull h2 39 | let txt3 = HelperFunctions.anyToStringShowingNull h3 40 | StringBuilder().Append("hashSet [").Append(txt1).Append("; ").Append(txt2).Append("; ").Append(txt3).Append("; ... ]").ToString() 41 | 42 | member this.Equals(other: HashSet<_, _>) = HashTrie.equals this.EqualityComparer keyExtractor (fun _ _ -> true) this.EqualityComparer.GetHashCode this.HashTrieRoot other.HashTrieRoot 43 | 44 | override this.Equals(other: obj) = match other with | :? HashSet<'tk, 'teq> as otherTyped -> this.Equals(otherTyped) | _ -> false 45 | 46 | interface IEquatable> with member this.Equals(other: HashSet<_, _>) = this.Equals(other) 47 | 48 | override this.GetHashCode() = 49 | // Shamelessly taken from the FSharp Set code for consistency (https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/set.fs#L683-L688) 50 | let inline combineHash x y = (x <<< 1) + y + 631 51 | let mutable res = 0 52 | for x in HashTrie.toSeq this.HashTrieRoot do 53 | res <- combineHash res (this.EqualityComparer.GetHashCode(keyExtractor x)) 54 | abs res 55 | 56 | member this.GetEnumerator() = (this.HashTrieRoot |> HashTrie.toSeq).GetEnumerator() 57 | interface IEnumerable<'tk> with 58 | member this.GetEnumerator() = this.GetEnumerator() 59 | interface IEnumerable with 60 | member this.GetEnumerator() = this.GetEnumerator() :> IEnumerator 61 | 62 | 63 | /// Immutable hash map with default structural comparison. 64 | type HashSet<'tk> = HashSet<'tk, IEqualityComparer<'tk>> 65 | 66 | module HashSet = 67 | 68 | let contains (k: 'tk) (hashSet: HashSet<'tk, 'teq>) : bool = 69 | HashTrie.tryFind keyExtractor valueExtractor hashSet.EqualityComparer k hashSet.HashTrieRoot |> ValueOption.isSome 70 | 71 | let add (k: 'tk) (hashSet: HashSet<'tk, 'teq>) = 72 | HashSet<_, _>(HashTrie.add keyExtractor hashSet.EqualityComparer k hashSet.HashTrieRoot, hashSet.EqualityComparer) 73 | 74 | let remove (k: 'tk) (hashSet: HashSet<'tk, 'teq>) = 75 | HashSet<_, _>(HashTrie.remove keyExtractor hashSet.EqualityComparer k hashSet.HashTrieRoot, hashSet.EqualityComparer) 76 | 77 | let count (h: HashSet<_, _>) = HashTrie.count h.HashTrieRoot 78 | 79 | let emptyWithComparer<'tk, 'teq when 'teq :> System.Collections.Generic.IEqualityComparer<'tk> and 'teq : (new : unit -> 'teq)> : HashSet<'tk, 'teq> = 80 | let eqTemplate = new 'teq() 81 | HashSet<_, _>(HashTrie.empty, eqTemplate) 82 | 83 | let empty<'tk when 'tk : equality> : HashSet<'tk> = 84 | HashSet<'tk>(HashTrie.empty, HashIdentity.Structural) 85 | 86 | let toSeq (h: HashSet<'tk, _>) : 'tk seq = h :> seq<'tk> 87 | 88 | let isEmpty (h: HashSet<_, _>) = h.HashTrieRoot |> HashTrie.isEmpty 89 | 90 | let emptyWithSameComparer (h: HashSet<'tk, 'teq>) : HashSet<'tk, 'teq> = HashSet<'tk, 'teq>(HashTrie.empty, h.EqualityComparer) 91 | 92 | let inline private getSmallerAndLargerSet h1 h2 = if count h1 > count h2 then struct (h2, h1) else struct (h1, h2) 93 | 94 | let intersect (h1: HashSet<'t, 'teq>) (h2: HashSet<'t, 'teq>) = 95 | let struct (smallerSet, largerSet) = getSmallerAndLargerSet h1 h2 96 | let mutable r = emptyWithSameComparer smallerSet 97 | for item in smallerSet |> toSeq do 98 | if largerSet |> contains item 99 | then r <- r |> add item 100 | r 101 | 102 | let fold folder state (h: HashSet<'t, 'teq>) = h |> toSeq |> Seq.fold folder state 103 | 104 | let difference (h1: HashSet<'t, 'teq>) (h2: HashSet<'t, 'teq>) = 105 | h2 |> fold (fun s t -> s |> remove t) h1 106 | 107 | let union (h1: HashSet<'t, 'teq>) (h2: HashSet<'t, 'teq>) = 108 | let struct (smallerSet, largerSet) = getSmallerAndLargerSet h1 h2 109 | smallerSet |> fold (fun s t -> s |> add t) largerSet 110 | 111 | let filter predicate (h: HashSet<'t, 'teq>) = 112 | h |> fold (fun s t -> if predicate t then s |> add t else s) (emptyWithComparer<'t, 'teq>) 113 | 114 | let map mapping (h: HashSet<_, 'teq>) = 115 | h |> fold (fun s t -> s |> add (mapping t)) (emptyWithComparer<_, 'teq>) 116 | 117 | let ofSeq (s: #seq<'k>) : HashSet<'k> = 118 | let eqComparer = HashIdentity.Structural 119 | HashSet<_>(HashTrie.ofSeq keyExtractor eqComparer s empty.HashTrieRoot, eqComparer) 120 | 121 | [] 122 | module AlwaysOpenHashSet = 123 | let hashSet s = HashSet.ofSeq s -------------------------------------------------------------------------------- /src/FSharp.HashCollections/InternalTypes.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections 2 | 3 | open System.Collections.Generic 4 | open System.Runtime.CompilerServices 5 | open System 6 | 7 | type internal TrieNodeContent<'tk> = 8 | { Nodes: CompressedArray> 9 | Entries: CompressedArray<'tk> } 10 | static member inline Empty = { Nodes = CompressedArray.empty; Entries = CompressedArray.empty } 11 | 12 | and [] internal HashTrieNode<'tk> = 13 | | TrieNode of TrieNodeContent<'tk> 14 | | HashCollisionNode of entries: 'tk list 15 | 16 | type [] internal HashTrieRoot<'tnode> = { 17 | CurrentCount: int32 18 | RootData: TrieNodeContent<'tnode> 19 | } 20 | 21 | module internal HelperFunctions = 22 | 23 | // Taken from https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs to be consistent with F# Map handling (MIT License - https://github.com/fsharp/fsharp/blob/master/License.txt) 24 | let inline anyToString nullStr x = 25 | match box x with 26 | | null -> nullStr 27 | | :? System.IFormattable as f -> f.ToString(null,System.Globalization.CultureInfo.InvariantCulture) 28 | | obj -> obj.ToString() 29 | 30 | let anyToStringShowingNull x = anyToString "null" x -------------------------------------------------------------------------------- /src/FSharp.HashCollections/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Benchmarks/AddBenchmark.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections.Benchmarks 2 | 3 | open System 4 | open BenchmarkDotNet.Attributes 5 | open System.Collections.Generic 6 | 7 | module private AddBenchmarkConstants = 8 | let [] OperationsPerInvoke = 50 9 | open AddBenchmarkConstants 10 | 11 | open FSharp.HashCollections 12 | open FSharpx.Collections.Experimental 13 | open FSharpx.Collections 14 | 15 | type AddBenchmark() = 16 | 17 | let mutable hashMap = HashMap.empty 18 | let mutable fsharpMap = Map.empty 19 | let mutable fsharpDataAdaptiveMap = FSharp.Data.Adaptive.HashMap.Empty 20 | let mutable fsharpXHashMap = FSharpx.Collections.PersistentHashMap.empty 21 | let mutable systemImmutableMap = System.Collections.Immutable.ImmutableDictionary.Empty 22 | let mutable fsharpXChampMap = FSharpx.Collections.Experimental.ChampHashMap() 23 | let mutable languageExtMap = LanguageExt.HashMap.Empty 24 | let mutable preppedData = Array.zeroCreate 0 25 | 26 | let elementsToAdd = 27 | let a = Array.zeroCreate OperationsPerInvoke 28 | let r = Random() 29 | for i = 0 to a.Length - 1 do 30 | a.[i] <- KeyValuePair<_, _>(r.Next(), r.Next()) 31 | a 32 | 33 | [] 34 | member val public CollectionSize = 0 with get, set 35 | 36 | member this.PrepData() = 37 | if preppedData.Length <> this.CollectionSize 38 | then 39 | let r = Random() 40 | preppedData <- Array.zeroCreate this.CollectionSize 41 | 42 | let mutable c = 0 43 | let mutable s = Set.empty 44 | while c < preppedData.Length do 45 | let i = r.Next() 46 | if s |> Set.contains i 47 | then () 48 | else 49 | s <- s |> Set.add i 50 | c <- c + 1 51 | 52 | if s.Count <> this.CollectionSize then failwithf "Bug in startup data generation" 53 | use e = (Set.toSeq s).GetEnumerator() 54 | for i = 0 to preppedData.Length - 1 do 55 | e.MoveNext() |> ignore 56 | preppedData.[i] <- KeyValuePair<_, _>(e.Current, e.Current) 57 | 58 | [] 59 | member this.SetupAddHashMap() = 60 | this.PrepData() 61 | hashMap <- HashMap.ofSeq preppedData 62 | if hashMap |> HashMap.count <> this.CollectionSize then failwithf "Not properly initialised" 63 | 64 | [] 65 | member this.AddHashMap() = 66 | let mutable hashMap = hashMap 67 | for i in elementsToAdd do hashMap <- hashMap |> HashMap.add i.Key i.Value 68 | 69 | [] 70 | member this.SetupAddToFSharpMap() = 71 | this.PrepData() 72 | fsharpMap <- preppedData |> Seq.fold (fun s (KeyValue(k, v)) -> s |> Map.add k v) Map.empty 73 | if fsharpMap.Count <> this.CollectionSize then failwithf "Not properly initialised" 74 | 75 | [] 76 | member this.AddToFSharpMap() = 77 | let mutable fsharpMap = fsharpMap 78 | for i in elementsToAdd do fsharpMap <- fsharpMap |> Map.add i.Key i.Value 79 | 80 | [] 81 | member this.SetupAddToFSharpAdaptiveMap() = 82 | this.PrepData() 83 | this.OfSeqFSharpAdaptiveMap() 84 | if fsharpDataAdaptiveMap.Count <> this.CollectionSize then failwithf "Not properly initialised" 85 | 86 | [] 87 | member this.AddToFSharpAdaptiveMap() = 88 | let mutable fsharpDataAdaptiveMap = fsharpDataAdaptiveMap 89 | for i in elementsToAdd do 90 | fsharpDataAdaptiveMap <- fsharpDataAdaptiveMap.Add(i.Key, i.Value) 91 | 92 | [] 93 | member this.SetupAddToFSharpXMap() = 94 | this.PrepData() 95 | this.OfSeqFSharpXMap() 96 | if fsharpXHashMap.Count <> this.CollectionSize then failwithf "Not properly initialised" 97 | 98 | [] 99 | member this.AddToFSharpXMap() = 100 | let mutable fsharpXHashMap = fsharpXHashMap 101 | for i in elementsToAdd do fsharpXHashMap <- fsharpXHashMap |> PersistentHashMap.add i.Key i.Value 102 | 103 | [] 104 | member this.SetupAddToSystemCollectionsImmutableMap() = 105 | this.PrepData() 106 | this.OfSeqSystemCollectionsImmutableMap() 107 | if systemImmutableMap.Count <> this.CollectionSize then failwithf "Not properly initialised" 108 | 109 | [] 110 | member this.AddToSystemCollectionsImmutableMap() = 111 | let mutable systemImmutableMap = systemImmutableMap 112 | for i in elementsToAdd do systemImmutableMap <- systemImmutableMap.SetItem(i.Key, i.Value) 113 | 114 | [] 115 | member this.SetupAddToFsharpxChampMap() = 116 | this.PrepData() 117 | this.OfSeqFsharpxChampMap() 118 | if fsharpXChampMap.Count <> this.CollectionSize then failwithf "Not properly initialised" 119 | 120 | [] 121 | member this.AddToFsharpxChampMap() = 122 | let mutable fsharpXChampMap = fsharpXChampMap 123 | for i in elementsToAdd do fsharpXChampMap <- ChampHashMap.add fsharpXChampMap i.Key i.Value 124 | 125 | [] 126 | member this.SetupAddToLangExtMap() = 127 | this.PrepData() 128 | this.OfSeqLangExtMap() 129 | if languageExtMap.Count <> this.CollectionSize then failwithf "Not properly initialised" 130 | 131 | [] 132 | member this.AddToLangExtMap() = 133 | let mutable langExtMap = languageExtMap 134 | for i in elementsToAdd do 135 | langExtMap <- langExtMap.AddOrUpdate(i.Key, i.Value) 136 | 137 | // OfSeq helpers 138 | member this.OfSeqHashMap() = hashMap <- HashMap.ofSeq preppedData 139 | 140 | member this.OfSeqFSharpXMap() = 141 | fsharpXHashMap <- FSharpx.Collections.PersistentHashMap.ofSeq (preppedData |> Seq.map (fun (KeyValue(kv)) -> kv)) 142 | 143 | member this.OfSeqFSharpAdaptiveMap() = 144 | fsharpDataAdaptiveMap <- FSharp.Data.Adaptive.HashMap.ofSeq (preppedData |> Seq.map (fun (KeyValue(kv)) -> kv)) 145 | 146 | member this.OfSeqSystemCollectionsImmutableMap() = systemImmutableMap <- System.Collections.Immutable.ImmutableDictionary.CreateRange(preppedData) 147 | 148 | member this.OfSeqFsharpxChampMap() = fsharpXChampMap <- preppedData |> FSharpx.Collections.Experimental.ChampHashMap.ofSeq (fun x -> x.Key) (fun x -> x.Value) 149 | 150 | member this.OfSeqLangExtMap() = languageExtMap <- languageExtMap.AddRange preppedData -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Benchmarks/BenchmarkProgram.fs: -------------------------------------------------------------------------------- 1 | module FSharp.HashCollections.Benchmarks.Program 2 | 3 | open BenchmarkDotNet.Running 4 | 5 | [] 6 | let main argv = 7 | let summary = BenchmarkSwitcher.FromAssembly(typeof.Assembly).Run(argv) 8 | 0 9 | 10 | -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Benchmarks/FSharp.HashCollections.Benchmarks.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Benchmarks/OfSeqBenchmark.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections.Benchmarks 2 | 3 | open System 4 | open FSharp.HashCollections 5 | open BenchmarkDotNet.Attributes 6 | open BenchmarkDotNet.Running 7 | open FSharpx.Collections 8 | open System.Collections.Generic 9 | open ImTools 10 | 11 | type OfSeqBenchmark() = 12 | 13 | let mutable hashMap = FSharp.HashCollections.HashMap.empty 14 | let mutable fsharpMap = Map.empty 15 | let mutable fsharpDataAdaptiveMap = FSharp.Data.Adaptive.HashMap.Empty 16 | let mutable fsharpXHashMap = FSharpx.Collections.PersistentHashMap.empty 17 | let mutable systemImmutableMap = System.Collections.Immutable.ImmutableDictionary.Empty 18 | let mutable fsharpXChampMap = FSharpx.Collections.Experimental.ChampHashMap() 19 | let mutable preppedData = Array.zeroCreate 0 20 | 21 | [] 22 | member val public CollectionSize = 0 with get, set 23 | 24 | member this.PrepData() = 25 | if preppedData.Length <> this.CollectionSize 26 | then 27 | let r = Random() 28 | preppedData <- Array.zeroCreate this.CollectionSize 29 | 30 | let mutable c = 0 31 | let mutable s = Set.empty 32 | while c < preppedData.Length do 33 | let i = r.Next() 34 | if s |> Set.contains i 35 | then () 36 | else 37 | s <- s |> Set.add i 38 | c <- c + 1 39 | 40 | if s.Count <> this.CollectionSize then failwithf "Bug in startup data generation" 41 | use e = (Set.toSeq s).GetEnumerator() 42 | for i = 0 to preppedData.Length - 1 do 43 | e.MoveNext() |> ignore 44 | preppedData.[i] <- KeyValuePair<_, _>(e.Current, e.Current) 45 | 46 | [] 47 | member this.OfSeqSetup() = this.PrepData() 48 | 49 | [] 50 | member this.OfSeqHashMap() = hashMap <- FSharp.HashCollections.HashMap.ofSeq preppedData 51 | 52 | [] 53 | member this.OfSeqFSharpXMap() = 54 | fsharpXHashMap <- FSharpx.Collections.PersistentHashMap.ofSeq (preppedData |> Seq.map (fun (KeyValue(kv)) -> kv)) 55 | 56 | [] 57 | member this.OfSeqFSharpAdaptiveMap() = 58 | fsharpDataAdaptiveMap <- FSharp.Data.Adaptive.HashMap.ofSeq (preppedData |> Seq.map (fun (KeyValue(kv)) -> kv)) 59 | 60 | [] 61 | member this.OfSeqSystemCollectionsImmutableMap() = systemImmutableMap <- System.Collections.Immutable.ImmutableDictionary.CreateRange(preppedData) 62 | 63 | [] 64 | member this.OfSeqFSharpxChampMap() = fsharpXChampMap <- preppedData |> FSharpx.Collections.Experimental.ChampHashMap.ofSeq (fun x -> x.Key) (fun x -> x.Value) 65 | 66 | [] 67 | member this.OfSeqLangExtMap() = LanguageExt.HashMap.Empty.AddRange(preppedData) -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Benchmarks/ReadBenchmark.fs: -------------------------------------------------------------------------------- 1 | namespace FSharp.HashCollections.Benchmarks 2 | 3 | open BenchmarkDotNet.Attributes 4 | 5 | module private ReadBenchmarkConstants = 6 | let [] OperationsPerInvokeInt = 100000 7 | open ReadBenchmarkConstants 8 | 9 | open FSharp.HashCollections 10 | open FSharpx.Collections.Experimental 11 | 12 | type ReadBenchmarks() = 13 | 14 | let mutable hashMapData = HashMap.empty 15 | let mutable imToolsMap = ImTools.ImHashMap.Empty 16 | let mutable fsharpMapData = Map.empty 17 | let mutable fsharpXHashMap = FSharpx.Collections.PersistentHashMap.empty 18 | let mutable systemImmutableMap = System.Collections.Immutable.ImmutableDictionary.Empty 19 | let mutable fsharpDataAdaptiveMap = FSharp.Data.Adaptive.HashMap.Empty 20 | let mutable fsharpXChampMap: FSharpx.Collections.Experimental.ChampHashMap<_, _> = FSharpx.Collections.Experimental.ChampHashMap() 21 | let mutable languageExtMap = LanguageExt.HashMap.Empty 22 | 23 | let mutable keyToLookup = Array.zeroCreate OperationsPerInvokeInt 24 | let mutable dummyBufferVOption = Array.zeroCreate OperationsPerInvokeInt 25 | let mutable dummyBufferOption = Array.zeroCreate OperationsPerInvokeInt 26 | let mutable dummyBufferNoOption = Array.zeroCreate OperationsPerInvokeInt 27 | let randomGen = System.Random() 28 | 29 | [] 30 | member val public CollectionSize = 0 with get, set 31 | 32 | member this.SetupKeyToLookup() = 33 | for i = 0 to keyToLookup.Length - 1 do 34 | keyToLookup.[i] <- randomGen.Next(0, this.CollectionSize) 35 | 36 | [] 37 | member this.SetupHashMapData() = 38 | hashMapData <- FSharp.HashCollections.HashMap.empty 39 | for i = 0 to this.CollectionSize - 1 do 40 | hashMapData <- hashMapData |> HashMap.add i i 41 | this.SetupKeyToLookup() 42 | 43 | [] 44 | member this.SetupImToolsHashMapData() = 45 | imToolsMap <- ImTools.ImHashMap.Empty 46 | for i = 0 to this.CollectionSize - 1 do 47 | imToolsMap <- imToolsMap.AddOrUpdate(i, i) 48 | this.SetupKeyToLookup() 49 | 50 | [] 51 | member this.SetupFSharpMapData() = 52 | fsharpMapData <- Map.empty 53 | for i = 0 to this.CollectionSize - 1 do 54 | fsharpMapData <- fsharpMapData |> Map.add i i 55 | this.SetupKeyToLookup() 56 | 57 | [] 58 | member this.SetupFSharpXMapData() = 59 | fsharpXHashMap <- FSharpx.Collections.PersistentHashMap.empty 60 | for i = 0 to this.CollectionSize - 1 do 61 | fsharpXHashMap <- fsharpXHashMap.Add(i, i) 62 | this.SetupKeyToLookup() 63 | 64 | [] 65 | member this.SetupSystemCollectionsImmutableMapData() = 66 | systemImmutableMap <- System.Collections.Immutable.ImmutableDictionary.Empty 67 | for i = 0 to this.CollectionSize - 1 do 68 | systemImmutableMap <- systemImmutableMap.Add(i, i) 69 | this.SetupKeyToLookup() 70 | 71 | [] 72 | member this.SetupFSharpDataAdaptiveMapData() = 73 | fsharpDataAdaptiveMap <- FSharp.Data.Adaptive.HashMap.Empty 74 | for i = 0 to this.CollectionSize - 1 do 75 | fsharpDataAdaptiveMap <- fsharpDataAdaptiveMap.Add(i, i) 76 | this.SetupKeyToLookup() 77 | 78 | [] 79 | member this.SetupFSharpXChampMap() = 80 | fsharpXChampMap <- FSharpx.Collections.Experimental.ChampHashMap() 81 | for i = 0 to this.CollectionSize - 1 do 82 | fsharpXChampMap <- ChampHashMap.add fsharpXChampMap i i 83 | this.SetupKeyToLookup() 84 | 85 | [] 86 | member this.SetupLangExtMap() = 87 | languageExtMap <- LanguageExt.HashMap.Empty 88 | for i = 0 to this.CollectionSize - 1 do 89 | languageExtMap <- languageExtMap.Add(i, i) 90 | this.SetupKeyToLookup() 91 | 92 | [] 93 | member _.GetHashMap() = 94 | let mutable i = 0 95 | for k in keyToLookup do 96 | dummyBufferVOption.[i] <- hashMapData |> FSharp.HashCollections.HashMap.tryFind k 97 | i <- i + 1 98 | 99 | [] 100 | member _.GetImToolsHashMap() = 101 | let mutable i = 0 102 | for k in keyToLookup do 103 | match imToolsMap.TryFind(k) with 104 | | (true, x) -> dummyBufferNoOption.[i] <- x 105 | | _ -> () 106 | i <- i + 1 107 | 108 | [] 109 | member _.GetFSharpMap() = 110 | let mutable i = 0 111 | for k in keyToLookup do 112 | dummyBufferOption.[i] <- fsharpMapData |> Map.tryFind k 113 | i <- i + 1 114 | 115 | [] 116 | member _.GetFSharpXHashMap() = 117 | let mutable i = 0 118 | for k in keyToLookup do 119 | dummyBufferNoOption.[i] <- fsharpXHashMap |> FSharpx.Collections.PersistentHashMap.find k 120 | i <- i + 1 121 | 122 | [] 123 | member _.GetSystemCollectionsImmutableMap() = 124 | let mutable i = 0 125 | for k in keyToLookup do 126 | match systemImmutableMap.TryGetValue(k) with 127 | | (true, x) -> dummyBufferNoOption.[i] <- x 128 | | _ -> () 129 | i <- i + 1 130 | 131 | [] 132 | member _.GetFSharpDataAdaptiveMap() = 133 | let mutable i = 0 134 | for k in keyToLookup do 135 | dummyBufferVOption.[i] <- fsharpDataAdaptiveMap |> FSharp.Data.Adaptive.HashMap.tryFindV k 136 | i <- i + 1 137 | 138 | [] 139 | member _.GetFSharpxChampMap() = 140 | let mutable i = 0 141 | for k in keyToLookup do 142 | dummyBufferOption.[i] <- FSharpx.Collections.Experimental.ChampHashMap.tryGetValue fsharpXChampMap k 143 | i <- i + 1 144 | 145 | [] 146 | member _.GetLangExtMap() = 147 | let mutable i = 0 148 | for k in keyToLookup do 149 | dummyBufferNoOption.[i] <- languageExtMap.[k] 150 | i <- i + 1 -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Benchmarks/paket.references: -------------------------------------------------------------------------------- 1 | BenchmarkDotNet 2 | FSharp.Data.Adaptive 3 | FSharpX.Collections 4 | FSharpx.Collections.Experimental 5 | ImTools.dll 6 | LanguageExt.FSharp 7 | System.Collections.Immutable 8 | FSharp.Core -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Unit/FSharp.HashCollections.Unit.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Unit/HashMapTest.fs: -------------------------------------------------------------------------------- 1 | module FSharp.HashCollections.Unit.HashTrieTest 2 | 3 | open Expecto 4 | open FsCheck 5 | open FSharp.HashCollections 6 | open System.Collections.Generic 7 | 8 | // List of actions to generate 9 | type KvAction<'k, 'v> = 10 | | Add of k: 'k * v: 'v 11 | | Remove of k: 'k 12 | 13 | let inline mapAndHashTrieAreTheSameAfterActions (actions: KvAction<'tk, 'tv> list) = 14 | 15 | let mutable mapToTest = Map.empty 16 | let mutable hashTrieToTest = HashMap.empty 17 | 18 | for action in actions do 19 | match action with 20 | | Add(k, v) -> 21 | mapToTest <- mapToTest |> Map.add k v 22 | hashTrieToTest <- hashTrieToTest |> HashMap.add k v 23 | | Remove k -> 24 | mapToTest <- mapToTest |> Map.remove k 25 | hashTrieToTest <- hashTrieToTest |> HashMap.remove k 26 | Expect.equal 27 | (hashTrieToTest |> HashMap.toSeq |> set) 28 | (mapToTest |> Map.toSeq |> Seq.map (fun (x, y) -> struct (x, y)) |> set) 29 | "Hash Trie and Map don't contain same data" 30 | 31 | let generateHashMapFromActions (actions: KvAction<'tk, 'tv> list) = 32 | let mutable hashTrieToTest = HashMap.empty 33 | 34 | for action in actions do 35 | match action with 36 | | Add(k, v) -> hashTrieToTest <- hashTrieToTest |> HashMap.add k v 37 | | Remove k -> hashTrieToTest <- hashTrieToTest |> HashMap.remove k 38 | hashTrieToTest 39 | 40 | let toVOption i = match i with | Some(x) -> ValueSome x | None -> ValueNone 41 | 42 | let inline mapAndHashTrieHaveSameGetValue (actions: KvAction<'tk, 'tv> list) = 43 | 44 | let mutable mapToTest = Map.empty 45 | let mutable hashTrieToTest = HashMap.empty 46 | 47 | for action in actions do 48 | let mutable key = Unchecked.defaultof<'tk> 49 | match action with 50 | | Add(k, v) -> 51 | mapToTest <- mapToTest |> Map.add k v 52 | hashTrieToTest <- hashTrieToTest |> HashMap.add k v 53 | key <- k 54 | | Remove k -> 55 | mapToTest <- mapToTest |> Map.remove k 56 | hashTrieToTest <- hashTrieToTest |> HashMap.remove k 57 | key <- k 58 | let mapResult = mapToTest |> Map.tryFind key |> toVOption 59 | let hashTrieResult = hashTrieToTest |> HashMap.tryFind key 60 | Expect.equal hashTrieResult mapResult "Key update did not hold" 61 | 62 | let inline mapAndHashMapHaveSameCountAtAllTimes (actions: KvAction<'tk, 'tv> list) = 63 | let mutable mapToTest = Map.empty 64 | let mutable hashTrieToTest = HashMap.empty 65 | 66 | for action in actions do 67 | let mutable key = Unchecked.defaultof<'tk> 68 | match action with 69 | | Add(k, v) -> 70 | mapToTest <- mapToTest |> Map.add k v 71 | hashTrieToTest <- hashTrieToTest |> HashMap.add k v 72 | key <- k 73 | | Remove k -> 74 | mapToTest <- mapToTest |> Map.remove k 75 | hashTrieToTest <- hashTrieToTest |> HashMap.remove k 76 | key <- k 77 | let mapResult = mapToTest |> Map.count 78 | let hashTrieResult = hashTrieToTest |> HashMap.count 79 | Expect.equal hashTrieResult mapResult "Count isn't the same" 80 | 81 | let largeMapAddAllThenRemoveAllIsEmpty() = 82 | testCase 83 | "Large map test then remove all results in empty" 84 | (fun () -> 85 | let testDataSize = 5000000 86 | let testData = Array.init testDataSize id 87 | let mutable resultMap = testData |> Array.fold (fun s t -> s |> HashMap.add t t) HashMap.empty 88 | let resultSeq = resultMap |> HashMap.toSeq |> Seq.map (fun struct (k, v) -> k) |> Seq.toArray |> Array.sort 89 | 90 | Expect.equal resultSeq testData "Array data not the same" 91 | Expect.equal testDataSize (HashMap.count resultMap) "Map not empty" 92 | Expect.equal (resultMap |> HashMap.tryFind (testDataSize / 2)) (ValueSome (testDataSize / 2)) "Value not found" 93 | Expect.equal (resultMap |> HashMap.tryFind 1) (ValueSome 1) "Value not found (1)" 94 | Expect.equal (resultMap |> HashMap.tryFind (testDataSize - 1)) (ValueSome (testDataSize - 1)) "Value not found (Last Value)" 95 | 96 | for k in testData do resultMap <- resultMap |> HashMap.remove k 97 | 98 | Expect.equal 0 (HashMap.count resultMap) "Map not empty" 99 | Expect.equal (resultMap |> HashMap.tryFind (testDataSize / 2)) ValueNone "Value still found" 100 | Expect.equal (resultMap |> HashMap.tryFind 1) ValueNone "Value still found (1)" 101 | Expect.equal (resultMap |> HashMap.tryFind (5000000 - 1)) ValueNone "Value still found (Last Value)" 102 | Expect.equal resultMap HashMap.empty "HashMap should be empty" 103 | ) 104 | 105 | let buildPropertyTest testName (testFunction: KvAction list -> _) = 106 | let config = { Config.QuickThrowOnFailure with StartSize = 0; EndSize = 100000; MaxTest = 100 } 107 | testCase testName <| fun () -> Check.One(config, testFunction) 108 | 109 | let generateLargeSizeMapTest() = 110 | testCase 111 | "Large map test of more than one depth" 112 | (fun () -> 113 | let testData = Array.init 100000 id 114 | let result = testData |> Array.fold (fun s t -> s |> HashMap.add t t) HashMap.empty 115 | for i = 0 to testData.Length - 1 do 116 | let testLookup = result |> HashMap.tryFind i 117 | Expect.equal testLookup (ValueSome i) "Not equal to what's expected") 118 | 119 | let generateLargeSizeMapOfSeqTest() = 120 | testCase 121 | "Large map test of more than one depth can be converted to sequence" 122 | (fun () -> 123 | let testData = Array.init 100000 id 124 | let resultMap = testData |> Array.fold (fun s t -> s |> HashMap.add t t) HashMap.empty 125 | let resultSeq = resultMap |> HashMap.toSeq |> Seq.map (fun struct (k, v) -> k) |> Seq.toArray |> Array.sort 126 | Expect.equal resultSeq testData "Array data not the same") 127 | 128 | let assertEqualsTheSame actions = 129 | let hashMap = generateHashMapFromActions actions 130 | let freshMap = hashMap |> HashMap.toSeq |> Seq.map (fun struct (k, v) -> KeyValuePair.Create(k, v)) |> HashMap.ofSeq 131 | Expect.equal (hashMap.GetHashCode()) (freshMap.GetHashCode()) "Hash codes not equal" 132 | Expect.equal hashMap freshMap "Map equality not working" 133 | 134 | let [] tests = 135 | testList 136 | "Hash Map Property Tests" 137 | [ 138 | testCase 139 | "Adding 3 k-v pairs" 140 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add (32u,0L); Add (1u,0L); Add (0u,0L) ]) 141 | 142 | testCase 143 | "Adding another close approximate 3 kv-pairs with a hash collision from 0 and -1 keys" 144 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add (1L,0); Add (-1L,0); Add (0L,0) ]) 145 | 146 | testCase 147 | "Adding another close approximate 3 kv-pairs with a hash collision from 0 and -1 keys and then removing one of them" 148 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add (1L,0); Add (-1L,0); Add (0L,0); Remove 0L ]) 149 | 150 | testCase 151 | "Map contains keys of the same hash (Hash = 0 for both 0 and -1" 152 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add(0L, 5); Add(-1L, 6) ] ) 153 | 154 | testCase 155 | "Hash collision node in tree; then one is removed with a collision" 156 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add (1L,0); Add (-1L,0); Add (0L,0); Remove 0L ]) 157 | 158 | testCase 159 | "Add and remove value with same hash" 160 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add (0L,0); Remove 1L ]) 161 | 162 | testCase 163 | "Add and remove same key" 164 | (fun () -> mapAndHashTrieAreTheSameAfterActions [ Add (1L,0); Remove 1L] ) 165 | 166 | testCase 167 | "Empty Map returns IsEmpty" 168 | (fun () -> Expect.isTrue (HashMap.isEmpty (HashMap.empty |> HashMap.add 1 1 |> HashMap.remove 1)) "HashSet not empty") 169 | 170 | testCase 171 | "Not equal map by values returns not equal" 172 | (fun () -> Expect.notEqual (HashMap.empty |> HashMap.add 1 1 |> HashMap.add 2 1) (HashMap.empty |> HashMap.add 1 2 |> HashMap.add 2 2) "Equal Hash maps when shouldn't be.") 173 | 174 | testCase 175 | "Equal map by values returns equal" 176 | (fun () -> Expect.equal (HashMap.empty |> HashMap.add 1 1 |> HashMap.add 2 2) (HashMap.empty |> HashMap.add 1 1 |> HashMap.add 2 2) "Not Equal Hash maps when should be.") 177 | 178 | testCase 179 | "Nested map by values returns equal" 180 | (fun () -> 181 | let buildHashMap() = hashMap [ (1, hashMap [ (2, 3) ]); (4, hashMap [ (5, 6) ]) ] 182 | Expect.equal (buildHashMap()) (buildHashMap()) "Not equal when should be") 183 | 184 | testCase 185 | "HashMap should be equal despite hash collisions being inserted in different order" 186 | (fun () -> 187 | let set1 = HashMap.empty |> HashMap.add 1UL 8 |> HashMap.add 0x200000003UL 9 188 | let set2 = HashMap.empty |> HashMap.add 0x200000003UL 9 |> HashMap.add 1UL 8 189 | Expect.equal set1 set2 "Sets not equal when they should be") 190 | 191 | testCase 192 | "HashMap should be not be equal despite having keys with a hash collision due to different values" 193 | (fun () -> 194 | let set1 = HashMap.empty |> HashMap.add 1UL 9 |> HashMap.add 0x200000003UL 8 195 | let set2 = HashMap.empty |> HashMap.add 0x200000003UL 9 |> HashMap.add 1UL 8 196 | Expect.notEqual set1 set2 "Sets equal when they should not be") 197 | 198 | testCase 199 | "Nested map not equal by values returns not equal" 200 | (fun () -> 201 | let buildHashMap v = hashMap [ (1, hashMap [ (2, v) ]); (4, hashMap [ (5, 6) ]) ] 202 | Expect.notEqual (buildHashMap 5) (buildHashMap 7) "Not equal when should be") 203 | 204 | testCase 205 | "ToString output is expected" 206 | (fun () -> 207 | let testHashMap = hashMap [ (1, hashMap [ (3, 4)]); (5, hashMap [(6, 7)] ) ] 208 | Expect.equal 209 | (testHashMap.ToString()) 210 | "hashMap [(1, hashMap [(3, 4)]); (5, hashMap [(6, 7)])]" 211 | "toString not valid" ) 212 | 213 | generateLargeSizeMapOfSeqTest() 214 | 215 | generateLargeSizeMapTest() 216 | 217 | largeMapAddAllThenRemoveAllIsEmpty() 218 | 219 | buildPropertyTest 220 | "Map and HashTrie behave the same on Add and Remove" 221 | mapAndHashTrieAreTheSameAfterActions 222 | 223 | buildPropertyTest 224 | "Map and HashTrie always have the same Get result" 225 | mapAndHashTrieHaveSameGetValue 226 | 227 | buildPropertyTest 228 | "Map and HashTrie always have the same Count result" 229 | mapAndHashMapHaveSameCountAtAllTimes 230 | 231 | buildPropertyTest 232 | "Equals from actions and fresh map the same" 233 | assertEqualsTheSame 234 | ] -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Unit/HashSetTest.fs: -------------------------------------------------------------------------------- 1 | module FSharp.HashCollections.HashSetTest 2 | 3 | open Expecto 4 | open FsCheck 5 | open FSharp.HashCollections 6 | open System 7 | open System.Collections.Generic 8 | 9 | // List of actions to generate 10 | type SetAction<'k> = 11 | | Add of k: 'k 12 | | Remove of k: 'k 13 | 14 | let generateHashSetFromActions (actions: SetAction<'tk> list) = 15 | let mutable hashTrieToTest = HashSet.empty 16 | 17 | for action in actions do 18 | match action with 19 | | Add(k) -> hashTrieToTest <- hashTrieToTest |> HashSet.add k 20 | | Remove k -> hashTrieToTest <- hashTrieToTest |> HashSet.remove k 21 | 22 | hashTrieToTest 23 | 24 | let generateSetFromActions (actions: SetAction<'tk> list) = 25 | let mutable s = Set.empty 26 | 27 | for action in actions do 28 | match action with 29 | | Add(k) -> s <- s |> Set.add k 30 | | Remove k -> s <- s |> Set.remove k 31 | 32 | s 33 | 34 | let inline setAndHashSetAreTheSameAfterActions (actions: SetAction<'tk> list) = 35 | 36 | let mutable mapToTest = Set.empty 37 | let mutable hashTrieToTest = HashSet.empty 38 | 39 | for action in actions do 40 | match action with 41 | | Add(k) -> 42 | mapToTest <- mapToTest |> Set.add k 43 | hashTrieToTest <- hashTrieToTest |> HashSet.add k 44 | | Remove k -> 45 | mapToTest <- mapToTest |> Set.remove k 46 | hashTrieToTest <- hashTrieToTest |> HashSet.remove k 47 | Expect.equal 48 | (hashTrieToTest |> HashSet.toSeq |> set) 49 | (mapToTest |> Set.toSeq |> set) 50 | "Hash Trie and Map don't contain same data" 51 | 52 | let inline setAndHashSetHaveSameContainsValue (actions: SetAction<'tk> list) = 53 | 54 | let mutable mapToTest = Set.empty 55 | let mutable hashTrieToTest = HashSet.empty 56 | 57 | for action in actions do 58 | let mutable key = Unchecked.defaultof<'tk> 59 | match action with 60 | | Add(k) -> 61 | mapToTest <- mapToTest |> Set.add k 62 | hashTrieToTest <- hashTrieToTest |> HashSet.add k 63 | key <- k 64 | | Remove k -> 65 | mapToTest <- mapToTest |> Set.remove k 66 | hashTrieToTest <- hashTrieToTest |> HashSet.remove k 67 | key <- k 68 | let mapResult = mapToTest |> Set.contains key 69 | let hashTrieResult = hashTrieToTest |> HashSet.contains key 70 | Expect.equal hashTrieResult mapResult "Key update did not hold" 71 | 72 | let inline setAndHashSetHaveSameCountAtAllTimes (actions: SetAction<'tk> list) = 73 | let mutable mapToTest = Set.empty 74 | let mutable hashTrieToTest = HashSet.empty 75 | 76 | for action in actions do 77 | let mutable key = Unchecked.defaultof<'tk> 78 | match action with 79 | | Add(k) -> 80 | mapToTest <- mapToTest |> Set.add k 81 | hashTrieToTest <- hashTrieToTest |> HashSet.add k 82 | key <- k 83 | | Remove k -> 84 | mapToTest <- mapToTest |> Set.remove k 85 | hashTrieToTest <- hashTrieToTest |> HashSet.remove k 86 | key <- k 87 | let mapResult = mapToTest |> Set.count 88 | let hashTrieResult = hashTrieToTest |> HashSet.count 89 | Expect.equal hashTrieResult mapResult "Count isn't equal" 90 | 91 | let buildPropertyTest testName (testFunction: SetAction list -> _) = 92 | let config = { Config.QuickThrowOnFailure with StartSize = 0; EndSize = 100000; MaxTest = 100 } 93 | testCase testName <| fun () -> Check.One(config, testFunction) 94 | 95 | let buildGenericPropertyTest testName (testFunction: _ -> _) = 96 | let config = { Config.QuickThrowOnFailure with StartSize = 0; EndSize = 100000; MaxTest = 100 } 97 | testCase testName <| fun () -> Check.One(config, testFunction) 98 | 99 | let inline generateLargeSizeMapTest() = 100 | testCase 101 | "Large set test of more than one depth" 102 | (fun () -> 103 | let testData = Array.init 100000 id 104 | let result = testData |> Array.fold (fun s t -> s |> HashSet.add t) HashSet.empty 105 | for i = 0 to testData.Length - 1 do 106 | let testLookup = result |> HashSet.contains i 107 | Expect.isTrue testLookup "Data not in set as expected") 108 | 109 | let generateLargeSizeMapOfSeqTest() = 110 | testCase 111 | "Large set test of more than one depth can be converted to sequence" 112 | (fun () -> 113 | let testData = Array.init 100000 id 114 | let resultSet = testData |> Array.fold (fun s t -> s |> HashSet.add t) HashSet.empty 115 | let resultSeq = resultSet |> HashSet.toSeq |> Seq.toArray |> Array.sort 116 | Expect.equal resultSeq testData "Array data not the same") 117 | 118 | let largeSetAddAllThenRemoveAllIsEmpty() = 119 | testCase 120 | "Large set test then remove all results in empty" 121 | (fun () -> 122 | let testDataSize = 5000000 123 | let testData = Array.init testDataSize id 124 | let mutable resultSet = testData |> Array.fold (fun s t -> s |> HashSet.add t) HashSet.empty 125 | let resultSeq = resultSet |> HashSet.toSeq |> Seq.toArray |> Array.sort 126 | 127 | Expect.equal resultSeq testData "Array data not the same" 128 | Expect.equal testDataSize (HashSet.count resultSet) "Set not empty" 129 | Expect.isTrue (resultSet |> HashSet.contains (testDataSize / 2)) "Value not found" 130 | Expect.isTrue (resultSet |> HashSet.contains 1) "Value not found (1)" 131 | Expect.isTrue (resultSet |> HashSet.contains (testDataSize - 1)) "Value not found (Last Value)" 132 | 133 | for k in testData do resultSet <- resultSet |> HashSet.remove k 134 | 135 | Expect.equal 0 (HashSet.count resultSet) "Set not empty" 136 | Expect.isFalse (resultSet |> HashSet.contains (testDataSize / 2)) "Value still found" 137 | Expect.isFalse (resultSet |> HashSet.contains 1) "Value still found (1)" 138 | Expect.isFalse (resultSet |> HashSet.contains (5000000 - 1)) "Value still found (Last Value)" 139 | Expect.equal resultSet HashSet.empty "HashSet should be empty" 140 | ) 141 | 142 | let intersectionEquilvalentToReference (hsOne: list) (hsTwo: list) = 143 | let referenceSet = System.Collections.Generic.HashSet(hsOne) 144 | referenceSet.IntersectWith(hsTwo) 145 | let referenceSetResults = referenceSet |> Seq.sort |> Seq.toArray 146 | let setUnderTest = HashSet.ofSeq hsOne |> HashSet.intersect (HashSet.ofSeq hsTwo) |> HashSet.toSeq |> Seq.sort |> Seq.toArray 147 | referenceSetResults = setUnderTest 148 | 149 | let intersectionSupersetWithSubsetEqualToSubset (actions: SetAction<'tk> list) = 150 | let subSet = generateSetFromActions actions 151 | let fullSet = generateSetFromActions (actions |> List.filter (fun x -> match x with | Add _ -> true | _ -> false)) 152 | 153 | let hashSubSet = generateHashSetFromActions actions 154 | let hashFullSet = generateHashSetFromActions (actions |> List.filter (fun x -> match x with | Add _ -> true | _ -> false)) 155 | 156 | let resultSeq x = x |> Seq.toArray |> Array.sort 157 | 158 | Expect.equal (Set.intersect subSet fullSet |> resultSeq) (HashSet.intersect hashSubSet hashFullSet |> resultSeq) "Intersect not expected" 159 | 160 | let assertEqualsTheSame actions = 161 | let hashSet = generateHashSetFromActions actions 162 | let freshSet = hashSet |> HashSet.toSeq |> HashSet.ofSeq 163 | Expect.equal (hashSet.GetHashCode()) (freshSet.GetHashCode()) "Hash codes not equal" 164 | Expect.equal hashSet freshSet "Set equality not working" 165 | 166 | let [] tests = 167 | testList 168 | "Set Property Tests" 169 | [ 170 | testCase 171 | "Adding 3 items" 172 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 32u; Add 1u; Add 0u ]) 173 | 174 | testCase 175 | "Adding another close approximate 3 items with a hash collision from 0 and -1 keys" 176 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 1L; Add -1L; Add 0L ]) 177 | 178 | testCase 179 | "Adding another close approximate 3 items with a hash collision from 0 and -1 keys and then removing one of them" 180 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 1L; Add -1L; Add 0L; Remove 0L ]) 181 | 182 | testCase 183 | "Set contains keys of the same hash (Hash = 0 for both 0 and -1" 184 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 0L; Add -1L ] ) 185 | 186 | testCase 187 | "Hash collision node in tree; then one is removed with a collision" 188 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 1L; Add -1L; Add 0L; Remove 0L ]) 189 | 190 | testCase 191 | "Add and remove value with same hash" 192 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 0L; Remove 1L ]) 193 | 194 | testCase 195 | "Add and remove same key" 196 | (fun () -> setAndHashSetAreTheSameAfterActions [ Add 1L; Remove 1L] ) 197 | 198 | testCase 199 | "Empty Set returns IsEmpty" 200 | (fun () -> Expect.isTrue (HashSet.isEmpty (HashSet.empty |> HashSet.add 1 |> HashSet.remove 1)) "HashSet not empty") 201 | 202 | testCase 203 | "Not equal set by values returns not equal" 204 | (fun () -> Expect.notEqual (HashSet.empty |> HashSet.add 1 |> HashSet.add 2) (HashSet.empty |> HashSet.add 3 |> HashSet.add 2) "Equal Hash Sets when shouldn't be.") 205 | 206 | testCase 207 | "Equal set by values returns equal" 208 | (fun () -> Expect.equal (HashSet.empty |> HashSet.add 1 |> HashSet.add 2) (HashSet.empty |> HashSet.add 2 |> HashSet.add 1) "Not equal hash sets when should be.") 209 | 210 | testCase 211 | "Nested set by values returns equal" 212 | (fun () -> 213 | let buildHashSet() = hashSet [ (1, hashSet [ (2, 3) ]); (2, hashSet [ (5, 6) ]) ] 214 | Expect.equal (buildHashSet()) (buildHashSet()) "Not equal when should be") 215 | 216 | testCase 217 | "HashSet should be equal despite hash collisions being inserted in different order" 218 | (fun () -> 219 | let set1 = HashSet.empty |> HashSet.add 1UL |> HashSet.add 0x200000003UL 220 | let set2 = HashSet.empty |> HashSet.add 0x200000003UL |> HashSet.add 1UL 221 | Expect.equal set1 set2 "Sets not equal when they should be") 222 | 223 | testCase 224 | "Nested set by different nested value returns not equal" 225 | (fun () -> 226 | let buildHashSet v = hashSet [ (1, hashSet [ (2, v) ]); (2, hashSet [ (5, 6) ]) ] 227 | Expect.notEqual (buildHashSet 5) (buildHashSet 7) "equal when should not be") 228 | 229 | testCase 230 | "ToString output is expected" 231 | (fun () -> 232 | let testHashSet = hashSet [ hashSet [1; 2]; hashSet [3; 4] ] 233 | Expect.equal 234 | (testHashSet.ToString()) 235 | "hashSet [hashSet [1; 2]; hashSet [3; 4]]" 236 | "toString not valid" ) 237 | 238 | generateLargeSizeMapTest() 239 | 240 | generateLargeSizeMapOfSeqTest() 241 | 242 | largeSetAddAllThenRemoveAllIsEmpty() 243 | 244 | buildPropertyTest 245 | "Set and HashSet behave the same on Add and Remove" 246 | setAndHashSetAreTheSameAfterActions 247 | 248 | buildPropertyTest 249 | "Set and HashSet always have the same Contains result" 250 | setAndHashSetHaveSameContainsValue 251 | 252 | buildPropertyTest 253 | "Set and HashSet always have the same Count result" 254 | setAndHashSetHaveSameCountAtAllTimes 255 | 256 | buildPropertyTest 257 | "Equals from actions and fresh set the same" 258 | assertEqualsTheSame 259 | 260 | buildGenericPropertyTest 261 | "Set and HashSet always have the same intersection result" 262 | intersectionEquilvalentToReference 263 | 264 | buildPropertyTest 265 | "Set and HashSet intersect removes all elements not removed in superset" 266 | intersectionSupersetWithSubsetEqualToSubset 267 | ] -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Unit/Program.fs: -------------------------------------------------------------------------------- 1 | module Program 2 | 3 | open Expecto 4 | 5 | let [] main argv = runTestsInAssembly defaultConfig argv -------------------------------------------------------------------------------- /test/FSharp.HashCollections.Unit/paket.references: -------------------------------------------------------------------------------- 1 | Expecto 2 | Expecto.FsCheck 3 | FsCheck 4 | FSharp.Core -------------------------------------------------------------------------------- /test/PlatformComparison/FSharpTest/FSharpTest.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 3390;$(WarnOn) 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/PlatformComparison/FSharpTest/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://docs.microsoft.com/dotnet/fsharp 2 | 3 | open System 4 | open FSharp.HashCollections 5 | open System.Diagnostics 6 | open System.Collections.Generic 7 | 8 | // Define a function to construct a message to print 9 | let runTest testSize toPrint = 10 | let sourceData = Array.init testSize id 11 | 12 | let populateSw = Stopwatch.StartNew() 13 | let map = HashMap.ofSeq (sourceData |> Seq.map (fun x -> KeyValuePair.Create(int64 x, int64 x))) 14 | populateSw.Stop() 15 | 16 | let resultArray : int64 voption[] = Array.zeroCreate testSize 17 | let sw = Stopwatch.StartNew() 18 | 19 | for i = 0 to resultArray.Length - 1 do 20 | resultArray.[i] <- map |> HashMap.tryFind (int64 i) 21 | 22 | sw.Stop() 23 | 24 | if toPrint 25 | then printfn $"| {testSize} | {sw.ElapsedMilliseconds} | {populateSw.ElapsedMilliseconds} |" 26 | 27 | (sw.ElapsedMilliseconds, populateSw.ElapsedMilliseconds) 28 | 29 | [] 30 | let main argv = 31 | let testSizes = [ 32 | 100 33 | 1000 34 | 10000 35 | 100000 36 | 500000 37 | 1000000 38 | 5000000 39 | 10000000 40 | 50000000 41 | ] 42 | 43 | runTest 100 false |> ignore // Warmup 44 | 45 | let testHeader = String.Join(" | ", testSizes) 46 | printfn $"| Lang | Operation | TestSize | {testHeader} |" 47 | 48 | let testResults = [ 49 | for testSize in testSizes do 50 | yield runTest testSize false ] 51 | 52 | printfn "| F# | TryFind | %s |" (String.Join(" | ", (testResults |> Seq.map fst))) 53 | printfn "| F# | OfSeq | %s |" (String.Join(" | ", (testResults |> Seq.map snd))) 54 | 55 | 0 -------------------------------------------------------------------------------- /test/PlatformComparison/FSharpTest/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core -------------------------------------------------------------------------------- /test/PlatformComparison/ScalaTest/Program.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.immutable.HashMap; 2 | 3 | object Benchmarker { 4 | def runTest(testSize: Int, toPrint: Boolean) : (Long, Long) = { 5 | val a = Array.range(0, testSize); 6 | 7 | val writeStartTime = System.nanoTime(); 8 | 9 | val m = a.map(i => i.toLong -> i.toLong).toMap; 10 | val endWriteTime = System.nanoTime(); 11 | 12 | val startTime = System.nanoTime(); 13 | 14 | val result = new Array[Long](testSize); 15 | 16 | for (i <- a) { 17 | result(i) = m.get(i.toLong).get 18 | } 19 | 20 | val endTime = System.nanoTime(); 21 | 22 | val timeIntervalGet = (endTime - startTime) / 1000000; // To milliseconds 23 | val timeIntervalWrite = (endWriteTime - writeStartTime) / 1000000; // To milliseconds 24 | 25 | if (toPrint) { 26 | println(s"| $testSize | $timeIntervalGet | $timeIntervalWrite |"); 27 | } 28 | 29 | return (timeIntervalGet, timeIntervalWrite); 30 | } 31 | } 32 | 33 | object Program extends App { 34 | val testSizes = List( 35 | 100, 36 | 1000, 37 | 10000, 38 | 100000, 39 | 500000, 40 | 1000000, 41 | 5000000, 42 | 10000000, 43 | 50000000 44 | ) 45 | 46 | var headingString = testSizes.foldLeft("| Lang | Operation | TestSize | ") { 47 | (acc, e) => acc + e.toString + " | " 48 | }; 49 | 50 | println(headingString); 51 | 52 | Benchmarker.runTest(100, false); // Warmup 53 | 54 | val testResults = testSizes.map(testSize => Benchmarker.runTest(testSize, false)); 55 | 56 | val testResultGetTime = testResults.foldLeft ("| Scala | TryFind | ") { 57 | (acc, times) => 58 | val (getTime, _) = times; 59 | acc + getTime.toString + " | "; 60 | }; 61 | 62 | println(testResultGetTime); 63 | 64 | val testResultOfSeqTime = testResults.foldLeft ("| Scala | OfSeq | ") { 65 | (acc, times) => 66 | val (_, ofSeqTime) = times; 67 | acc + ofSeqTime.toString + " | "; 68 | }; 69 | 70 | println(testResultOfSeqTime); 71 | } -------------------------------------------------------------------------------- /test/PlatformComparison/ScalaTest/run.sh: -------------------------------------------------------------------------------- 1 | scala --version 2 | scalac ./Program.scala && env JAVA_OPTS="-Xmx8g" scala Program -deprecation -------------------------------------------------------------------------------- /test/PlatformComparison/runAll.sh: -------------------------------------------------------------------------------- 1 | cd FSharpTest 2 | echo "Dotnet version:" `dotnet --version` 3 | dotnet run -c Release 4 | cd .. 5 | cd ScalaTest 6 | ./run.sh 7 | cd .. --------------------------------------------------------------------------------