Skip to content

Map: Optimize away isinst check - #10845

Merged
cartermp merged 3 commits into
dotnet:mainfrom
buybackoff:map_step2
Jan 17, 2021
Merged

Map: Optimize away isinst check#10845
cartermp merged 3 commits into
dotnet:mainfrom
buybackoff:map_step2

Conversation

@buybackoff

@buybackoffbuybackoff commented Jan 6, 2021

Copy link
Copy Markdown
Contributor

Following a discussion here #10768

Found a simple perf gain at the cost of 2 bytes per a Map item. A significant gain in getItem/contains/add due to replacement of isinst by int32 equality check. Benchmarked against current main.

The change is to store the height in leaves and share the field with nodes. Use it as an implicit tag (a leaf when height = 1) instead of the type check. Compared to the old discussion, when Left/Right were proposed to be stored in a universal node, this adds 4 bytes to leaves or 2 bytes per item on average (vs 16/8).

The tradeoff is basically the only thing to consider. Code changes are trivial.

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DEBUG
After : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Before : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MaxRelativeError=0.01 Arguments=/p:Optimize=true IterationCount=20 IterationTime=250.0000 ms WarmupCount=1 
MethodJobBuildConfigurationSizeMeanErrorStdDevRankGen 0Gen 1Gen 2AllocatedCode Size
getItemAfterLocalBuild10028.34 ns0.291 ns0.324 ns1----93 B
getItemBeforeDefault10036.48 ns0.206 ns0.212 ns2----119 B
getItemAfterLocalBuild1000060.40 ns0.365 ns0.421 ns3----93 B
getItemBeforeDefault1000082.42 ns0.446 ns0.513 ns4----119 B
containsKeyAfterLocalBuild10024.95 ns0.231 ns0.266 ns1----155 B
containsKeyBeforeDefault10029.23 ns0.385 ns0.412 ns2----175 B
containsKeyAfterLocalBuild1000046.19 ns0.292 ns0.337 ns3----155 B
containsKeyBeforeDefault1000053.00 ns0.177 ns0.196 ns4----175 B
itemCountAfterLocalBuild100179.55 ns1.426 ns1.643 ns1----81 B
itemCountBeforeDefault100217.68 ns1.816 ns1.865 ns2----96 B
itemCountAfterLocalBuild1000033,454.41 ns1,561.752 ns1,798.516 ns3----81 B
itemCountBeforeDefault1000034,357.14 ns374.999 ns431.850 ns3----96 B
iterForeachAfterLocalBuild1003,223.95 ns23.071 ns23.692 ns21.0311--6520 B283 B
iterForeachBeforeDefault1003,082.44 ns28.212 ns30.186 ns10.9704--6120 B283 B
iterForeachAfterLocalBuild10000336,049.08 ns1,375.830 ns1,472.123 ns4101.0638--640120 B283 B
iterForeachBeforeDefault10000317,291.41 ns511.972 ns525.758 ns395.6633--600120 B283 B
addItemAfterLocalBuild100145.75 ns0.811 ns0.934 ns10.0590--374 B601 B
addItemBeforeDefault100170.09 ns2.717 ns2.907 ns20.0584--369 B603 B
addItemAfterLocalBuild1000036,414.43 ns415.002 ns477.917 ns311.00003.6250-69724 B601 B
addItemBeforeDefault1000037,677.58 ns103.857 ns111.126 ns411.00003.2500-69324 B603 B
removeItemAfterLocalBuild10012.33 ns0.114 ns0.112 ns10.0064--40 B425 B
removeItemBeforeDefault10012.22 ns0.076 ns0.081 ns10.0064--40 B443 B
removeItemAfterLocalBuild100001,189.82 ns6.181 ns7.118 ns20.6345--4000 B425 B
removeItemBeforeDefault100001,188.84 ns7.846 ns8.720 ns20.6345--4000 B443 B

@buybackoffbuybackoff changed the title Map: Optimize away ininst checkMap: Optimize away isinst checkJan 6, 2021
@buybackoffbuybackoff mentioned this pull request Jan 6, 2021
Store height in leaves. Compared to the old discussion, when Left/Right were proposed to be stored in a universal node,
this adds 4 bytes to leaves or 2 bytes per item on average (vs 16/8).
@buybackoff

buybackoff commented Jan 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Initially (in the benchmarks table) in the asNode function I used (# "" value: MapTreeNode<'Key,'Value> #) instead of value :?> MapTreeNode<'Key,'Value>. This is a replacement for Unsafe.As, discussed long time ago here.

But that fails in a bad way on Windows/full framework. The updated benchmarks are below.

error FS0193#L0
error FS0193(0,0): error : (NETCORE_ENGINEERING_TELEMETRY=Build) Operation could destabilize the runtime.

There is still an improvement, but smaller. Making this a draft.

Is there a way to suppress the error for (# "" value: MapTreeNode<'Key,'Value> #) or achieve the same thing without a cast and without S.R.CS.Unsafe dependency?

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DEBUG
After : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Before : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MaxRelativeError=0.01 Arguments=/p:Optimize=true IterationCount=20 IterationTime=250.0000 ms WarmupCount=1 
MethodJobBuildConfigurationSizeMeanErrorStdDevRankGen 0Gen 1Gen 2AllocatedCode Size
getItemAfterLocalBuild10034.16 ns0.258 ns0.297 ns1----93 B
getItemBeforeDefault10036.51 ns0.291 ns0.312 ns2----119 B
getItemAfterLocalBuild1000075.90 ns0.839 ns0.933 ns3----93 B
getItemBeforeDefault1000082.41 ns0.306 ns0.352 ns4----119 B
containsKeyAfterLocalBuild10029.02 ns0.310 ns0.357 ns1----198 B
containsKeyBeforeDefault10028.70 ns0.308 ns0.355 ns1----175 B
containsKeyAfterLocalBuild1000052.57 ns0.453 ns0.465 ns2----198 B
containsKeyBeforeDefault1000053.28 ns0.423 ns0.487 ns2----175 B
itemCountAfterLocalBuild100205.14 ns1.642 ns1.891 ns1----110 B
itemCountBeforeDefault100216.20 ns0.965 ns1.072 ns2----96 B
itemCountAfterLocalBuild1000035,507.48 ns934.860 ns1,076.586 ns4----110 B
itemCountBeforeDefault1000034,428.44 ns534.973 ns616.076 ns3----96 B
iterForeachAfterLocalBuild1003,139.77 ns15.790 ns18.184 ns21.0354--6520 B283 B
iterForeachBeforeDefault1003,065.72 ns19.452 ns22.401 ns10.9659--6120 B283 B
iterForeachAfterLocalBuild10000324,242.95 ns1,222.667 ns1,408.025 ns3101.5625--640120 B283 B
iterForeachBeforeDefault10000320,282.94 ns1,422.972 ns1,638.697 ns395.6633--600120 B283 B
addItemAfterLocalBuild100151.75 ns0.757 ns0.810 ns10.0591--374 B655 B
addItemBeforeDefault100168.32 ns0.436 ns0.502 ns20.0583--369 B603 B
addItemAfterLocalBuild1000037,558.66 ns509.422 ns566.221 ns311.00003.6250-69724 B655 B
addItemBeforeDefault1000039,931.69 ns349.741 ns388.736 ns410.93753.2813-69324 B603 B
removeItemAfterLocalBuild10012.65 ns0.100 ns0.115 ns20.0064--40 B454 B
removeItemBeforeDefault10012.35 ns0.147 ns0.169 ns10.0064--40 B443 B
removeItemAfterLocalBuild100001,290.84 ns21.354 ns22.848 ns40.6351--4000 B454 B
removeItemBeforeDefault100001,195.52 ns6.200 ns6.634 ns30.6346--4000 B443 B

@buybackoff
buybackoff marked this pull request as draft January 7, 2021 00:14
@buybackoff

Copy link
Copy Markdown
ContributorAuthor

Results for count = 10:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DEBUG
After : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Before : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MaxRelativeError=0.01 Arguments=/p:Optimize=true IterationCount=20 IterationTime=250.0000 ms WarmupCount=1 
MethodJobBuildConfigurationSizeMeanErrorStdDevRankGen 0Gen 1Gen 2AllocatedCode Size
getItemAfterLocalBuild1017.92 ns0.055 ns0.057 ns1----93 B
getItemBeforeDefault1036.07 ns0.329 ns0.379 ns2----119 B
containsKeyAfterLocalBuild1016.03 ns0.117 ns0.134 ns1----198 B
containsKeyBeforeDefault1028.26 ns0.241 ns0.268 ns2----175 B
itemCountAfterLocalBuild1019.27 ns0.170 ns0.189 ns1----110 B
itemCountBeforeDefault10217.36 ns1.386 ns1.540 ns2----96 B
iterForeachAfterLocalBuild10336.97 ns0.816 ns0.801 ns10.1201--760 B283 B
iterForeachBeforeDefault103,045.81 ns7.461 ns8.293 ns20.9686--6120 B283 B
addItemAfterLocalBuild1069.05 ns0.414 ns0.477 ns10.0313--198 B655 B
addItemBeforeDefault10172.04 ns0.954 ns1.061 ns20.0587--369 B603 B
removeItemAfterLocalBuild1014.13 ns0.060 ns0.066 ns20.0070--44 B454 B
removeItemBeforeDefault1012.13 ns0.043 ns0.046 ns10.0064--40 B443 B

@buybackoff

Copy link
Copy Markdown
ContributorAuthor

Oh, that was not due to the cast. Match on int field with just 2 cases, when the second case is else, generates a switch statement and additional sub instruction for it.

Or the effect of this is similar to removing cast. But will not test that if the unsafe cast is unverifiable and there is no workaround.

match m.Height with
| 1 -> ...
| _ -> ...
IL_0006: ldarg.0 // m
IL_0007: ldfld int32 class Microsoft.FSharp.Collections.MapTree`2<!!0/*TKey*/, !!1/*TValue*/>::h
IL_000c: ldc.i4.1
IL_000d: sub
IL_000e: switch (IL_007d)

`Match` produces `sub 1` and `switch` instruction. Here, for any non-trivial count,
nodes are more frequent than leaves on the path, so branch prediction should be beneficial.
@buybackoff

buybackoff commented Jan 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Updated benchmarks after changing match to if.

The numbers for count = 10 are +/- same as above.

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DEBUG
After : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Before : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MaxRelativeError=0.01 Arguments=/p:Optimize=true IterationCount=20 IterationTime=250.0000 ms WarmupCount=1 
MethodJobBuildConfigurationSizeMeanErrorStdDevRankGen 0Gen 1Gen 2AllocatedCode Size
getItemAfterLocalBuild10028.65 ns0.381 ns0.424 ns1----93 B
getItemBeforeDefault10036.39 ns0.226 ns0.251 ns2----119 B
getItemAfterLocalBuild1000061.33 ns0.263 ns0.293 ns3----93 B
getItemBeforeDefault1000082.86 ns0.129 ns0.143 ns4----119 B
containsKeyAfterLocalBuild10027.50 ns0.256 ns0.285 ns1----200 B
containsKeyBeforeDefault10029.29 ns0.342 ns0.380 ns2----175 B
containsKeyAfterLocalBuild1000053.51 ns0.289 ns0.309 ns3----200 B
containsKeyBeforeDefault1000054.77 ns0.341 ns0.393 ns4----175 B
itemCountAfterLocalBuild100197.96 ns1.155 ns1.284 ns1----112 B
itemCountBeforeDefault100217.04 ns1.008 ns1.079 ns2----96 B
itemCountAfterLocalBuild1000034,628.61 ns651.610 ns750.396 ns3----112 B
itemCountBeforeDefault1000034,815.34 ns1,108.432 ns1,186.009 ns3----96 B
iterForeachAfterLocalBuild1003,090.08 ns10.900 ns11.663 ns11.0373--6520 B283 B
iterForeachBeforeDefault1003,057.11 ns10.379 ns11.537 ns10.9728--6120 B283 B
iterForeachAfterLocalBuild10000324,417.64 ns1,901.370 ns2,189.621 ns2102.0408--640120 B283 B
iterForeachBeforeDefault10000322,203.22 ns1,951.415 ns2,247.253 ns295.6633--600120 B283 B
addItemAfterLocalBuild100153.87 ns1.593 ns1.834 ns10.0593--374 B668 B
addItemBeforeDefault100168.03 ns0.663 ns0.763 ns20.0583--369 B603 B
addItemAfterLocalBuild1000039,308.48 ns1,460.013 ns1,562.198 ns311.09383.7500-69724 B668 B
addItemBeforeDefault1000040,129.25 ns179.695 ns192.272 ns310.93753.2813-69324 B603 B
removeItemAfterLocalBuild10012.99 ns0.067 ns0.077 ns20.0064--40 B461 B
removeItemBeforeDefault10012.20 ns0.074 ns0.085 ns10.0064--40 B443 B
removeItemAfterLocalBuild100001,292.43 ns16.367 ns17.512 ns40.6358--4000 B461 B
removeItemBeforeDefault100001,208.58 ns6.424 ns6.597 ns30.6347--4000 B443 B

@buybackoff
buybackoff marked this pull request as ready for review January 7, 2021 13:00
@cartermp

Copy link
Copy Markdown
Contributor

This looks pretty nice! get/add/contains are also the most relevant operations for the F# compiler, and I suspect that's the case for most consumers as well. The one cause for concern is the error in the add operation. Much higher now and basically makes the improvement a wash. Any thoughts as to why that might be?

I expect that the same technique could be applied to Sets as well?

@buybackoff

buybackoff commented Jan 7, 2021

Copy link
Copy Markdown
ContributorAuthor

This may be explained by short time of running the bench, while my machine was not idle. Or by GC. Need to rerun longer. But the number for add was stable between 3-4 runs.

@buybackoff

Copy link
Copy Markdown
ContributorAuthor

For the count 100, the improvement in add is 7+ sigma, isn't it?

@buybackoff

buybackoff commented Jan 7, 2021

Copy link
Copy Markdown
ContributorAuthor

4x longer run (2x more iterations each 2x longer)

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DEBUG
After : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Before : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MaxRelativeError=0.01 Arguments=/p:Optimize=true IterationCount=40 WarmupCount=1 
MethodJobBuildConfigurationSizeMeanErrorStdDevRankGen 0Gen 1Gen 2AllocatedCode Size
getItemAfterLocalBuild10028.99 ns0.128 ns0.228 ns1----93 B
getItemBeforeDefault10036.21 ns0.329 ns0.366 ns2----119 B
getItemAfterLocalBuild1000064.13 ns0.217 ns0.374 ns3----93 B
getItemBeforeDefault1000082.35 ns0.223 ns0.239 ns4----119 B
containsKeyAfterLocalBuild10028.17 ns0.103 ns0.178 ns1----200 B
containsKeyBeforeDefault10028.50 ns0.457 ns0.526 ns1----175 B
containsKeyAfterLocalBuild1000056.37 ns0.189 ns0.321 ns3----200 B
containsKeyBeforeDefault1000054.79 ns0.646 ns0.744 ns2----175 B
itemCountAfterLocalBuild100180.71 ns0.866 ns1.539 ns1----112 B
itemCountBeforeDefault100218.13 ns1.972 ns2.192 ns2----96 B
itemCountAfterLocalBuild1000033,312.56 ns753.070 ns1,299.009 ns3----112 B
itemCountBeforeDefault1000034,219.59 ns459.480 ns529.138 ns4----96 B
iterForeachAfterLocalBuild1003,087.15 ns10.305 ns17.217 ns21.0376--6520 B283 B
iterForeachBeforeDefault1003,046.73 ns6.480 ns7.202 ns10.9754--6120 B283 B
iterForeachAfterLocalBuild10000325,384.57 ns574.842 ns1,006.789 ns4101.5625--640120 B283 B
iterForeachBeforeDefault10000320,892.29 ns919.239 ns1,021.732 ns395.6633--600120 B283 B
addItemAfterLocalBuild100154.49 ns0.265 ns0.470 ns10.05950.0003-374 B668 B
addItemBeforeDefault100170.07 ns0.702 ns0.780 ns20.0588--369 B603 B
addItemAfterLocalBuild1000037,704.46 ns79.124 ns136.485 ns311.09383.4375-69724 B668 B
addItemBeforeDefault1000038,549.17 ns189.526 ns218.259 ns411.00003.2500-69324 B603 B
removeItemAfterLocalBuild10011.59 ns0.045 ns0.072 ns10.0064--40 B461 B
removeItemBeforeDefault10012.21 ns0.113 ns0.120 ns20.0064--40 B443 B
removeItemAfterLocalBuild100001,249.23 ns4.061 ns7.218 ns40.6372--4000 B461 B
removeItemBeforeDefault100001,214.33 ns6.080 ns6.758 ns30.6348--4000 B443 B

@cartermp

Copy link
Copy Markdown
Contributor

Thanks! I think that looks great.

@cartermpcartermp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good change, thanks, and thanks for all of the testing.

@buybackoff

buybackoff commented Jan 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Thanks!

I think there is still some "free lunch" with comparer devirtualization for at least primitive types. It surprises me that static readonly fields or member vals with get-only are not generated with the readonly modifier. From F# perspective they are readonly, and JIT optimizations for static readonly fields are important these days.

As for Set and complier Map/Set friends, an issue with up-for-grabs label would be nice. I may do this myself later, but do not know when.

@cartermp

Copy link
Copy Markdown
Contributor

No pressure :) - we can also apply your work to the other implementations if this gets merged soon

@buybackoff

buybackoff commented Jan 8, 2021

Copy link
Copy Markdown
ContributorAuthor

I've seen #9348, #513, and related. Such a hopeless state to optimize the comparison :(

Less than an hour tweaking the comparison gives result like below. But it's probably not 100% compatible with the current behavior. It could be if we apply the logic from #9348 snippet, to exclude records & DUs, if that code is correct.

If we take the current NuGet 5.0 release without any changes to Map as a baseline, are we ready to (Ratio column)

  • [Improve the map performance with] primitive keys by 3.65x (44% vs this PR) - getItem,
  • .. struct T : IComparable<T> by 4.14x (3.64x vs this PR) - getItemIntLike,
  • .. string by 52% (yet 10% slower than this PR) - getItemString,
  • but lose 15% for reference types and 17% for struct records - getItemRefLike and getItemIntRecord

?

I would say that everything could be wrapped by struct T : IComparable<T> + efficient logic there, and if someone uses ref-types as a key they do care about about performance by definition. But I do understand that regressing existing code by 15/17% may be too big. Yet the tradeoff is so great for primitive types.

The types in the bench are:

 [<StructuralEquality;CustomComparison>]
type IntLike =
struct
val Value: int
new(v:int) = {Value = v}
member x.CompareTo(y:IntLike) = x.Value.CompareTo(y.Value)
end
interface IComparable<IntLike> with
member x.CompareTo(y) = x.CompareTo(y)
interface IComparable with
member x.CompareTo(y) = x.CompareTo(y :?> IntLike) type RefLike =
val Value: int
new(v:int) = {Value = v}
member x.CompareTo(y:RefLike) = x.Value.CompareTo(y.Value)
interface IComparable<RefLike> with
member x.CompareTo(y) = x.CompareTo(y)
interface IComparable with
member x.CompareTo(y) = x.CompareTo(y :?> RefLike)
[<Struct>]
type IntRecord =
{ Value1 : int
Value2 : int
}
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT DEBUG
After : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Main50 : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
NuGet50 : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MaxRelativeError=0.01 Arguments=/p:Optimize=true IterationCount=5 IterationTime=250.0000 ms WarmupCount=1 
MethodJobBuildConfigurationSizeMeanErrorStdDevRatioRatioSDRankGen 0Gen 1Gen 2Allocated
getItemAfterAfter10020.48 ns0.935 ns0.243 ns1.000.001----
getItemMain50Main5010029.40 ns0.728 ns0.189 ns1.440.022----
getItemNuGet50NuGet5010074.83 ns6.092 ns0.943 ns3.650.083----
getItemIntLikeAfterAfter10060.45 ns0.460 ns0.119 ns1.000.001----
getItemIntLikeMain50Main50100220.00 ns4.159 ns0.644 ns3.640.0220.0450--283 B
getItemIntLikeNuGet50NuGet50100250.34 ns9.453 ns1.463 ns4.140.0330.0449--283 B
getItemStringAfterAfter10076.03 ns1.027 ns0.267 ns1.000.002----
getItemStringMain50Main5010068.22 ns0.538 ns0.083 ns0.900.001----
getItemStringNuGet50NuGet50100115.77 ns0.626 ns0.162 ns1.520.013----
getItemRefLikeAfterAfter100278.41 ns3.569 ns0.927 ns1.000.003----
getItemRefLikeMain50Main50100200.05 ns6.346 ns1.648 ns0.720.011----
getItemRefLikeNuGet50NuGet50100236.40 ns3.194 ns0.494 ns0.850.002----
getItemIntRecordAfterAfter100282.93 ns12.166 ns3.160 ns1.000.003----
getItemIntRecordMain50Main50100200.34 ns6.643 ns1.028 ns0.710.011----
getItemIntRecordNuGet50NuGet50100235.25 ns1.148 ns0.178 ns0.830.012----

It should be probably a separate issue. But I'm not sure I would dig deeper if such tradeoff or breaking changes are not acceptable.

And again, static readonly fields would help. It's a total mess now. Not only such fields are not possible, beforefieldinit also feels random, and some weird init fields are appearing. I still could not understand what's going one with the code gen. I would like to embed multi-line IL directly if I may, beyond (#..#) things 🙄

@buybackoffbuybackoff mentioned this pull request Jan 8, 2021
buybackoff added a commit to buybackoff/fsharp that referenced this pull request Jan 10, 2021
@cartermp

Copy link
Copy Markdown
Contributor

@dsyme it would be good to get your eyes on this as well.

Note that the last set of benchmarks are not reflective of this change, they are a part of a discussion that manifests in #10855

@KevinRansomKevinRansom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good,

thank you for this

@cartermp

Copy link
Copy Markdown
Contributor

I will merge this in. Thanks @buybackoff

@cartermp
cartermp merged commit 15654d2 into dotnet:mainJan 17, 2021
@buybackoff

Copy link
Copy Markdown
ContributorAuthor

@cartermp

I will merge this in. Thanks @buybackoff

Thanks!

It would be interesting to know if comparer changes or even the direction have any chance? It looks like there are huge easy gains for 95+% cases, but they are blocked by the remaining <5% mostly edge cases. I've noticed you are going to add S.C.Immutable dependency for immutable arrays, but (hypothetically) replacing MapTree with AVL implementation from there will require exactly the same transition from F#'s comparison constraint to S.C.G.Comparable<T>.Default for efficient inlined comparer calls.

@cartermp

Copy link
Copy Markdown
Contributor

@buybackoff we'll take a look and have a think about them :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@buybackoff@cartermp@KevinRansom@vzarytovskii