Data Race Resolution
Finished the comments on data races and their resolution.
This commit is contained in:
parent
aed7e02da9
commit
1185f70bc9
@ -34,7 +34,6 @@ public:
|
||||
|
||||
using ExecutionSignature = void(WorkIndex, _1, _2, _3, _4);
|
||||
|
||||
// TODO: Use Scatter?
|
||||
template <typename InPortalType, typename AtomicCompInOut>
|
||||
VTKM_EXEC void operator()(vtkm::Id index,
|
||||
vtkm::Id start,
|
||||
@ -84,7 +83,6 @@ public:
|
||||
vtkm::cont::ArrayHandleCounting<vtkm::Id>(0, 1, numIndicesArray.GetNumberOfValues()),
|
||||
componentsOut);
|
||||
|
||||
// TODO: give the reason that single pass algorithm works.
|
||||
vtkm::cont::Invoker invoke;
|
||||
invoke(
|
||||
detail::GraphGraft{}, indexOffsetsArray, numIndicesArray, connectivityArray, componentsOut);
|
||||
|
@ -60,14 +60,15 @@ public:
|
||||
{
|
||||
for (int i = minIndices[0]; i <= maxIndices[0]; i++)
|
||||
{
|
||||
// We need to reload thisComp and thatComp every iteration since
|
||||
// they might have been changed by Unite(), both as a result of
|
||||
// attaching one tree to the other or as a result of path compaction
|
||||
// in findRoot().
|
||||
auto thisComp = neighborComp.Get(0, 0, 0);
|
||||
auto thatComp = neighborComp.Get(i, j, k);
|
||||
if (thisColor == neighborColor.Get(i, j, k))
|
||||
{
|
||||
// We need to reload thisComp and thatComp every iteration since
|
||||
// they might have been changed by Unite(), both as a result of
|
||||
// attaching one tree to the other or as a result of path compaction
|
||||
// in findRoot().
|
||||
auto thisComp = neighborComp.Get(0, 0, 0);
|
||||
auto thatComp = neighborComp.Get(i, j, k);
|
||||
|
||||
// Merge the two components one way or the other, the order will
|
||||
// be resolved by Unite().
|
||||
UnionFind::Unite(compOut, thisComp, thatComp);
|
||||
@ -103,7 +104,6 @@ public:
|
||||
Algorithm::Copy(vtkm::cont::ArrayHandleCounting<vtkm::Id>(0, 1, pixels.GetNumberOfValues()),
|
||||
componentsOut);
|
||||
|
||||
// TODO: give the reason that single pass algorithm works.
|
||||
vtkm::cont::Invoker invoke;
|
||||
invoke(detail::ImageGraft{}, input, componentsOut, pixels, componentsOut);
|
||||
invoke(PointerJumping{}, componentsOut);
|
||||
|
@ -84,23 +84,22 @@ public:
|
||||
class Renumber
|
||||
{
|
||||
public:
|
||||
// FIXME: const correctness for input. Or make it single InOut argument.
|
||||
static void Run(vtkm::cont::ArrayHandle<vtkm::Id>& components)
|
||||
static void Run(vtkm::cont::ArrayHandle<vtkm::Id>& componentsInOut)
|
||||
{
|
||||
using Algorithm = vtkm::cont::Algorithm;
|
||||
|
||||
// FIXME: we should able to apply findRoot to each pixel and use some kind
|
||||
// FIXME: we should be able to apply findRoot to each pixel and use some kind
|
||||
// of atomic operation to get the number of unique components without the
|
||||
// cost of copying and sorting. This might be able to be extended to also
|
||||
// work for the renumbering (replacing InnerJoin) through atomic increment.
|
||||
vtkm::cont::ArrayHandle<vtkm::Id> uniqueComponents;
|
||||
Algorithm::Copy(components, uniqueComponents);
|
||||
Algorithm::Copy(componentsInOut, uniqueComponents);
|
||||
Algorithm::Sort(uniqueComponents);
|
||||
Algorithm::Unique(uniqueComponents);
|
||||
|
||||
vtkm::cont::ArrayHandle<vtkm::Id> ids;
|
||||
Algorithm::Copy(vtkm::cont::ArrayHandleCounting<vtkm::Id>(0, 1, components.GetNumberOfValues()),
|
||||
ids);
|
||||
Algorithm::Copy(
|
||||
vtkm::cont::ArrayHandleCounting<vtkm::Id>(0, 1, componentsInOut.GetNumberOfValues()), ids);
|
||||
|
||||
vtkm::cont::ArrayHandle<vtkm::Id> uniqueColor;
|
||||
Algorithm::Copy(
|
||||
@ -109,10 +108,15 @@ public:
|
||||
|
||||
vtkm::cont::ArrayHandle<vtkm::Id> cellColors;
|
||||
vtkm::cont::ArrayHandle<vtkm::Id> pixelIdsOut;
|
||||
InnerJoin::Run(
|
||||
components, ids, uniqueComponents, uniqueColor, cellColors, pixelIdsOut, components);
|
||||
InnerJoin::Run(componentsInOut,
|
||||
ids,
|
||||
uniqueComponents,
|
||||
uniqueColor,
|
||||
cellColors,
|
||||
pixelIdsOut,
|
||||
componentsInOut);
|
||||
|
||||
Algorithm::SortByKey(pixelIdsOut, components);
|
||||
Algorithm::SortByKey(pixelIdsOut, componentsInOut);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -59,49 +59,58 @@ public:
|
||||
// rooted forest structure of Union-Find at the expense of duplicated (but
|
||||
// benign) work.
|
||||
|
||||
// Case 2, T0 calling Unite(u, v), T1 calling Unite(u, w) and T2 calling
|
||||
// Unite(v, s) concurrently.
|
||||
// Case 2, T0 calling Unite(u, v) and T1 calling Unite(u, w) concurrently.
|
||||
// Problem I: There is a potential write after read data race. After T0
|
||||
// calls findRoot for u and v, T1 might have called parents.Set(root_u, root_w)
|
||||
// thus changed root_u to root_w thus making root_u "obsolete" before T0 calls
|
||||
// parents.Set() on root_u/root_v.
|
||||
// calls findRoot() for u but before actually updating the parent of root_u,
|
||||
// T1 might have changed root_u to root_w and made root_u "obsolete".
|
||||
// When the root of the tree to be attached to (e.g. root_u, when root_u < root_v)
|
||||
// is changed, there is no hazard, since we are just attaching a tree to a
|
||||
// now a non-root node, root_u, (thus, root_w <- root_u <- root_v) and three
|
||||
// components merged.
|
||||
// now a non-root node, root_u, (thus, root_w <- root_u <- root_v).
|
||||
// However, when the root of the attaching tree (root_v) is changed, it
|
||||
// means that the root_u has been attached to yet some other root_s and became
|
||||
// a non-root node. If we are now attaching this non-root node to root_w we
|
||||
// would leave root_s behind and undoing previous work.
|
||||
// Resolution: TDB, mostly some reason for CompareAndSwap
|
||||
// Atomic Load/Store with memory_order_acquire are no able to detect this
|
||||
// data race. While Load see all previous Stores by other threds, it can not
|
||||
// be aware of any Store after the Load.
|
||||
// Resolution: Use atomic Compare and Swap in a loop when updating root_u.
|
||||
// CAS will check if root of u has been updated by some other thread between
|
||||
// findRoot(u) is called and when root of u is going to be updated. This is
|
||||
// done by comparing the root_u = findRoot(u) to the current value at
|
||||
// parents[root_u]. If they are the same, no data race has happened and the
|
||||
// value in parents[root_u] is updated. However, if root_u != parent[root_u],
|
||||
// it means parent[root_u] has been updated by some other thread. CAS returns
|
||||
// the new value of parent[root_u] (root_s in the Problem description) which
|
||||
// we can use as the new root_u. We keep retrying until there is no more
|
||||
// data race and root_u == root_w i.e. they are in the same component.
|
||||
|
||||
// Case 3. There is a potential concurrent write data race as it is possible
|
||||
// for two threads to try to change the same old root to different new roots,
|
||||
// e.g. threadA calls parents.Set(root, rootA) while threadB calls
|
||||
// parents(root, rootB) where rootB < root and rootA < root (but the order
|
||||
// of rootA and rootB is unspecified.) Each thread assumes success while
|
||||
// the outcome is actually unspecified.
|
||||
// Resolution: An atomic Compare and Swap is suggested in SV Janati et. al.
|
||||
// Problem II: There is a potential concurrent write data race as it is
|
||||
// possible for the two threads to try to change the same old root to
|
||||
// different new roots, e.g. T0 calls parents.Set(u, v) while T1 calls
|
||||
// parents.Set(u, w) where v < u and w < u (but the order of v and w is
|
||||
// unspecified.) Each thread assumes success while the outcome is actually
|
||||
// unspecified.
|
||||
// Resolution: Use an atomic Compare and Swap is suggested in SV Janati et. al.
|
||||
// as well as J. Jaiganesht et. al. to resolve the data race. The CAS
|
||||
// checks if the old root is the same as what we expected. If so, there is
|
||||
// no data race, CAS will set the root to the desired value. The return value
|
||||
// from CAS will equal to our expected old root and signifies a successful
|
||||
// write which terminates the while loop.
|
||||
// If the old root is not what we expected, it was updated by some other
|
||||
// thread and the update by this thread fails. The root as updated by
|
||||
// the other thread is returned. This returned value would not equal to our desired new
|
||||
// root, signifying the need to retry with the while loop. On the other
|
||||
// hand,
|
||||
// Why simple Load/Store as provide by AtomicArray Get/Set do not work.
|
||||
|
||||
// no data race, CAS will set the root to the desired new value. The return
|
||||
// value from CAS will equal to our expected old root and signifies a
|
||||
// successful write which terminates the while loop.
|
||||
// If the old root is not what we expected, it has been updated by some
|
||||
// other thread and the update by this thread fails. The root as updated by
|
||||
// the other thread is returned. This returned value would not equal to
|
||||
// our desired new root, signifying the need to retry with the while loop.
|
||||
// We can use this return "new root" as is without calling findRoot() to
|
||||
// find the "new root". The while loop terminates when both u and v have
|
||||
// the same root (thus united).
|
||||
auto root_u = UnionFind::findRoot(parents, u);
|
||||
auto root_v = UnionFind::findRoot(parents, v);
|
||||
|
||||
while (root_u != root_v)
|
||||
{
|
||||
// FIXME: we might be executing the loop one extra time.
|
||||
// FIXME: we might be executing the loop one extra time than necessary.
|
||||
// Nota Bene: VTKm's CompareAndSwap has a different order of parameters
|
||||
// than normal practice, it is (index, new, expected) rather than
|
||||
// than common practice, it is (index, new, expected) rather than
|
||||
// (index, expected, new).
|
||||
if (root_u < root_v)
|
||||
root_v = parents.CompareAndSwap(root_v, root_u, root_v);
|
||||
|
Loading…
Reference in New Issue
Block a user