Skip to content

[nomerge] faster ++ and union for HashSet#8589

Merged
retronym merged 1 commit intoscala:2.12.xfrom
rorygraves:mike/2.12.x_quickHashSetUnion
Feb 17, 2020
Merged

[nomerge] faster ++ and union for HashSet#8589
retronym merged 1 commit intoscala:2.12.xfrom
rorygraves:mike/2.12.x_quickHashSetUnion

Conversation

@mkeskells
Copy link
Contributor

@mkeskells mkeskells commented Dec 10, 2019

for 2.12 only - this will not merge directly into 2.13. A separate PR will be needed if any of these changes need to be ported to 2.13

faster operations (mostly union and ++) for HashSet
EmptySet and SetBuilder adjusted to take advantage of ++ optimisation for simple and common cases

eliminate unneeded allocations for HashSets where the result is already built for -
   subSet ++ superSet
   subSet union superSet
   superSet union subSet
   superSet ++ subSet

make a fast path when there is structural sharing in the HashSet
for union, guarantee internal operations will only return a new HashSet if one of the existing HashSet parameters or internal values cant be used

use System.arraycopy rather than Array.copy as it avoid JVM nulling the array

add missing eq fast path to intersect0 and diff0

minor improvements to + to reduce allocations

reduce calls to size which is expensive, especially when we know the size beforehand

@scala-jenkins scala-jenkins added this to the 2.12.11 milestone Dec 10, 2019
@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch 2 times, most recently from 7b93385 to e68f694 Compare December 12, 2019 00:48
@mkeskells
Copy link
Contributor Author

mkeskells commented Dec 12, 2019

Note - perf data is outdated - waiting on a new run, after improvements - but for info the old results were in https://gist.github.com/mkeskells/a3ec74d3dd79c6564ac957c8387c8fda

as summary here though, some or it unexpected

based on these benchmarks this PR is generally faster, for this ++
for opContainedWithLarge (sharing makes no difference for this test) roughly a 10x speedup
this is roughly subset ++ superset
before

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0      10  avgt   20       9744.980 ▒      142.660  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0     100  avgt   20     151431.353 ▒     9977.708  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0    1000  avgt   20    1809340.500 ▒    20265.960  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0   10000  avgt   20   24871168.850 ▒  1172582.403  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0  100000  avgt   20  307419271.995 ▒ 11660918.310  ns/op

after

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0      10  avgt   20        422.985 ▒       3.649  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0     100  avgt   20      11030.800 ▒     305.684  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0    1000  avgt   20      97682.239 ▒    9048.045  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0   10000  avgt   20    1659434.234 ▒   27740.896  ns/op
[info] HashSetPlusPlusBenchmark.opContainedWithLarge          0  100000  avgt   20   27916736.500 ▒ 2031136.722  ns/op

for opEmptyWithContained ( roughly HashSet.empty ++ set) the result is constant time
again sharing doesnt mean anything
before

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0      10  avgt   20       7974.439 ▒      736.207  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0     100  avgt   20     159306.679 ▒     9042.004  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0    1000  avgt   20    1786056.237 ▒    10988.317  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0   10000  avgt   20   24018732.300 ▒   559415.648  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0  100000  avgt   20  304566590.500 ▒  6853616.865  ns/op

after

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0      10  avgt   20          6.147 ▒       0.325  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0     100  avgt   20          5.943 ▒       0.089  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0    1000  avgt   20          6.082 ▒       0.208  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0   10000  avgt   20          5.993 ▒       0.106  ns/op
[info] HashSetPlusPlusBenchmark.opEmptyWithContained          0  100000  avgt   20          5.925 ▒       0.052  ns/op

for opLargeWithContained ( roughly superset ++ subset), where the subset has the same values, but no structural sharing, is slower, more than 50% slower
before

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0      10  avgt   20        359.570 ▒        2.689  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0     100  avgt   20       5847.542 ▒      339.311  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0    1000  avgt   20      84791.967 ▒     6336.078  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0   10000  avgt   20    1463687.727 ▒    70435.294  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0  100000  avgt   20   17498114.320 ▒  2309792.760  ns/op

after

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0      10  avgt   20        417.390 ▒       4.413  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0     100  avgt   20       9560.103 ▒      71.380  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0    1000  avgt   20      90247.421 ▒    3834.973  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0   10000  avgt   20    1617386.403 ▒   27404.463  ns/op
[info] HashSetPlusPlusBenchmark.opLargeWithContained          0  100000  avgt   20   28008580.875 ▒ 1885487.745  ns/op

opLargeWithEmpty is trivial in both cases
opWithDistinct - adding non overlapping sets is about 3x faster
before

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opWithDistinct                0      10  avgt   20        673.877 ▒        5.234  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0     100  avgt   20      13343.080 ▒      428.135  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0    1000  avgt   20     175657.800 ▒     1678.009  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0   10000  avgt   20    2175801.267 ▒     7979.342  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0  100000  avgt   20   27818889.500 ▒   270538.247  ns/op

after

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opWithDistinct                0      10  avgt   20        211.629 ▒       1.629  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0     100  avgt   20       2963.875 ▒       9.130  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0    1000  avgt   20      53410.397 ▒    2963.410  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0   10000  avgt   20     668198.521 ▒   16270.055  ns/op
[info] HashSetPlusPlusBenchmark.opWithDistinct                0  100000  avgt   20    9144742.953 ▒  850943.160  ns/op

for opWithOverlap adding 2 set that partially overlap, but don't share structure 2-3 x faster
before

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0      10  avgt   20       2767.593 ▒      160.711  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0     100  avgt   20      70057.189 ▒     2797.278  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0    1000  avgt   20    1008365.163 ▒     3398.963  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0   10000  avgt   20   10091560.364 ▒    73586.293  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0  100000  avgt   20  109812473.000 ▒  7878452.591  ns/op

after

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0      10  avgt   20       1629.035 ▒       8.210  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0     100  avgt   20      21293.519 ▒     533.269  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0    1000  avgt   20     298748.476 ▒   10874.936  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0   10000  avgt   20    3936182.795 ▒   22880.808  ns/op
[info] HashSetPlusPlusBenchmark.opWithOverlap                 0  100000  avgt   20   38748065.167 ▒  882330.327  ns/op

opWithShared is similar to opWithOverlap, but with shared structure. Here the degree of structural sharing makes a difference. in general is seems to be 3-4 x faster

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opWithShared                  0      10  avgt   20      11797.167 ▒       86.355  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0     100  avgt   20     200615.259 ▒     4293.505  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0    1000  avgt   20    2311063.558 ▒   220145.374  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0   10000  avgt   20   29573128.750 ▒   122618.359  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0  100000  avgt   20  401452987.000 ▒ 14649242.141  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 20  100000  avgt   20  333808538.495 ▒  4499989.419  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 40  100000  avgt   20  286483932.505 ▒  6256244.707  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 60  100000  avgt   20  230013784.000 ▒  3560713.801  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 80  100000  avgt   20  165039797.000 ▒  3532941.437  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 90  100000  avgt   20  150678145.000 ▒  5145690.234  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                100  100000  avgt   20  110482294.250 ▒  7684039.614  ns/op

after

[info] Benchmark                                      (sharing)  (size)  Mode  Cnt          Score          Error  Units
[info] HashSetPlusPlusBenchmark.opWithShared                  0      10  avgt   20       2883.413 ▒      36.261  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0     100  avgt   20      52635.662 ▒     117.919  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0    1000  avgt   20     541960.551 ▒   25599.479  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0   10000  avgt   20    9532744.977 ▒  415512.332  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                  0  100000  avgt   20  116030047.000 ▒ 9916748.064  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 20  100000  avgt   20  107303203.750 ▒ 8584760.299  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 40  100000  avgt   20   95388560.750 ▒ 6409158.068  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 60  100000  avgt   20   76985525.500 ▒ 3989717.795  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 80  100000  avgt   20   60160765.500 ▒ 3734495.243  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                 90  100000  avgt   20   51376904.500 ▒ 2859621.033  ns/op
[info] HashSetPlusPlusBenchmark.opWithShared                100  100000  avgt   20          6.031 ▒       0.172  ns/op

@diesalbla diesalbla added the library:collections PRs involving changes to the standard collection library label Dec 13, 2019
@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch 2 times, most recently from ab40692 to 81ff240 Compare December 16, 2019 23:18
@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch from 81ff240 to df747da Compare December 28, 2019 23:54
@mkeskells
Copy link
Contributor Author

improved, and rebased

@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch 4 times, most recently from 1027a00 to b480b04 Compare December 31, 2019 19:37
@mkeskells
Copy link
Contributor Author

@joshlemer can you have a look as you did lots of work on the 2.13 maps and sets

@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch 2 times, most recently from 644b86d to 01a4794 Compare January 1, 2020 18:06
@szeiger
Copy link
Contributor

szeiger commented Jan 3, 2020

I'm surprised that adding overlapping sets without structural sharing is 50% slower whereas adding distinct sets is 3x faster. This doesn't sound right. Why would they behave differently if the difference lies in the treatment of structural sharing (which doesn't apply in either case)?

@mkeskells
Copy link
Contributor Author

Overlapping set contain the same values, so hit the same hashcodes and therefore HashSet1 merge, whereas the distinct just create merge HashSet1 into a new Trie, or sometime merge a whole Trie because none of the hashes collide in any of its children
Overlapping tests have to test equality of the values, non overlapping set shouldn't (if we don't collide hashes)

The benchmarks are out of date though, and I did some work on the merging of the distinct cases to improve the performance. I should have the results in the 24 hours

@szeiger
Copy link
Contributor

szeiger commented Jan 23, 2020

@mkeskells Do you have the new benchmark results?

@mkeskells
Copy link
Contributor Author

mkeskells commented Jan 23, 2020

@szeiger I have some, but from running on a laptop and some are less stable then I hoped
@rorygraves is getting a better set ATM. To run the full dataset takes 36 hours for the baseline, and another 36 for the post PR

the results and a comparison are
https://1drv.ms/x/s!Am3TAKFvNVy_m1xzYxrldZKiY_Jx?e=zIidmA

On sheet 3 - change the dataset and the operation to see the effect
not all combinations are valid - the unshared benchmark only has one of the data points, because they dont have the degree of how much is shared

I am less sure on how to visualise the data, what we have is a range of different sizes and datasets, and operations, and CPU/allocation to compare

the operations tested are

'+'
'--'
'-'
'++'
'--'
'union'
'diff'
'intersect'
'subsetOf'
'sameElements'
 
on datasets of
Shared benchmarks -
opWithOverlapShared - operations on data with structural sharing
opWithOverlapUnshared - operation on data with shared values, but not shared HashSet structure

both of the above operate for different amounts of overlap

opContainedSharedWithData - subset with a superset (e.g. smallSet ++ superset), with structural sharing
opContainedUnsharedWithData - subset with a superset (e.g. smallSet ++ superset), without structural sharing, just shared values
opDataWithContainedShared - superset with subset (e.g. superset ++ smallSet ), with structural sharing
opDataWithContainedUnshared - superset with subset (e.g. superset ++ smallSet ), without structural sharing
opDataWithEmpty - e.g. set ++ HashSet.empty
opDataWithSetEmpty - e.g. set ++ Set.empty
opEmptyWithData - e.g. HashSet.empty ++ set
opSetEmptyWithData - Set.empty ++ set
opWithDistinct - set ++ otherSet

The tests allow to data that heavily collides on hashcode , or doesn't, but I don't have the data for colliding data yet

so for ++, opWithOverlapShared data the results for different degrees of sharing are

    size 10 100 1000 10000
    old NS 13,370 188,566 2,176,863 31,044,478
    new NS 3,028 49,987 487,436 9,043,898
    old all 26,374 377,310 4,729,831 59,706,222
    new all 2,653 27,777 228,820 2,590,108
             
             
duration improvement 77.35% 73.49% 77.61% 70.87%
allocation improvement 89.94% 92.64% 95.16% 95.66%
             
             
             
             
    size 10 100 1000 10000
    old NS 12,758 164,534 1,873,404 28,038,742
    new NS 2,799 46,087 462,166 8,294,675
    old all 20,862 299,673 3,760,636 47,639,448
    new all 2,346 21,873 186,546 2,235,285
             
             
duration improvement 78.06% 71.99% 75.33% 70.42%
allocation improvement 88.75% 92.70% 95.04% 95.31%
             
             
             
             
    size 10 100 1000 10000
    old NS 9,699 143,708 1,564,424 23,910,666
    new NS 2,402 41,425 419,419 7,498,449
    old all 15,478 222,370 2,801,492 35,628,647
    new all 1,975 16,034 149,434 1,810,038
             
             
duration improvement 75.23% 71.17% 73.19% 68.64%
allocation improvement 87.24% 92.79% 94.67% 94.92%
             
             
             
             
    size 10 100 1000 10000
    old NS 8,141 111,515 1,213,250 19,219,547
    new NS 2,126 34,545 350,257 6,563,930
    old all 10,204 146,391 1,854,356 23,679,103
    new all 1,391 10,474 116,687 1,270,059
             
             
duration improvement 73.88% 69.02% 71.13% 65.85%
allocation improvement 86.37% 92.85% 93.71% 94.64%
             
             
             
             
    size 10 100 1000 10000
    old NS 6,022 82,897 874,119 13,959,189
    new NS 1,537 25,691 273,990 4,693,990
    old all 5,042 72,263 919,776 11,797,190
    new all 665 6,094 76,314 619,438
             
             
duration improvement 74.48% 69.01% 68.66% 66.37%
allocation improvement 86.82% 91.57% 91.70% 94.75%
             
             
             
             
    size 10 100 1000 10000
    old NS 3,903 74,473 926,667 18,337,603
    new NS 9 9 9 9
    old all 40 40 42 68
    new all 0 0 0 0
             
             
duration improvement 99.78% 99.99% 100.00% 100.00%
allocation improvement 100.00% 100.00% 100.00% 100.00%

@mkeskells
Copy link
Contributor Author

mkeskells commented Jan 23, 2020

all of the ++ operation look good to me, with one exception dataWithContainedShared/unshared
both show a blip on the wall time for size 100 case that I have not had time to look at

    size 10 100 1000 10000
    old NS 333 5,218 122,479 1,481,221
    new NS 217 6,672 65,459 762,688
    old all 40 40 40 42
    new all 0 0 0 1
             
             
duration improvement 34.88% -27.87% 46.55% 48.51%
allocation improvement 100.00% 99.97% 99.73% 97.02%

generally there is a marked improvement for '++' sameElements,
measureable improvement for 'union',
not much difference for '-', '--', intersect

but there are some anomalies e.g. for diff on large sets on opWithOverlapShared

e.g.

    size 10 100 1000 10000
    old NS 2,275 37,495 431,304 5,654,334
    new NS 2,359 38,364 451,010 7,090,971
    old all 1,132 6,408 62,070 585,699
    new all 1,091 6,026 57,986 565,573
             
             
duration improvement -3.71% -2.32% -4.57% -25.41%
allocation improvement 3.60% 5.95% 6.58% 3.44%

Copy link
Contributor

@szeiger szeiger left a comment

Choose a reason for hiding this comment

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

Performance looks good. I couldn't figure out how to use the Excel sheet (there's one data set at the top of sheet 3 and a lot of invalid fields, strangely enough with error messages in German, below) but I prefer a summary of the interesting cases anyway.

var res = HashSet.this
override def apply(v1: A): Unit = res += v1
}
that foreach acc
Copy link
Contributor

Choose a reason for hiding this comment

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

foreach can run in parallel for a ParSet. This implementation should be restricted to Set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

reworked as

        if (that.isInstanceOf[Set[A]])
          that foreach acc
        else
          that.iterator foreach acc

@mkeskells
Copy link
Contributor Author

to use the spreadsheet - there are 2 cells at the top or sheet 3 with a border around them
if you change the values ( there is a pick list) then the rest change

When you only get one value, its a test which doesn't have the degree of sharing, e.g. opWithDistinct - if the data sets are distinct, there is no sharing

@szeiger
Copy link
Contributor

szeiger commented Jan 27, 2020

@mkeskells If I enable editing by copying the file to my account, I can open the dropdown menus but whatever I change them to, I get "illegal value" errors (with the error message in German, despite my account and my system settings all being set to US-English) in all fields that previously contained values.

@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch 2 times, most recently from a0a2e6a to 1182fa7 Compare January 30, 2020 08:07
@mkeskells
Copy link
Contributor Author

Haha if you are confused by it it makes me feel less stupid.

@lrytz lrytz force-pushed the mike/2.12.x_quickHashSetUnion branch from 1182fa7 to 1075a9c Compare January 31, 2020 13:32
Copy link
Contributor

@szeiger szeiger left a comment

Choose a reason for hiding this comment

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

Needs to be squashed and rebased but apart from that it looks good to go.

An additional scalacheck test might be a good idea. While there are tests for all the individual cases in the implementation, there is no guarantee that the test data actually utilizes the intended branch. This is a situation where I would go for property-based testing with generated data.

@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch from e95f30f to a70006b Compare February 3, 2020 19:20
EmptySet and SetBuilder adjusted to take advantage of ++ optimisation for simple and common cases

eliminate unneeded allocations for HashSets where the result is already built for -
   subSet ++ superSet
   subSet union superSet
   superSet union subSet
   superSet ++ subSet

make a fast path when there is structural sharing in the HashSet
for union, guarantee internal operations will only return a new HashSet if one of the existing HashSet parameters or internal values cant be used

use System.arraycopy rather than Array.copy as it avoid JVM nulling the array

add missing `eq` fast path to intersect0 and diff0

minor improvements to + to reduce allocations

reduce calls to HashSet.size which can be a bottleneck

reduce allocations in ListSet ++, + and -
no allocations in ++ or + if the data is contained in the original sets

HashSetCollision1 stores its length so as to avoid calls to ListSet.size which is O(n)
take advantage of ListSet guarantees on identity for return or + or - where we can
avoid calling ListSet.size where we can avoid it
@mkeskells mkeskells force-pushed the mike/2.12.x_quickHashSetUnion branch from a70006b to 2613eda Compare February 3, 2020 20:32
@retronym retronym self-requested a review February 4, 2020 12:03
@SethTisue SethTisue changed the title faster ++ and union for HashSet [nomerge] faster ++ and union for HashSet Feb 4, 2020
@retronym retronym merged commit 2ef4ac3 into scala:2.12.x Feb 17, 2020
@SethTisue SethTisue added the performance the need for speed. usually compiler performance, sometimes runtime performance. label Feb 18, 2020
retronym added a commit to retronym/scala that referenced this pull request Feb 28, 2020
retronym added a commit to retronym/scala that referenced this pull request Feb 28, 2020
retronym added a commit to retronym/scala that referenced this pull request Feb 28, 2020
@retronym
Copy link
Member

retronym commented Mar 3, 2020

We've just realised that this optimization is wrong for subclasses of HashSet that override elemHashCode. We need to introduce a defensive guard that the operands are exactly immutable.HashMap (or maybe they are exactly the same as each other?)

/cc @mkeskells

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

Labels

library:collections PRs involving changes to the standard collection library performance the need for speed. usually compiler performance, sometimes runtime performance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants