-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Multithreaded evaluator #10938
base: master
Are you sure you want to change the base?
Multithreaded evaluator #10938
Changes from 46 commits
6760b39
d3854d1
945cd69
5f3b1a3
9ddca98
d133aca
1a55754
d623dfb
a9e3594
b63a132
76f822f
6a85af7
6eafc52
f018a55
ec8593d
105dea5
27fb652
d990974
eba54f5
a25a5b7
ca11328
c2c01d8
9b88021
708e0e8
cc38822
424e01e
c663076
adcc351
3988faf
a70ec9e
0cd29fe
0c87ead
33f50ae
5e87cf4
400a670
5c6eb1a
3353f9a
9b814c4
fd5c32b
1bdf907
3cc1319
f6cbd6a
6103246
576a03e
52bd994
b713591
fcbdc9f
997af66
d3397d7
2b4c36f
d131a02
4482fe4
8cf80c9
67ff326
c8c9500
dd44b26
9102baf
998a289
5310b0f
839aec2
4f90786
6357885
4086c1c
a6d8217
ea4e981
d36ea2e
114d1a0
f947b63
ceeb648
8b7d5b4
8020c0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,8 @@ Value * EvalState::allocValue() | |
GC_malloc_many returns a linked list of objects of the given size, where the first word | ||
of each object is also the pointer to the next object in the list. This also means that we | ||
have to explicitly clear the first word of every object we take. */ | ||
thread_local static std::shared_ptr<void *> valueAllocCache{std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)}; | ||
|
||
if (!*valueAllocCache) { | ||
*valueAllocCache = GC_malloc_many(sizeof(Value)); | ||
if (!*valueAllocCache) throw std::bad_alloc(); | ||
|
@@ -62,6 +64,8 @@ Env & EvalState::allocEnv(size_t size) | |
#if HAVE_BOEHMGC | ||
if (size == 1) { | ||
/* see allocValue for explanations. */ | ||
thread_local static std::shared_ptr<void *> env1AllocCache{std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)}; | ||
|
||
if (!*env1AllocCache) { | ||
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *)); | ||
if (!*env1AllocCache) throw std::bad_alloc(); | ||
|
@@ -84,9 +88,33 @@ Env & EvalState::allocEnv(size_t size) | |
[[gnu::always_inline]] | ||
void EvalState::forceValue(Value & v, const PosIdx pos) | ||
{ | ||
auto type = v.internalType.load(std::memory_order_acquire); | ||
|
||
if (isFinished(type)) | ||
goto done; | ||
|
||
if (type == tThunk) { | ||
try { | ||
if (!v.internalType.compare_exchange_strong(type, tPending, std::memory_order_acquire, std::memory_order_acquire)) { | ||
if (type == tPending || type == tAwaited) { | ||
waitOnThunk(v, type == tAwaited); | ||
goto done; | ||
} | ||
if (isFinished(type)) | ||
goto done; | ||
printError("NO LONGER THUNK %x %d", this, type); | ||
abort(); | ||
} | ||
Env * env = v.payload.thunk.env; | ||
Expr * expr = v.payload.thunk.expr; | ||
expr->eval(*this, *env, v); | ||
} catch (...) { | ||
v.mkFailed(); | ||
throw; | ||
} | ||
} | ||
#if 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be a bit rusty on this, but GHC black holes store a thread id (thread pointer?) in its blackholes. |
||
if (v.isThunk()) { | ||
Env * env = v.payload.thunk.env; | ||
Expr * expr = v.payload.thunk.expr; | ||
try { | ||
v.mkBlackhole(); | ||
//checkInterrupt(); | ||
|
@@ -97,8 +125,31 @@ void EvalState::forceValue(Value & v, const PosIdx pos) | |
throw; | ||
} | ||
} | ||
else if (v.isApp()) | ||
callFunction(*v.payload.app.left, *v.payload.app.right, v, pos); | ||
#endif | ||
else if (type == tApp) { | ||
try { | ||
if (!v.internalType.compare_exchange_strong(type, tPending, std::memory_order_acquire, std::memory_order_acquire)) { | ||
if (type == tPending || type == tAwaited) { | ||
waitOnThunk(v, type == tAwaited); | ||
goto done; | ||
} | ||
if (isFinished(type)) | ||
goto done; | ||
printError("NO LONGER APP %x %d", this, type); | ||
abort(); | ||
} | ||
callFunction(*v.payload.app.left, *v.payload.app.right, v, pos); | ||
} catch (...) { | ||
v.mkFailed(); | ||
throw; | ||
} | ||
} | ||
else if (type == tPending || type == tAwaited) | ||
type = waitOnThunk(v, type == tAwaited); | ||
|
||
done: | ||
if (type == tFailed) | ||
std::rethrow_exception(v.payload.failed->ex); | ||
} | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally the thread could pick up other work from a collection of thunks that are likely needed.
There's latent parallelism in functions like
foldl'
,toString
andderivationStrict
that we could exploit this way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And there is parallelism to exploit in vector operations like ExprConcatStrings and all the binary operators, provided we relax the strictness assumptions of these operators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not all binary operators; for instance
&&
is conditionally lazy in its second operand. We'd need speculative evaluation for those, which is absolutely feasible, but adds a cross cutting concern, so I wouldn't go there first.String concatenation definitely belongs in that list, regardless of which part of the code does it, and we might want to move the actual concatenation out of
ExprConcatStrings
to make #6530 deterministic and pure.