-
Notifications
You must be signed in to change notification settings - Fork 123
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
Word eval refactor #1136
Word eval refactor #1136
Conversation
I think this refactoring has reached a pretty good point to merge. The main goals of this were to factor out the With the performance considerations in mind, this PR contains a lot of small, self-contained commits and I spent a lot of time performance testing after each change. It should be relatively straightforward to bisect for performance problems inside this PR, as I believe each commit leaves the project in a testable state. At the end, the benchmarks I tried ended up performance neutral, or slightly faster than they started, with the exception of the Karatsuba example, which slowed down somewhat due to a lazier Along the way, I generalized the type of the The next stages of refactoring are quite a bit more speculative, but should hopefully be largely contained within the |
0c4c025
to
d842145
Compare
b39bab1
to
7bcc47e
Compare
Instead of every `WordValue` being wrapped in the `SEval` monad, create a separate `ThunkWordVal` constructor that captures exactly when lazy behavior is needed. This simplifies several things, and makes it more clear some places where we can actually be stricter in the spine of word values.
It is now lazier in its arguments when required.
This brings the evaluator more in line with the reference semantics, although there may still be some differences.
into the `Value` module. Remove most uses of the `WordValue` constructors in other modules, but don't quite make the type abstract yet.
This allows us to generalize the type of `take` and simplifies the implementations.
Make sure they are sufficently lazy in their arguments. Join is still to eager in some cases, but that will require a larger refactoring.
Introduce the concept of "index segments", which allows us to delay deciding if index values should be treated as packed or unpacked until they are consumed in an indexing operation.
Memoization now only occurs in the barrel shifter when a symbolic bit is encountered, instead of at every shift.
as the symbolic simulators.
This breaks the test for issue211, but we were cheating on it anyway.
Update it's type to prepare for upcoming changes
We should either decide that the semantics of `#` requires it to be strict in its arguments, or we should fix the definition.
Replace it with `takeWordVal` and `dropWordVal` instead.
This should eliminte the possiblity of building up chains of thunked word values.
This lets us share work, and lets us optimistically treat the word as packed if we know it has already been forced. It also lets us delay unpacking decisions.
With the lazier join operator, this squashes a space leak in examples that do a lot of hashing.
Also, test for looping computations in addition to `error` computations. This doesn't let us cheat by eta-expanding the error, as we were doing before.
I think we've finally cracked the nut on this strictness bug. Fixes #640
7bcc47e
to
aac42f4
Compare
The main benefit of this reorganization is that it notices when a memoized `SeqMap` has been forced at all of its locations. This allows us to discard the underlying computation, which will never need to be consulted again. This, in turn, allows the garbage collector to reclaim the associated memory and help prevent certain classes of space leaks.
Refactor various aspects of the evaluator, mostly dealing with the packed word representation, but also aiming to remove the eta-delay mechanism, which should no longer be required with the current evaluation semantics. The main trick, I think, is to find a way to implement sufficiently-lazy sequence primitives without also introducing unnecessary unpacking or thunking.