A couple of days ago, I gave a talk at Boston Haskell about a shiny new speculative evaluation library, speculation on hackage, that I have implemented in Haskell. The implementation is based on the material presented as "Safe Programmable Speculative Parallelism" by Prakash Prabhu, G Ramalingam, and Kapil Vaswani at last month's PLDI.
I've uploaded a copy of my slides here:
* Introducing Speculation [PowerPoint | PDF]
This package provides speculative function application and speculative folds. Speculative STM transactions take the place of the transactional rollback machinery from the paper, but transactions are not always required in pure code. To get a feel for the shape of the library, here is an excerpt from the documentation for one of the combinators:
spec :: Eq a => a -> (a -> b) -> a -> b
spec g f aevaluatesf gwhile forcinga, ifg == athenf gis returned, otherwisef ais evaluated and returned. Furthermore, if the argument has already been evaluated, we skip thef gcomputation entirely. If a good guess at the value ofais available, this is one way to induce parallelism in an otherwise sequential task. However, if the guess isn't available more cheaply than the actual answer, then this saves no work and if the guess is wrong, you risk evaluating the function twice. Under high load, sincef gis computed via the spark queue, the speculation will be skipped and you will obtain the same answer asf $! a.
ASCII art time-lines of how this can speed up evaluation are available in both the slides and the documentation linked to above, but assuming an otherwise serial problem, you effectively wager otherwise idle CPU time and the time to generate your guess on the quality of your guess.
Note that numSparks# feature request that was mentioned in the slides has already been implemented in GHC HEAD, and support shall be added to improve the performance of the speculative STM transactions under high load as mentioned in the slides.
Discussion
@Asger: You could likely exploit the speculation primitive if you supply your own equality check that checked fitness instead of value and used it to wrap the continuation, but I’ll admit I haven’t thought it through in depth.
@Pepe: There are a couple of things happening here.
You definitely shouldn’t be getting segfaults on the unboxed version. If so, then I have faulty reasoning and will need to revert to the boxed version.
With regards to the boxed version still showing unevaluated-ness, what you’re experiencing is that the ‘i’ value at that point still points to the indirection because a garbage collection hasn’t happened yet to propagate the tag bits. I’d hazard that if you inserted a call to
performGcin the middle there that you’d start seeing the tags.Ed,
thanks for the tips. I tried inserting a call to
performGCin the example, but it didn’t help the boxed version.main = do let i = trace "–> evaluated" () putStrLn $ "tagbits: " ++ show (unsafeGetTagBits i) print i performGC >> putStrLn "–> GC performed" putStrLn $ "tagbits: " ++ show (unsafeGetTagBits i)Nevertheless, I have gone back to my actual use case (pruning the non-evaluated branches of an incomplete infinite proof tree, for display purposes), and found I was traversing the tree bottom-up, with a non strict fold. In a bottom-up traversal, the boxed version was *correctly* returning
Falsefor subtrees (since the fold is lazy), whereas the unboxed version was actuallyseq’ing subtrees and returningTrue, which in the particular example at hand was what I expected. Hence the unboxed version was hiding an actual mistake in my reasoning. Now the boxed version works as expected with a top-down traversal.To see why the unboxed version forces evaluation of the argument, let’s look at the core generated by ghc:
There is a case expression where the scrutinee is the argument. Unless I am wrong, this case expression forces the evaluation of the argument.
If you are curious I also cooked a version of
unsafeIsEvaluatedwhich avoids the tricky pointer tagging business and looks directly at the info table: http://gist.github.com/504764I was deliberately avoiding reading the info table to avoid dealing with variations in ghc compilation options changing the layout.
I’ll revert to the boxed version for now I guess and then see if I can get a better way to do the
unsafeCoerce#without forcing.That said, it might be worthwhile to add that as a second check, assuming we can get something like the unboxed version to work in the first place.
That way it could check the tag bits, and if zero, before giving up it could double check the info table, that way we avoid having to actually use the target object in case where the tag bits having been propagated, which potentially avoids paging it in.
FYI, I did wind up adding that info table check to the tag-bits library, although I do so only after checking the tag bits themselves as a fallback.
It works well, and lets the library be useful even from ghci.
Thanks!
Apparently,
undefinedwill sometimes claim to be evaluated. Now I need to chase bottoms too!