diff --git a/vtkm/Hash.h b/vtkm/Hash.h index ea2515fbefe5451f6d5884d912499b610cb0c30f..4c6c2322df33d458d7327c2585d26b6e28e5022c 100644 --- a/vtkm/Hash.h +++ b/vtkm/Hash.h @@ -100,6 +100,16 @@ struct HashChooser } }; +// Treat smaller integer types the same as 32-bit integers +template <> +struct HashChooser : HashChooser +{ +}; +template <> +struct HashChooser : HashChooser +{ +}; + } // namespace detail /// \brief Returns a 32-bit hash on a group of integer-type values. diff --git a/vtkm/exec/arg/FetchTagKeysIn.h b/vtkm/exec/arg/FetchTagKeysIn.h index 0388f9f612ba6e8037c87454d52e5856aa0e4d20..6ba0f3e8ebeb5a1f22103d4b5bf613b76d37d40d 100644 --- a/vtkm/exec/arg/FetchTagKeysIn.h +++ b/vtkm/exec/arg/FetchTagKeysIn.h @@ -59,6 +59,16 @@ struct Fetch< VTKM_EXEC ValueType Load(const ThreadIndicesType& indices, const ExecObjectType& keys) const { + VTKM_ASSERT( + (keys.UniqueKeys.GetNumberOfValues() > 0) && + "\nTried to fetch keys that were not evaluated. When the internal structure of the\n" + "vtkm::worklet::Keys object was created (usually through one of the BuildArrays* methods),\n" + "the UniqueKeys array was not generated. By default UniqueKeys are generated, but some\n" + "methods have the option to skip the creating of UniqueKeys because it takes some time\n" + "and is not always necessary. Apparently this option of not building UniqueKeys was\n" + "selected and that Keys object was subsequently used in a worklet that tried to reference\n" + "the keys argument. To correct this error, find where the Keys object's arrays were built\n" + "(sometime before the worklet was dispatched) and make sure that it constructs UniqueKeys."); return keys.UniqueKeys.Get(indices.GetInputIndex()); } diff --git a/vtkm/exec/internal/ReduceByKeyLookup.h b/vtkm/exec/internal/ReduceByKeyLookup.h index 16b6548c670c7212cf1dc90b8ad281bda7b4c1ed..1d5ec6c06af054a4a9b2ce5a547818a578e99577 100644 --- a/vtkm/exec/internal/ReduceByKeyLookup.h +++ b/vtkm/exec/internal/ReduceByKeyLookup.h @@ -20,8 +20,6 @@ #ifndef vtk_m_exec_internal_ReduceByKeyLookup_h #define vtk_m_exec_internal_ReduceByKeyLookup_h -#include - #include #include @@ -41,7 +39,7 @@ namespace internal /// state. /// template -struct ReduceByKeyLookup : vtkm::cont::ExecutionObjectBase +struct ReduceByKeyLookup { using KeyType = typename KeyPortalType::ValueType; diff --git a/vtkm/worklet/Keys.cxx b/vtkm/worklet/Keys.cxx index d34c30a9199ee3ec7bd323ea59726ae582e4e140..a003e7fdbb58dca8f03bd9268386b1042f65d71e 100644 --- a/vtkm/worklet/Keys.cxx +++ b/vtkm/worklet/Keys.cxx @@ -28,7 +28,7 @@ vtkm::worklet::KeysSortType sort, \ vtkm::cont::DeviceAdapterId device); \ template VTKM_WORKLET_EXPORT VTKM_CONT void vtkm::worklet::Keys::BuildArrays( \ - const vtkm::cont::ArrayHandleVirtual& keys, \ + const vtkm::cont::ArrayHandle& keys, \ vtkm::worklet::KeysSortType sort, \ vtkm::cont::DeviceAdapterId device) diff --git a/vtkm/worklet/Keys.h b/vtkm/worklet/Keys.h index 2260d8e53a8b4c5ef8a4dcc080d02387cf7b9ed3..831600d576f6ec63daf2c5e08ef61d2579e41644 100644 --- a/vtkm/worklet/Keys.h +++ b/vtkm/worklet/Keys.h @@ -40,11 +40,14 @@ #include #include +#include #include -#include +#include #include +#include + namespace vtkm { namespace worklet @@ -105,8 +108,8 @@ public: /// Build the internal arrays without modifying the input. This is more /// efficient for stable sorted arrays, but requires an extra copy of the /// keys for unstable sorting. - template - VTKM_CONT void BuildArrays(const KeyArrayType& keys, + template + VTKM_CONT void BuildArrays(const vtkm::cont::ArrayHandle& keys, KeysSortType sort, vtkm::cont::DeviceAdapterId device = vtkm::cont::DeviceAdapterTagAny()) { @@ -131,9 +134,9 @@ public: /// Build the internal arrays and also sort the input keys. This is more /// efficient for unstable sorting, but requires an extra copy for stable /// sorting. - template + template VTKM_CONT void BuildArraysInPlace( - KeyArrayType& keys, + vtkm::cont::ArrayHandle& keys, KeysSortType sort, vtkm::cont::DeviceAdapterId device = vtkm::cont::DeviceAdapterTagAny()) { @@ -158,8 +161,84 @@ public: } } + /// \brief Build the internal arrays using atomics. + /// + /// This works by creating an array indexed by all possible keys and then using atomics to count + /// how many of each key exists. Once these counts are known, then indexing can be derived with + /// scans and further atomics. + /// + /// This version of build arrays only works if (1) the keys are a standard integer type (e.g. + /// vtkm::Id or vtkm::IdComponent) and (2) the keys are indexed from 0 to a managable maximum. + /// For example, if the keys refer to points in a mesh, then this would be a good method to build + /// the arrays (as all keys are between 0 and the number of points in the mesh). + /// + /// \param keys An array handle containing the keys. + /// + /// \param maxKey The maximum key value. All keys must be in the range [0,maxKey). + /// + /// \param buildUniqueKeys If true (the default) then the UniqueKeys array will be built. The + /// UniqueKeys array is used internally whenever a worklet references its KeysIn parameter + /// (returning the key being worked on). Setting buildUniqueKeys to false can save a little bit + /// of time, but it can also cause a runtime error if the Keys object is later used with a + /// worklet that gets its own keys. Use with caution! + /// + /// \param device The device on which to compute the keys (optional). + template + VTKM_CONT void BuildArraysAtomics( + const vtkm::cont::ArrayHandle& keys, + KeyType maxKey, + bool buildUniqueKeys = true, + vtkm::cont::DeviceAdapterId device = vtkm::cont::DeviceAdapterTagAny()) + { + VTKM_LOG_SCOPE(vtkm::cont::LogLevel::Perf, "Keys::BuildArraysAtomics"); + + this->BuildArraysInternalAtomics(keys, maxKey, device); + if (buildUniqueKeys) + { + this->BuildUniqueKeys(keys, device); + } + else + { + this->UniqueKeys.Allocate(0); + } + } + + /// \brief Build the internal arrays using hash tables and atomics. + /// + /// This tends to be faster than sorting, but the keys are in semi-random order and the order of + /// the values is not stable. + /// + /// \param keys An array handle containing the keys. + /// + /// \param buildUniqueKeys If true (the default) then the UniqueKeys array will be built. The + /// UniqueKeys array is used internally whenever a worklet references its KeysIn parameter + /// (returning the key being worked on). Setting buildUniqueKeys to false can save a little bit + /// of time, but it can also cause a runtime error if the Keys object is later used with a + /// worklet that gets its own keys. Use with caution! + /// + /// \param device The device on which to compute the keys (optional). + /// + template + VTKM_CONT void BuildArraysHash( + const vtkm::cont::ArrayHandle& keys, + bool buildUniqueKeys = true, + vtkm::cont::DeviceAdapterId device = vtkm::cont::DeviceAdapterTagAny()) + { + VTKM_LOG_SCOPE(vtkm::cont::LogLevel::Perf, "Keys::BuildArraysHash"); + + this->BuildArraysInternalHash(keys, device); + if (buildUniqueKeys) + { + this->BuildUniqueKeys(keys, device); + } + else + { + this->UniqueKeys.Allocate(0); + } + } + VTKM_CONT - vtkm::Id GetInputRange() const { return this->UniqueKeys.GetNumberOfValues(); } + vtkm::Id GetInputRange() const { return this->Offsets.GetNumberOfValues(); } VTKM_CONT KeyArrayHandleType GetUniqueKeys() const { return this->UniqueKeys; } @@ -270,6 +349,132 @@ private: VTKM_ASSERT(offsetsTotal == numKeys); // Sanity check (void)offsetsTotal; // Shut up, compiler } + + // Note: builds SortedValuesMap, Offsets, and Counts. UniqueKeys does _not_ get built. + template + VTKM_CONT void BuildArraysInternalAtomics(KeyArrayType& keys, + typename KeyArrayType::ValueType maxKey, + vtkm::cont::DeviceAdapterId device) + { + constexpr typename KeyArrayType::ValueType zero = 0; + + vtkm::worklet::Invoker invoker(device); + + // Figure out how much of each key is in the data + vtkm::cont::Algorithm::Copy( + device, vtkm::cont::make_ArrayHandleConstant(zero, vtkm::Id(maxKey)), this->Counts); + invoker(internal::KeysCount(), keys, this->Counts); + + // We are going to reorder the keys to group them together. Compute the offsets for each group. + vtkm::Id numberOfValues = vtkm::cont::Algorithm::ScanExclusive( + device, vtkm::cont::make_ArrayHandleCast(this->Counts), this->Offsets); + VTKM_ASSERT(numberOfValues == keys.GetNumberOfValues()); + + // Build the sorted values map. This is done by taking the offsets computed in the last step + // and repeat the counting to place the lookback index into the map. + this->SortedValuesMap.Allocate(numberOfValues); + vtkm::cont::Algorithm::Copy( + device, vtkm::cont::make_ArrayHandleConstant(zero, vtkm::Id(maxKey)), this->Counts); + invoker(internal::KeysMakeMap(), keys, this->Offsets, this->Counts, this->SortedValuesMap); + + // Make sure that Offsets and Counts do not have any "empty" entries. + vtkm::cont::ArrayHandle compactOffsets; + vtkm::cont::Algorithm::CopyIf(device, this->Offsets, this->Counts, compactOffsets); + this->Offsets = compactOffsets; + + vtkm::cont::ArrayHandle compactCounts; + vtkm::cont::Algorithm::CopyIf(device, this->Counts, this->Counts, compactCounts); + this->Counts = compactCounts; + } + + // Note: builds SortedValuesMap, Offsets, and Counts. UniqueKeys does _not_ get built. + template + VTKM_CONT void BuildArraysInternalHash(KeyArrayType& keys, vtkm::cont::DeviceAdapterId device) + { + vtkm::worklet::Invoker invoker(device); + + // How big should we make the hash table? Larger hash tables will take more time to build + // indices from, but smaller hash tables will generally result in more hash collisions + // (which can slow down the collision separation below). The only metric we have is how + // big the keys array is. The hash collisions relies on the overlap in the keys and + // dumb luck, so the hash table size is mostly a guess. Dividing the array size by 16 + // seems to work well enough. This will probably guarantee collisions in most cases, + // but the speed savings in building the hash table seems to be worth it. + // + // We add one to the hash table size just to make absolutely sure we have at least one + // entry in the hash table. + vtkm::Id hashTableSize = (keys.GetNumberOfValues() / 16) + 1; +#ifdef VTKM_USE_64BIT_IDS + if (hashTableSize > std::numeric_limits::max()) + { + hashTableSize = std::numeric_limits::max(); + } +#endif + vtkm::HashType maxHash = static_cast(hashTableSize); + + // Create the hash table values + vtkm::cont::ArrayHandle hashes; + invoker(internal::KeysConstructHashes(maxHash), keys, hashes); + + // Use atomics to construct a map using the hashes as keys, which will be close but imperfect. + this->BuildArraysInternalAtomics(hashes, maxHash, device); + + // Now we need to separate collisions where two keys with the same hash got grouped together. + // The first step for that is to reorder the subarrays in each mesh (by reordering + // SortedValuesMap) so that identicial keys are put next to each other. While we do that, we + // also count how many keys are grouped in each hash. + vtkm::cont::ArrayHandle numKeysInHash; + invoker(internal::KeysSeparateCollisions(), + this->Offsets, + this->Counts, + keys, + this->SortedValuesMap, + numKeysInHash); + + // Create new offset and count arrays. We will wrap them in an ArrayHandleGroupVecVariable so + // that we can fill them within a worklet operating on a single hash value. This gets a little + // confusing because we are using an offsets for ArrayHandleGroupVecVariable on a grouped + // offset array. + vtkm::Id numUniqueKeys; + vtkm::cont::ArrayHandle groupVecVariableOffsets = + vtkm::cont::ConvertNumComponentsToOffsets(numKeysInHash, numUniqueKeys, device); + + vtkm::cont::ArrayHandle newOffsets; + newOffsets.Allocate(numUniqueKeys); + auto groupedNewOffsets = + vtkm::cont::make_ArrayHandleGroupVecVariable(newOffsets, groupVecVariableOffsets); + + vtkm::cont::ArrayHandle newCounts; + newCounts.Allocate(numUniqueKeys); + auto groupedNewCounts = + vtkm::cont::make_ArrayHandleGroupVecVariable(newCounts, groupVecVariableOffsets); + + // Now run a worklet to rebuild the offset and count arrays that split up hash collisions. + invoker(internal::KeysRebuildOffsetsCounts(), + this->Offsets, + this->Counts, + keys, + this->SortedValuesMap, + groupedNewOffsets, + groupedNewCounts); + + // We are now ready to replace the offsets and counts. + this->Offsets = newOffsets; + this->Counts = newCounts; + } + + // Given that SortedValuesMap, Offsets, and Counts are built, computes the UniqueKeys array. + template + VTKM_CONT void BuildUniqueKeys(KeyArrayType& keys, vtkm::cont::DeviceAdapterId device) + { + // Make permutation arrays that point from a compact lists of keys to the keys array. + // This will be the unique set of keys. + auto permutationKeys = vtkm::cont::make_ArrayHandlePermutation(this->SortedValuesMap, keys); + auto uniqueKeys = vtkm::cont::make_ArrayHandlePermutation(this->Offsets, permutationKeys); + + // Copy the permutation array into a real array. + vtkm::cont::Algorithm::Copy(device, uniqueKeys, this->UniqueKeys); + } }; template @@ -439,7 +644,7 @@ struct Transport::BuildArrays( \ - const vtkm::cont::ArrayHandleVirtual& keys, \ + const vtkm::cont::ArrayHandle& keys, \ vtkm::worklet::KeysSortType sort, \ vtkm::cont::DeviceAdapterId device) diff --git a/vtkm/worklet/internal/CMakeLists.txt b/vtkm/worklet/internal/CMakeLists.txt index 612290fa897e9a3ea9612aab3d57fa4c58560ef9..f9aef2140d076883fb1963aeaf62901ce0fa67ba 100644 --- a/vtkm/worklet/internal/CMakeLists.txt +++ b/vtkm/worklet/internal/CMakeLists.txt @@ -20,6 +20,7 @@ set(headers DispatcherBase.h + KeyBuilders.h TriangulateTables.h WorkletBase.h ) diff --git a/vtkm/worklet/internal/KeyBuilders.h b/vtkm/worklet/internal/KeyBuilders.h new file mode 100644 index 0000000000000000000000000000000000000000..98d7bf41dc1c65ea2de7e80928de477548ceb02a --- /dev/null +++ b/vtkm/worklet/internal/KeyBuilders.h @@ -0,0 +1,206 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +// +// Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). +// Copyright 2018 UT-Battelle, LLC. +// Copyright 2018 Los Alamos National Security. +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National +// Laboratory (LANL), the U.S. Government retains certain rights in +// this software. +//============================================================================ +#ifndef vtk_m_worklet_internal_KeyBuilders_h +#define vtk_m_worklet_internal_KeyBuilders_h + +#include + +#include +#include + +namespace vtkm +{ +namespace worklet +{ +namespace internal +{ + +struct KeysCount : public vtkm::worklet::WorkletMapField +{ + using ControlSignature = void(FieldIn keys, AtomicArrayInOut counts); + using ExecutionSignature = void(_1, _2); + using InputDomain = _1; + + template + VTKM_EXEC void operator()( + T key, + const vtkm::exec::AtomicArrayExecutionObject& counts) const + { + counts.Add(static_cast(key), 1); + } +}; + +struct KeysMakeMap : public vtkm::worklet::WorkletMapField +{ + using ControlSignature = void(FieldIn keys, + WholeArrayIn baseOffset, + AtomicArrayInOut counts, + WholeArrayOut map); + using ExecutionSignature = void(_1, _2, _3, _4, InputIndex); + + template + VTKM_EXEC void operator()( + T key, + BaseOffsetPortalType& baseOffsets, + const vtkm::exec::AtomicArrayExecutionObject& counts, + OutputPortalType& outputMap, + vtkm::Id indexToMapTo) const + { + vtkm::Id base = baseOffsets.Get(static_cast(key)); + vtkm::Id destinationId = base + counts.Add(static_cast(key), 1); + outputMap.Set(destinationId, indexToMapTo); + } +}; + +struct KeysConstructHashes : public vtkm::worklet::WorkletMapField +{ + using ControlSignature = void(FieldIn keys, FieldOut hashes); + using ExecutionSignature = _2(_1); + using InputDomain = _1; + + vtkm::HashType HashTableSize; + + KeysConstructHashes(vtkm::HashType hashTableSize) + : HashTableSize(hashTableSize) + { + } + + template + VTKM_EXEC vtkm::HashType operator()(T&& value) const + { + return vtkm::Hash(value) % this->HashTableSize; + } +}; + +struct KeysSeparateCollisions : public vtkm::worklet::WorkletMapField +{ + using ControlSignature = void(FieldIn Offsets, + FieldIn Counts, + WholeArrayIn originalKeys, + WholeArrayInOut map, + FieldOut numKeysInHash); + using ExecutionSignature = _5(_1, _2, _3, _4); + using InputDomain = _1; + + template + VTKM_EXEC vtkm::IdComponent operator()(vtkm::Id offset, + vtkm::IdComponent count, + KeyPortalType& keyPortal, + MapPortalType& mapPortal) const + { + using T = typename KeyPortalType::ValueType; + + vtkm::IdComponent numKeysInHash = 0; + vtkm::IdComponent nextToCheck = 0; + while (nextToCheck < count) + { + ++numKeysInHash; + T currentKey = keyPortal.Get(mapPortal.Get(offset + nextToCheck)); + ++nextToCheck; + for (vtkm::IdComponent compareIndex = nextToCheck; compareIndex < count; ++compareIndex) + { + T compareKey = keyPortal.Get(mapPortal.Get(offset + compareIndex)); + if (currentKey == compareKey) + { + // Found another key of the same type. Make sure it is in this group + if (compareIndex != nextToCheck) + { + // Need to swap to be close to the group. + vtkm::Id mapAtNextToCheck = mapPortal.Get(offset + nextToCheck); + vtkm::Id mapAtCompareIndex = mapPortal.Get(offset + compareIndex); + mapPortal.Set(offset + nextToCheck, mapAtCompareIndex); + mapPortal.Set(offset + compareIndex, mapAtNextToCheck); + } + else + { + // Already adjacent to the group. Nothing to swap. + } + // Group grows by 1 + ++nextToCheck; + } + else + { + // Found a key of a different type. Just skip over it. + } + } + } + + return numKeysInHash; + } +}; + +struct KeysRebuildOffsetsCounts : public vtkm::worklet::WorkletMapField +{ + using ControlSignature = void(FieldIn originalOffsets, + FieldIn originalCounts, + WholeArrayIn keys, + WholeArrayIn map, + FieldOut newOffsets, + FieldOut newCounts); + using ExecutionSignature = void(_1, _2, _3, _4, _5, _6); + using InputDomain = _1; + + template + VTKM_EXEC void operator()(vtkm::Id originalOffset, + vtkm::IdComponent originalCount, + KeysPortal keys, + MapPortal map, + OffsetsVecLike& newOffsets, + CountsVecLike& newCounts) const + { + using KeyType = typename KeysPortal::ValueType; + + vtkm::IdComponent numNew = newOffsets.GetNumberOfComponents(); + VTKM_ASSERT(numNew == newCounts.GetNumberOfComponents()); + + vtkm::IdComponent groupStart = 0; + KeyType groupKey = keys.Get(map.Get(originalOffset)); + for (vtkm::IdComponent groupIndex = 0; groupIndex < numNew - 1; ++groupIndex) + { + VTKM_ASSERT(groupStart < originalCount - 1); + + vtkm::IdComponent groupEnd = groupStart; + KeyType nextKey; + do + { + ++groupEnd; + VTKM_ASSERT(groupEnd < originalCount); + nextKey = keys.Get(map.Get(originalOffset + groupEnd)); + } while (groupKey == nextKey); + + newOffsets[groupIndex] = originalOffset + groupStart; + newCounts[groupIndex] = groupEnd - groupStart; + groupStart = groupEnd; + groupKey = nextKey; + } + + // Shortcut for last group + newOffsets[numNew - 1] = originalOffset + groupStart; + newCounts[numNew - 1] = originalCount - groupStart; + } +}; +} +} +} // vtkm::worklet::internal + +#endif //vtk_m_worklet_internal_KeyBuilders_h diff --git a/vtkm/worklet/testing/UnitTestKeys.cxx b/vtkm/worklet/testing/UnitTestKeys.cxx index 582bba18b6771e83ae8a3e97913e2d51253e9dd2..ee9727dc88db433f90b274c49275d43548163fe7 100644 --- a/vtkm/worklet/testing/UnitTestKeys.cxx +++ b/vtkm/worklet/testing/UnitTestKeys.cxx @@ -24,6 +24,8 @@ #include +#include + #include namespace @@ -37,7 +39,8 @@ void CheckKeyReduce(const KeyPortal& originalKeys, const KeyPortal& uniqueKeys, const IdPortal& sortedValuesMap, const IdPortal& offsets, - const IdComponentPortal& counts) + const IdComponentPortal& counts, + bool checkStable) { using KeyType = typename KeyPortal::ValueType; vtkm::Id originalSize = originalKeys.GetNumberOfValues(); @@ -51,37 +54,150 @@ void CheckKeyReduce(const KeyPortal& originalKeys, KeyType key = uniqueKeys.Get(uniqueIndex); vtkm::Id offset = offsets.Get(uniqueIndex); vtkm::IdComponent groupCount = counts.Get(uniqueIndex); + vtkm::Id lastIndex = -1; for (vtkm::IdComponent groupIndex = 0; groupIndex < groupCount; groupIndex++) { vtkm::Id originalIndex = sortedValuesMap.Get(offset + groupIndex); KeyType originalKey = originalKeys.Get(originalIndex); VTKM_TEST_ASSERT(key == originalKey, "Bad key lookup."); + if (checkStable) + { + VTKM_TEST_ASSERT(originalIndex > lastIndex, "Order of values per key not stable."); + } + lastIndex = originalIndex; } } } +template +void CheckKeyReduce(const vtkm::cont::ArrayHandle& originalKeys, + const vtkm::worklet::Keys& keys, + bool checkStable) +{ + VTKM_TEST_ASSERT(keys.GetInputRange() == NUM_UNIQUE, + "Keys has bad input range. Has ", + keys.GetInputRange(), + ", expected ", + NUM_UNIQUE); + + CheckKeyReduce(originalKeys.GetPortalConstControl(), + keys.GetUniqueKeys().GetPortalConstControl(), + keys.GetSortedValuesMap().GetPortalConstControl(), + keys.GetOffsets().GetPortalConstControl(), + keys.GetCounts().GetPortalConstControl(), + checkStable); +} + +template +void TryConstructor(const vtkm::cont::ArrayHandle& keyArray) +{ + std::cout << " Trying construct with keys." << std::endl; + + vtkm::cont::ArrayHandle sortedKeys; + vtkm::cont::ArrayCopy(keyArray, sortedKeys); + + vtkm::worklet::Keys keys(sortedKeys); + CheckKeyReduce(keyArray, keys, false); +} + +template +void TryBuildUnstable(const vtkm::cont::ArrayHandle& keyArray) +{ + std::cout << " Trying build unstable." << std::endl; + + vtkm::worklet::Keys keys; + keys.BuildArrays(keyArray, vtkm::worklet::KeysSortType::Unstable); + CheckKeyReduce(keyArray, keys, false); +} + +template +void TryBuildStable(const vtkm::cont::ArrayHandle& keyArray) +{ + std::cout << " Trying build stable." << std::endl; + + vtkm::worklet::Keys keys; + keys.BuildArrays(keyArray, vtkm::worklet::KeysSortType::Stable); + CheckKeyReduce(keyArray, keys, true); +} + +template +void TryBuildInPlaceUnstable(const vtkm::cont::ArrayHandle& keyArray) +{ + std::cout << " Trying build in place unstable." << std::endl; + + vtkm::cont::ArrayHandle sortedKeys; + vtkm::cont::ArrayCopy(keyArray, sortedKeys); + + vtkm::worklet::Keys keys; + keys.BuildArraysInPlace(sortedKeys, vtkm::worklet::KeysSortType::Unstable); + CheckKeyReduce(keyArray, keys, false); +} + +template +void TryBuildInPlaceStable(const vtkm::cont::ArrayHandle& keyArray) +{ + std::cout << " Trying build in place stable." << std::endl; + + vtkm::cont::ArrayHandle sortedKeys; + vtkm::cont::ArrayCopy(keyArray, sortedKeys); + + vtkm::worklet::Keys keys; + keys.BuildArraysInPlace(sortedKeys, vtkm::worklet::KeysSortType::Stable); + CheckKeyReduce(keyArray, keys, true); +} + +template +void TryBuildHash(const vtkm::cont::ArrayHandle& keyArray) +{ + std::cout << " Trying build hash." << std::endl; + + vtkm::worklet::Keys keys; + keys.BuildArraysHash(keyArray); + CheckKeyReduce(keyArray, keys, false); +} + +template +void TryBuildAtomics(KeyType, vtkm::TypeTraitsScalarTag) +{ + std::cout << " Trying build atomics." << std::endl; + + KeyType keyBuffer[ARRAY_SIZE]; + for (vtkm::Id index = 0; index < ARRAY_SIZE; ++index) + { + keyBuffer[index] = static_cast(2 * (index % NUM_UNIQUE)); + } + vtkm::cont::ArrayHandle keyArray = vtkm::cont::make_ArrayHandle(keyBuffer, ARRAY_SIZE); + + vtkm::worklet::Keys keys; + keys.BuildArraysAtomics(keyArray, 2 * NUM_UNIQUE + 10); + CheckKeyReduce(keyArray, keys, false); +} + +template +void TryBuildAtomics(KeyType, vtkm::TypeTraitsVectorTag) +{ + std::cout << " --Skipping atomics." << std::endl; +} + template void TryKeyType(KeyType) { KeyType keyBuffer[ARRAY_SIZE]; - for (vtkm::Id index = 0; index < ARRAY_SIZE; index++) + for (vtkm::Id index = 0; index < ARRAY_SIZE; ++index) { keyBuffer[index] = TestValue(index % NUM_UNIQUE, KeyType()); } vtkm::cont::ArrayHandle keyArray = vtkm::cont::make_ArrayHandle(keyBuffer, ARRAY_SIZE); - vtkm::cont::ArrayHandle sortedKeys; - vtkm::cont::ArrayCopy(keyArray, sortedKeys); - - vtkm::worklet::Keys keys(sortedKeys); - VTKM_TEST_ASSERT(keys.GetInputRange() == NUM_UNIQUE, "Keys has bad input range."); + TryConstructor(keyArray); + TryBuildUnstable(keyArray); + TryBuildStable(keyArray); + TryBuildInPlaceUnstable(keyArray); + TryBuildInPlaceStable(keyArray); + TryBuildHash(keyArray); - CheckKeyReduce(keyArray.GetPortalConstControl(), - keys.GetUniqueKeys().GetPortalConstControl(), - keys.GetSortedValuesMap().GetPortalConstControl(), - keys.GetOffsets().GetPortalConstControl(), - keys.GetCounts().GetPortalConstControl()); + TryBuildAtomics(KeyType(), typename vtkm::TypeTraits::DimensionalityTag()); } void TestKeys()