Skip to content

[CompilerPerf] Using reflection to enhance #5112 and #5278 - #5307

Closed
manofstick wants to merge 46 commits into
dotnet:masterfrom
manofstick:nobox_reflection
Closed

[CompilerPerf] Using reflection to enhance #5112 and #5278#5307
manofstick wants to merge 46 commits into
dotnet:masterfrom
manofstick:nobox_reflection

Conversation

@manofstick

Copy link
Copy Markdown
Contributor

Third in a series of PRs, each building on the other, sequentially merge-able without the subsequent ones - although ideally all of them would be merged.

This is realizing the discussion begun here.

It's moving the relevant parts of reflection.fs down into prim-types.fs, and then utilizing those functions to enhance the ability of the functions canUseDefaultComparer and canUseDefaultEqualityComparer to determine if PER = ER on the requested type - hence providing a more efficient comparer, especially in the value type case where it means that boxing can be avoided.

@manofstickmanofstick changed the title Using reflection to enhance #5112 and #5278[WIP] Using reflection to enhance #5112 and #5278Jul 8, 2018
@manofstick

Copy link
Copy Markdown
ContributorAuthor

Some information about this PR:

  • Minimal subset of reflect.fs moved to prim-types.fs for querying fsharp types (records, unions)
  • Function stubs left in reflect.fs call implementations in prim-types.fs
  • Removed any stubs in reflect.fs which were no longer required
  • Copied required reshapedreflection.fs functions to prim-types.fs (I'm hoping this will all go away as .netstandard 2.0 becomes standard?)
  • Used Reflection functions to query objects to determine if ER comparer could be used.
  • All project internal

Some information about moving the functions

  • No access to option<>, list<>, Seq, etc.
  • reflect.fs has what appeared to be optimizations for option<> and list<>. This code was left in the stub.
  • tryXXX functions whose signature was ...->option<X> where changed to ...->byref<X>->bool
  • (Seq|Array).filter was converted to Array.FindAll
  • (Seq|Array).select was converted to Array.ConvertAll
  • A reshaped Array.ConvertAll was required as that only was introduced in .netstandard 2.0
  • sortFreshArray didn't originally create a fresh array, and still doesn't, but I didn't rename it

@manofstick

Copy link
Copy Markdown
ContributorAuthor

I've run the tests from #5112 & #5278 again after this PR. The following results are compared against those already optimized results reported in those PRs, rather than back to master.

I haven't rerun the old results, just used what was in the original PR.

Note that +/- 5% probably is just a fluctuation of the runs, I'm not doing this fully scientifically. The results should be significant enough that such "minor" performance changes are negligible.

Test 1

#5112 (comment)
https://gist.github.com/manofstick/2b5f11c4574f206ad27b

64-bit

test#5112#5307Percent
custom dynamic120.3310285%
custom structural136.83110.8381%
custom default123.67106.8386%
value dynamic933.83382.8341%
value structural501438.588%
value default465.67410.3388%
gen value dynamic1273.67415.3333%
gen value structural83857869%
gen value default572399.3370%
ref dynamic1002.67664.3366%
ref structural740658.3389%
ref default783.5712.591%
gen ref dynamic1469.83733.6750%
gen ref structural1135.83878.3377%
gen ref default1011.67764.6776%
tuple dynamic593.17528.589%
tuple structural251.33241.3396%
tuple default706.83664.594%
value tuple dynamic149.5126.585%
value tuple structural150.6712784%
value tuple default149.33128.1786%

32-bit

test#5112#5307Percent
custom dynamic113.1794.6784%
custom structural277.17185.1767%
custom default112.67100.1789%
value dynamic2365.5400.1717%
value structural577.67502.8387%
value default481.67413.6786%
gen value dynamic3186.83511.6716%
gen value structural1702127575%
gen value default603.33521.6786%
ref dynamic2289654.6729%
ref structural722656.1791%
ref default774.5688.3389%
gen ref dynamic3055.83849.3328%
gen ref structural2231.51798.3381%
gen ref default1079.3388782%
tuple dynamic1208.67949.8379%
tuple structural261240.6792%
tuple default792.33743.8394%
value tuple dynamic152.67134.1788%
value tuple structural153.17132.8387%
value tuple default154.33134.587%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 2

#5112 (comment)
https://gist.github.com/manofstick/847d965aff5c1de360cf63d66e8a53ed

32-bit

test#5112#5307Percent
seqGroupBy67451076%
seqCountBy67645667%
listGroupBy71625235%
listCountBy45721146%
arrayCountBy30415150%
arrayGroupBy21712658%
arrayCountBy38430479%

64-bit

test#5112#5307Percent
seqGroupBy98656958%
seqCountBy129655943%
listGroupBy75821428%
listCountBy94921222%
arrayCountBy81816220%
arrayGroupBy43212128%
arrayCountBy52530157%

@forki

Copy link
Copy Markdown
Contributor

Wow. So it's ready?

@manofstick

Copy link
Copy Markdown
ContributorAuthor

Test 3

#5112 (comment)
https://gist.github.com/manofstick/9b06efefe1396808611cf55e812c0ea9

test#5112#5307Percent
32-bit55125628102%
64-bit4822397082%

@manofstick

Copy link
Copy Markdown
ContributorAuthor

Test 4

#5112 (comment)
https://gist.github.com/manofstick/42bfe5f1a888480af3d83190fe364e85

test#5112#5307Percent
32-bit non-generic2664248893%
32-bit generic30834098133%
64-bit non-generic2639244993%
64-bit generic2891265892%

@manofstick

Copy link
Copy Markdown
ContributorAuthor

@manofstick

Copy link
Copy Markdown
ContributorAuthor

@manofstick

Copy link
Copy Markdown
ContributorAuthor

@forki

Yeah, I'm happy with it, so any code reviews now would be appreciated. I'll just finish posting my test times and then remove the WIP in the name...

Oh, and just as I got to test 6 I realized that I was comparing my checks to times from the first cut I did which was removing tailcalls. But it's not too far off - but does explain a couple of anomalies... I should have been referring back to #5112 (comment) - but doesn't totally invalidate I dont' think... just painful posting as I haven't automated this... Grrr.. But just slog through.

@manofstick

Copy link
Copy Markdown
ContributorAuthor

Test 7

#5112 (comment)
https://gist.github.com/manofstick/46ae43f5969cc1bb4c125509703299d5

32-bit

test#5112#5307Percent
int2439229694%
float3795348292%
struct int*int3079278891%
struct int*int643200288390%
struct int*float8216751791%
struct int64int64int64int64int64int64int64*int645498527296%
struct floatfloatfloatfloatfloatfloatfloat*float221372045692%
int*int8348774293%
int*float8042745493%
int64int64int64int64int64int64int64*int64210231936292%
floatfloatfloatfloatfloatfloatfloat*float189851786094%
TestRecordData4542418892%
TestGenericRecordData6482600093%
TestUnionData5094478894%
TestGenericUnionData5739520391%

64-bit

test#5112#5307Percent
int67161592%
float92691799%
struct int*int1230104085%
struct int*int64184861699092%
struct int*float215211982492%
struct int64int64int64int64int64int64int64*int64196261858595%
struct floatfloatfloatfloatfloatfloatfloat*float289922577089%
int*int3021291496%
int*float3008298899%
int64int64int64int64int64int64int64*int648928832293%
floatfloatfloatfloatfloatfloatfloat*float860010502122%
TestRecordData1317120391%
TestGenericRecordData1495137992%
TestUnionData1907155782%
TestGenericUnionData1991169385%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 8

#5278 (comment)
https://gist.github.com/manofstick/33fefb8ac6b60d401375be738b521ac3

32-bit

test#5278#5307Percent
custom dynamic1192.331239.67104%
custom structural3798.53866.00102%
custom default1208.831244.50103%
value dynamic3648.831301.0036%
value structural1251.331288.83103%
value default1259.831303.83103%
gen value dynamic6444.833659.6757%
gen value structural3522.833697.17105%
gen value default3560.673714.67104%
ref dynamic3766.831403.0037%
ref structural1281.671302.67102%
ref default1438.831469.17102%
gen ref dynamic6484.833991.8362%
gen ref structural4804.174934.33103%
gen ref default3961.54091.17103%
tuple dynamic11176.3311145.33100%
tuple structural1256.671297.33103%
tuple default8640.839091.50105%
value tuple dynamic13481346.83100%
value tuple structural1336.831342.17100%
value tuple default1349.51350.33100%

64-bit

test#5112#5307Percent
custom dynamic1193.171204.50101%
custom structural1380.331410.67102%
custom default1197.331224.50102%
value dynamic2187.171269.6758%
value structural12401279.50103%
value default12491270.33102%
gen value dynamic2466.51521.5062%
gen value structural1503.331551.00103%
gen value default1524.831570.33103%
ref dynamic1749.671369.1778%
ref structural1262.831262.00100%
ref default1361.671410.33104%
gen ref dynamic2026.171665.3382%
gen ref structural1572.331511.0096%
gen ref default1673.51710.00102%
tuple dynamic44304650.33105%
tuple structural1070.331118.83105%
tuple default7844.338283.17106%
value tuple dynamic1570.171637.33104%
value tuple structural1558.831636.83105%
value tuple default1535.171633.17106%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 9

#5278 (comment)
https://gist.github.com/manofstick/04ba0c70c398bf5edeaba9b17d0b17c5

test#5278#5307Percent
mapTest 32-bit3356324897%
mapTest 64-bit4441281563%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 10

#5278 (comment)
https://gist.github.com/manofstick/bd374436a6d40218b2d836db34c494ca

test#5278#5307Percent
32-bit746157075195%
64-bit682786502695%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 11

#5278 (comment)
https://gist.github.com/manofstick/258edf7e7d39a76e9d1cdf0316ae2b47

test#5278#5307Percent
non-generic 32-bit245451142047%
generic 32-bit464212611956%
non-generic 64-bit182271207166%
generic 64-bit220511413764%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 12

#5278 (comment)
https://gist.github.com/manofstick/847922bcce2e2f47d3eca033ed9dc068

test#5278#5307Percent
32-bit36335096%
64-bit306.828794%

@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

Test 13

#5278 (comment)
https://gist.github.com/manofstick/b4fa5acc1abe3ca73a77a7eea12d205a

test#5278#5307Percent
32-bit575.855897%
64-bit498.8509102%

@manofstick

Copy link
Copy Markdown
ContributorAuthor

Done.

Remember that the times presented here are relative to already optimized #5278 and #5112 !

This PR is good to go! (well code review, etc.)

@manofstickmanofstick changed the title [WIP] Using reflection to enhance #5112 and #5278[CompilerPerf] Using reflection to enhance #5112 and #5278Jul 14, 2018
@manofstick

manofstick commented Jul 14, 2018

Copy link
Copy Markdown
ContributorAuthor

... and one final test ...

Five years ago I posted a response on stackoverflow where I compared various key types for Dictionary<_,_>, dict and Map. I have slightly modified the code in this gist (added ValueTuple and inlined some functions) but quite interesting to see how far FSharp.Core has come! (I have done some of this by removing layers in value types in dict and now this PR, but others have improved Map - maybe it's just general compiler improvements? I'm not sure...)

So below are the results! From the original posting:

Using .net's System.Collections.Generic.Dictionary
checksum -55.339450 elapsed 874/562 (KeyRecord)
checksum -55.339450 elapsed 1251/898 (KeyGenericRecord`1)
checksum -55.339450 elapsed 569/1024 (KeyStruct)
checksum -55.339450 elapsed 740/1427 (KeyGenericStruct`1)
checksum -55.339450 elapsed 2497/2218 (Tuple`3)
Using f# 'dict'
checksum -55.339450 elapsed 979/628 (KeyRecord)
checksum -55.339450 elapsed 1614/1206 (KeyGenericRecord`1)
checksum -55.339450 elapsed 3237/5625 (KeyStruct)
checksum -55.339450 elapsed 3290/5626 (KeyGenericStruct`1)
checksum -55.339450 elapsed 2448/1914 (Tuple`3)
Using f# 'Map'
checksum -55.339450 elapsed 8453/2638 (KeyRecord)
checksum -55.339450 elapsed 31301/25441 (KeyGenericRecord`1)
checksum -55.339450 elapsed 30956/26931 (KeyStruct)
checksum -55.339450 elapsed 53699/49274 (KeyGenericStruct`1)
checksum -55.339450 elapsed 32203/25274 (Tuple`3)
Using custom array
checksum -55.339450 elapsed 484/160 (Tuple`3)

(read the results as n/m where n is the time to create the container, m is the time to access the container)

Unfortunately I didn't record if this was the 32-bit or 64-bit build

The less important thing here is not so much the absolute times (as I don't remember what the hardware was) but rather using the Dictionary with KeyRecord has a baseline from which the other numbers can be interpreted.

The results from a current run (prior to this PR) are:

32-bit

Using .net's System.Collections.Generic.Dictionary
checksum -55.339450 elapsed 634/444 (KeyRecord)
checksum -55.339450 elapsed 1001/829 (KeyGenericRecord`1)
checksum -55.339450 elapsed 380/283 (KeyStruct)
checksum -55.339450 elapsed 543/669 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1700/1529 (Tuple`3)
checksum -55.339450 elapsed 597/539 (ValueTuple`3)
Using f# 'dict'
checksum -55.339450 elapsed 750/608 (KeyRecord)
checksum -55.339450 elapsed 1095/1185 (KeyGenericRecord`1)
checksum -55.339450 elapsed 526/565 (KeyStruct)
checksum -55.339450 elapsed 760/1121 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1987/1922 (Tuple`3)
checksum -55.339450 elapsed 1500/1743 (ValueTuple`3)
Using f# 'Map'
checksum -55.339450 elapsed 6720/3540 (KeyRecord)
checksum -55.339450 elapsed 11355/7586 (KeyGenericRecord`1)
checksum -55.339450 elapsed 9682/3243 (KeyStruct)
checksum -55.339450 elapsed 10804/7364 (KeyGenericStruct`1)
checksum -55.339450 elapsed 10695/6700 (Tuple`3)
checksum -55.339450 elapsed 13516/6492 (ValueTuple`3)
Using custom array
checksum -55.339450 elapsed 272/3857 (Tuple`3)

64-bit

Using .net's System.Collections.Generic.Dictionary
checksum -55.339450 elapsed 591/434 (KeyRecord)
checksum -55.339450 elapsed 864/629 (KeyGenericRecord`1)
checksum -55.339450 elapsed 706/568 (KeyStruct)
checksum -55.339450 elapsed 799/811 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1619/1585 (Tuple`3)
checksum -55.339450 elapsed 941/857 (ValueTuple`3)
Using f# 'dict'
checksum -55.339450 elapsed 575/526 (KeyRecord)
checksum -55.339450 elapsed 784/695 (KeyGenericRecord`1)
checksum -55.339450 elapsed 1381/1679 (KeyStruct)
checksum -55.339450 elapsed 1515/1912 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1450/1338 (Tuple`3)
checksum -55.339450 elapsed 3129/3510 (ValueTuple`3)
Using f# 'Map'
checksum -55.339450 elapsed 5180/1748 (KeyRecord)
checksum -55.339450 elapsed 7364/3298 (KeyGenericRecord`1)
checksum -55.339450 elapsed 17353/13944 (KeyStruct)
checksum -55.339450 elapsed 19098/15394 (KeyGenericStruct`1)
checksum -55.339450 elapsed 7422/3325 (Tuple`3)
checksum -55.339450 elapsed 19207/15323 (ValueTuple`3)
Using custom array
checksum -55.339450 elapsed 274/99 (Tuple`3)

And post this PR are:

32-bit

Using .net's System.Collections.Generic.Dictionary
checksum -55.339450 elapsed 601/430 (KeyRecord)
checksum -55.339450 elapsed 741/553 (KeyGenericRecord`1)
checksum -55.339450 elapsed 359/284 (KeyStruct)
checksum -55.339450 elapsed 437/389 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1772/1526 (Tuple`3)
checksum -55.339450 elapsed 590/527 (ValueTuple`3)
Using f# 'dict'
checksum -55.339450 elapsed 610/451 (KeyRecord)
checksum -55.339450 elapsed 711/570 (KeyGenericRecord`1)
checksum -55.339450 elapsed 358/327 (KeyStruct)
checksum -55.339450 elapsed 436/431 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1866/1808 (Tuple`3)
checksum -55.339450 elapsed 556/540 (ValueTuple`3)
Using f# 'Map'
checksum -55.339450 elapsed 4299/1155 (KeyRecord)
checksum -55.339450 elapsed 5758/2476 (KeyGenericRecord`1)
checksum -55.339450 elapsed 6775/794 (KeyStruct)
checksum -55.339450 elapsed 4690/2081 (KeyGenericStruct`1)
checksum -55.339450 elapsed 12744/5136 (Tuple`3)
checksum -55.339450 elapsed 7207/943 (ValueTuple`3)
Using custom array
checksum -55.339450 elapsed 265/121 (Tuple`3)

64-bit

Using .net's System.Collections.Generic.Dictionary
checksum -55.339450 elapsed 569/425 (KeyRecord)
checksum -55.339450 elapsed 601/467 (KeyGenericRecord`1)
checksum -55.339450 elapsed 672/544 (KeyStruct)
checksum -55.339450 elapsed 698/592 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1578/1538 (Tuple`3)
checksum -55.339450 elapsed 911/840 (ValueTuple`3)
Using f# 'dict'
checksum -55.339450 elapsed 533/447 (KeyRecord)
checksum -55.339450 elapsed 556/482 (KeyGenericRecord`1)
checksum -55.339450 elapsed 669/575 (KeyStruct)
checksum -55.339450 elapsed 699/624 (KeyGenericStruct`1)
checksum -55.339450 elapsed 1399/1274 (Tuple`3)
checksum -55.339450 elapsed 911/862 (ValueTuple`3)
Using f# 'Map'
checksum -55.339450 elapsed 4603/1203 (KeyRecord)
checksum -55.339450 elapsed 5409/1887 (KeyGenericRecord`1)
checksum -55.339450 elapsed 4323/1111 (KeyStruct)
checksum -55.339450 elapsed 4950/1585 (KeyGenericStruct`1)
checksum -55.339450 elapsed 7014/3007 (Tuple`3)
checksum -55.339450 elapsed 5078/1281 (ValueTuple`3)
Using custom array
checksum -55.339450 elapsed 263/88 (Tuple`3)

So finally we see that dict almost add no overhead, and Map is now quite competitive!

Time for me to enjoy my Sunday.

@dsyme

Copy link
Copy Markdown
Contributor

Amazing work!

@manofstick
manofstickforce-pushed the nobox_reflection branch 2 times, most recently from 080801c to 9e75253CompareJuly 19, 2018 05:52
@KevinRansom

KevinRansom commented May 28, 2020

Copy link
Copy Markdown
Contributor

@dsyme , @manofstick --- guys, is this orphaned, should I close it? I am on a mission to reduce the PR's in the repo, and an amazing number haven't been worked on in over a year. I think the techniques in this look useful?

But if it's not going to be productized I would like to close it, what do you think?

Thanks

Kevin

@manofstick

Copy link
Copy Markdown
ContributorAuthor

@KevinRansom,

I will pick these up again if anyone was going to do anything with them. They were complete, but obviously code base has moved on.

So I'll just go and close 'em all, and if anyone decides that they are worth anything then they can poke me with a stick.

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.

5 participants

@manofstick@forki@dsyme@KevinRansom@cartermp