Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Concurrency] TaskLocals lookup "skip" optimization
- Loading branch information
Showing
10 changed files
with
309 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -139,13 +139,13 @@ class ActiveTaskStatus { | |
/// | ||
/// +--------------------------+ | ||
/// | childFragment? | | ||
/// | taskLocalValuesFragment | | ||
/// | taskLocalValuesFragment? | | ||
/// | groupFragment? | | ||
/// | futureFragment? |* | ||
/// +--------------------------+ | ||
/// | ||
/// The future fragment is dynamic in size, based on the future result type | ||
/// it can hold, and thus must be the *last* fragment. | ||
/// * The future fragment is dynamic in size, based on the future result type | ||
/// it can hold, and thus must be the *last* fragment. | ||
class AsyncTask : public HeapObject, public Job { | ||
public: | ||
/// The context for resuming the job. When a task is scheduled | ||
|
@@ -217,7 +217,7 @@ class AsyncTask : public HeapObject, public Job { | |
return reinterpret_cast<ChildFragment*>(this + 1); | ||
} | ||
|
||
// ==== Task Locals Values-- ------------------------------------------------- | ||
// ==== Task Locals Values --------------------------------------------------- | ||
|
||
class TaskLocalValuesFragment { | ||
public: | ||
|
@@ -230,8 +230,14 @@ class AsyncTask : public HeapObject, public Job { | |
IsTerminal = 0b00, | ||
/// The storage pointer points at the next TaskLocalChainItem in this task. | ||
IsNext = 0b01, | ||
/// The storage pointer points at a parent AsyncTask, | ||
/// in which we should continue the lookup. | ||
/// The storage pointer points at a parent AsyncTask, in which we should | ||
/// continue the lookup. | ||
/// | ||
/// Note that this may not necessarily be the same as the task's parent | ||
/// task -- we may point to a super-parent if we know / that the parent | ||
/// does not "contribute" any task local values. This is to speed up | ||
/// lookups by skipping empty parent tasks during get(), and explained | ||
/// in depth in `createParentLink`. | ||
IsParent = 0b11 | ||
}; | ||
|
||
|
@@ -272,23 +278,42 @@ class AsyncTask : public HeapObject, public Job { | |
/// the TaskLocalItem linked list into the appropriate parent. | ||
static TaskLocalItem* createParentLink(AsyncTask *task, AsyncTask *parent) { | ||
assert(parent); | ||
assert(parent->hasTaskLocalValues()); | ||
assert(task->hasTaskLocalValues()); | ||
size_t amountToAllocate = TaskLocalItem::itemSize(/*valueType*/nullptr); | ||
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this? | ||
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator | ||
fprintf(stderr, "MALLOC parent link item: %d\n", allocation); | ||
|
||
TaskLocalItem *item = | ||
new(allocation) TaskLocalItem(nullptr, nullptr); | ||
|
||
auto next = parent->localValuesFragment()->head; | ||
auto nextLinkType = next ? NextLinkType::IsParent : NextLinkType::IsTerminal; | ||
item->next = reinterpret_cast<uintptr_t>(next) | | ||
static_cast<uintptr_t>(nextLinkType); | ||
|
||
fprintf(stderr, "error: %s [%s:%d] created parent item: task=%d -> parentTask=%d :: item=%d -> item->getNext()=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__, | ||
task, parent, item, item->getNext()); | ||
auto parentHead = parent->localValuesFragment()->head; | ||
if (parentHead) { | ||
if (parentHead->isEmpty()) { | ||
switch (parentHead->getNextLinkType()) { | ||
case NextLinkType::IsParent: | ||
// it has no values, and just points to its parent, | ||
// therefore skip also skip pointing to that parent and point | ||
// to whichever parent it was pointing to as well, it may be its | ||
// immediate parent, or some super-parent. | ||
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()); | ||
static_cast<uintptr_t>(NextLinkType::IsParent); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
ktoso
Author
Member
|
||
break; | ||
case NextLinkType::IsNext: | ||
assert(false && "empty taskValue head in parent task, yet parent's 'head' is `IsNext`, " | ||
"this should not happen, as it implies the parent must have stored some value."); | ||
break; | ||
case NextLinkType::IsTerminal: | ||
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()); | ||
static_cast<uintptr_t>(NextLinkType::IsTerminal); | ||
break; | ||
} | ||
} else { | ||
item->next = reinterpret_cast<uintptr_t>(parentHead) | | ||
static_cast<uintptr_t>(NextLinkType::IsParent); | ||
} | ||
} else { | ||
item->next = reinterpret_cast<uintptr_t>(parentHead) | | ||
static_cast<uintptr_t>(NextLinkType::IsTerminal); | ||
} | ||
|
||
return item; | ||
} | ||
|
@@ -297,7 +322,6 @@ class AsyncTask : public HeapObject, public Job { | |
const Metadata *keyType, | ||
const Metadata *valueType) { | ||
assert(task); | ||
assert(task->hasTaskLocalValues()); | ||
size_t amountToAllocate = TaskLocalItem::itemSize(valueType); | ||
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this? | ||
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator | ||
|
@@ -327,9 +351,14 @@ class AsyncTask : public HeapObject, public Job { | |
return static_cast<NextLinkType>(next & statusMask); | ||
} | ||
|
||
/// Item does not contain any actual value, and is only used to point at | ||
/// a specific parent item. | ||
bool isEmpty() { | ||
return !valueType; | ||
} | ||
|
||
/// Retrieve a pointer to the storage of the value. | ||
OpaqueValue *getStoragePtr() { | ||
// assert(valueType && "valueType must be set before accessing storage pointer."); | ||
return reinterpret_cast<OpaqueValue *>( | ||
reinterpret_cast<char *>(this) + storageOffset(valueType)); | ||
} | ||
|
@@ -356,7 +385,8 @@ class AsyncTask : public HeapObject, public Job { | |
}; | ||
|
||
private: | ||
/// Single-linked list of task local values. | ||
/// A stack (single-linked list) of task local values. | ||
/// | ||
/// Once task local values within this task are traversed, the list continues | ||
/// to the "next parent that contributes task local values," or if no such | ||
/// parent exists it terminates with null. | ||
|
@@ -366,11 +396,28 @@ class AsyncTask : public HeapObject, public Job { | |
/// parent that has values. If this task does not have any values, the head | ||
/// pointer MAY immediately point at this task's parent task which has values. | ||
/// | ||
/// NOTE: Check the higher bits to know if this is a self or parent value. | ||
/// ### Concurrency | ||
/// Access to the head is only performed from the task itself, when it | ||
/// creates child tasks, the child during creation will inspect its parent's | ||
/// task local value stack head, and point to it. This is done on the calling | ||
/// task, and thus needs not to be synchronized. Subsequent traversal is | ||
/// performed by child tasks concurrently, however they use their own | ||
/// pointers/stack and can never mutate the parent's stack. | ||
/// | ||
/// The stack is only pushed/popped by the owning task, at the beginning and | ||
/// end a `body` block of `withLocal(_:boundTo:body:)` respectively. | ||
/// | ||
/// Correctness of the stack strongly relies on the guarantee that tasks | ||
/// never outline a scope in which they are created. Thanks to this, if | ||
/// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)` | ||
/// all tasks created inside the `withLocal` body must complete before it | ||
/// returns, as such, any child tasks potentially accessing the value stack | ||
/// are guaranteed to be completed by the time we pop values off the stack | ||
/// (after the body has completed). | ||
TaskLocalItem *head = nullptr; | ||
|
||
public: | ||
TaskLocalValuesFragment() {} | ||
TaskLocalValuesFragment() {} | ||
|
||
void destroy(); | ||
|
||
|
@@ -386,13 +433,7 @@ class AsyncTask : public HeapObject, public Job { | |
OpaqueValue* get(const Metadata *keyType); | ||
}; | ||
|
||
bool hasTaskLocalValues() const { | ||
return Flags.task_hasLocalValues(); | ||
} | ||
|
||
TaskLocalValuesFragment *localValuesFragment() { | ||
assert(hasTaskLocalValues()); | ||
|
||
auto offset = reinterpret_cast<char*>(this); | ||
offset += sizeof(AsyncTask); | ||
|
||
|
@@ -404,14 +445,7 @@ class AsyncTask : public HeapObject, public Job { | |
} | ||
|
||
OpaqueValue* localValueGet(const Metadata *keyType) { | ||
if (hasTaskLocalValues()) { | ||
return localValuesFragment()->get(keyType); | ||
} else { | ||
// We are guaranteed to have a task-local fragment even if this task has | ||
// no bindings, but its parent tasks do. Thus, if no fragment, we can | ||
// immediately return null. | ||
return nullptr; | ||
} | ||
return localValuesFragment()->get(keyType); | ||
} | ||
|
||
// ==== TaskGroup ------------------------------------------------------------ | ||
|
@@ -724,9 +758,7 @@ class AsyncTask : public HeapObject, public Job { | |
offset += sizeof(ChildFragment); | ||
} | ||
|
||
if (hasTaskLocalValues()) { | ||
offset += sizeof(TaskLocalValuesFragment); | ||
} | ||
offset += sizeof(TaskLocalValuesFragment); | ||
|
||
return reinterpret_cast<GroupFragment *>(offset); | ||
} | ||
|
@@ -861,9 +893,7 @@ class AsyncTask : public HeapObject, public Job { | |
offset += sizeof(ChildFragment); | ||
} | ||
|
||
if (hasTaskLocalValues()) { | ||
offset += sizeof(TaskLocalValuesFragment); | ||
} | ||
offset += sizeof(TaskLocalValuesFragment); | ||
|
||
if (isTaskGroup()) { | ||
offset += sizeof(GroupFragment); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Is there a missing bitwise or to include
NextLinkType::IsParent
bits?(one more occurrence below)