-
Notifications
You must be signed in to change notification settings - Fork 531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance issue with HList
's Filter
#596
Comments
Would you like to take a swing at trying to speed it up? |
Thanks for your response, I'll try to do dig it down a bit. |
I don't know why this happens or what to do about it but I just want to mention that the exponential growth in compilation time can be easily measured with the newly added val times = List(
compileTime("Filter[HNil, _0]"),
compileTime("Filter[_1 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: _3 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: _3 :: _4 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: _3 :: _4 :: _5 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: HNil, _0]"),
compileTime("Filter[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: HNil, _0]")
)
scala> times.map(_.toMillis).zipWithIndex.map(t => t._2 + " " + t._1).mkString("\n")
res0: String =
0 9
1 16
2 65
3 164
4 497
5 1594
6 4287
7 13989
8 55662 |
I played a little bit more with |
In your examples this must be because of using typed numbers. For non-recursive types I get more reasonable and non-exponential results:
while for recursive numbers my times are similar to yours:
|
The problem persists even on non-recursive types e.g. type L9 = _0 :: _0 :: _0 ::
_0 :: _0 :: _0 ::
_0 :: _0 :: _0 ::
HNil
type L10 = _0 :: L9
type L11 = _0 :: L10
type L12 = _0 :: L11
type L13 = _0 :: L12
val times = List(
compileTime("Filter[L9, _0]"),
compileTime("Filter[L10, _0]"),
compileTime("Filter[L11, _0]"),
compileTime("Filter[L12, _0]"),
compileTime("Filter[L13, _0]")
) still exponential time:
|
I am not sure if this is related specifically to So far its been compiling for over 5 minutes |
These sorts of compile times aren't completely out of line with my expectations for filter. Is this really an operation you would expect to be performing on a DB row though? If you can say what you're trying to achieve at a higher level I might be able to suggest an alternative. |
I just hit this issue today - I'm still pretty new to using this stuff but I had my hand at a simpler reimplementation of Filter that doesn't use Partition under-the-hood. The main difference is that this implementation uses an implicit I've run comparisons for the various tests above (with Scala 2.12.5, shapeless 2.3.3): #596 (comment)
Non-recursive types
If this looks good, I'd be happy to also implement FilterNot and open up a PR! EDIT: I'm going to try reimplementing Partition to use |
cc: @milessabin Quick follow-up: the change to Partition worked as well. Here are the results when rerunning the tests from #596 (comment)
Additionally, I did notice the stale PR #682 doing something similar. I can piggyback off of that and complete the work. Luckily, it seems that the reimplementation of Partition would allow us to leave Filter/FilterNot alone, for now. However, I'm still running some tests on the Filter derived from Partition with the new implementation. For completeness, the test: {
import shapeless.ops.hlist.Partition
import shapeless.nat._
val times = List(
compileTime("Partition[HNil, _0]"),
compileTime("Partition[_1 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: HNil, _0]")
)
println(times.map(_.toMillis).zipWithIndex.map(t => t._2 + " " + t._1).mkString("\n"))
println()
val times2 = List(
compileTime("FasterPartition[HNil, _0]"),
compileTime("FasterPartition[_1 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: _3 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: _3 :: _4 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: _3 :: _4 :: _5 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: HNil, _0]"),
compileTime("FasterPartition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: HNil, _0]")
)
println(times2.map(_.toMillis).zipWithIndex.map(t => t._2 + " " + t._1).mkString("\n"))
} |
This is great stuff ... thanks! What Scala version are these benchmarks relative to? I'd be really interested to see if there's a significant difference in compile time between 2.12.6 and 2.12.7 in this case. |
Ahh ... just seen that you mention 2.12.5. Please try the benchmark with 2.12.7 ... I anticipate a significant speedup due to scala/scala#7067. It's likely that this reworking of filter and partition will still be worth doing, but it'd be good to have up to date numbers. |
Of course! I should've bumped it initially; had it on 2.12.5 to get parity with the application in which I saw slowness. The 2.12.7 numbers for the existing implementation are much more encouraging (and much less so for the version using I've included a few more test targets this time so we can get a feel. compileTime("Partition[HNil, _0]"),
compileTime("Partition[_1 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: _0 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: _0 :: _0 :: HNil, _0]"),
compileTime("Partition[_1 :: _2 :: _3 :: _4 :: _5 :: _6 :: _7 :: _8 :: _0 :: _0 :: _0 :: HNil, _9]")
Interestingly, the new Partition that uses neither =:= nor =:!= still does quite well in the non-recursive, long list test: import shapeless.nat._
type L9 = _0 :: _0 :: _0 ::
_0 :: _0 :: _0 ::
_0 :: _0 :: _0 ::
HNil
type L10 = _0 :: L9
type L11 = _0 :: L10
type L12 = _0 :: L11
type L13 = _0 :: L12
val times = List(
compileTime("Partition[L9, _0]"),
compileTime("Partition[L10, _0]"),
compileTime("Partition[L11, _0]"),
compileTime("Partition[L12, _0]"),
compileTime("Partition[L13, _0]")
)
println(times.map(_.toMillis).zipWithIndex.map(t => t._2 + " " + t._1).mkString("\n"))
val times2 = List(
compileTime("FasterPartition[L9, _0]"),
compileTime("FasterPartition[L10, _0]"),
compileTime("FasterPartition[L11, _0]"),
compileTime("FasterPartition[L12, _0]"),
compileTime("FasterPartition[L13, _0]")
)
println(times2.map(_.toMillis).zipWithIndex.map(t => t._2 + " " + t._1).mkString("\n"))
|
That looks good :-) I've lost track a bit ... are things good with 2.12.7 as they are now, or are there any improvements which still need to be made? |
So things look mostly good with 2.12.7 to me. The one concerning thing I found was that final test. For some reason, the new implementation is still performing much better. It's a bit unclear to me what characteristics about that final test are making the existing implementation still perform so slowly. It's interesting because that behavior isn't exhibited when the target list of the same length but using another type, but this could be separate issue entirely. |
Confirmed that this issue is fixed on 2.12.7 for me. I was previously on Typelevel scala 2.12.4-bin-typelevel-4. |
@chrisbenincasa do you think there's still value in PR'ing any or all of your changes? If not I'll close this. |
@milessabin happy to push my branch up for the diff, but from what I can tell the compilation time issue was resolved in 2.12.7 like you mentioned. |
Fixed by #682 |
It seems there is a problem with
shapeless.ops.hlist.Filter
where the compiler takes too much time to filter on a very shortHList
e.g. on anHList
of 10Nat
it takes almost 3 mins to compile.Reproduced code:
The text was updated successfully, but these errors were encountered: