VTK-m  2.2
ParallelQuickSortOpenMP.h
Go to the documentation of this file.
1 //============================================================================
2 // Copyright (c) Kitware, Inc.
3 // All rights reserved.
4 // See LICENSE.txt for details.
5 //
6 // This software is distributed WITHOUT ANY WARRANTY; without even
7 // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8 // PURPOSE. See the above copyright notice for more information.
9 //============================================================================
10 
13 
15 
16 #include <vtkm/Types.h>
17 #include <vtkm/cont/ArrayHandle.h>
18 
19 #include <omp.h>
20 
21 #include <iterator>
22 
23 namespace vtkm
24 {
25 namespace cont
26 {
27 namespace openmp
28 {
29 namespace sort
30 {
31 namespace quick
32 {
33 
34 template <typename IterType, typename RawBinaryCompare>
36 {
37  using BinaryCompare = vtkm::cont::internal::WrappedBinaryOperator<bool, RawBinaryCompare>;
38  using ValueType = typename std::iterator_traits<IterType>::value_type;
39 
40  IterType Data;
43 
44  QuickSorter(IterType iter, RawBinaryCompare comp)
45  : Data(iter)
46  , Compare(comp)
47  , SerialSize(0)
48  {
49  }
50 
51  void Execute(const vtkm::Id2 range)
52  {
53  VTKM_OPENMP_DIRECTIVE(parallel default(shared))
54  {
55  VTKM_OPENMP_DIRECTIVE(single)
56  {
57  this->Prepare(range);
58  this->Sort(range);
59  }
60  }
61  }
62 
63 private:
64  void Prepare(const vtkm::Id2 /*range*/)
65  {
66  // Rough benchmarking on an 4-core+4HT processor shows that this sort is
67  // most efficient (within 5% of TBB sort) when we switch to a serial
68  // implementation once a partition is less than 32K keys
69  this->SerialSize = 32768;
70  }
71 
74  const vtkm::Pair<vtkm::Id, ValueType>& v3) const
75  {
76  if (this->Compare(v1.second, v2.second))
77  { // v1 < v2
78  if (this->Compare(v1.second, v3.second))
79  { // v1 < v3
80  if (this->Compare(v2.second, v3.second))
81  { // v1 < v2 < v3
82  return v2;
83  }
84  else // v3 < v2
85  { // v1 < v3 < v2
86  return v3;
87  }
88  }
89  else // v3 < v1
90  { // v3 < v1 < v2
91  return v1;
92  }
93  }
94  else
95  { // v2 < v1
96  if (this->Compare(v2.second, v3.second))
97  { // v2 < v3
98  if (this->Compare(v1.second, v3.second))
99  { // v2 < v1 < v3
100  return v1;
101  }
102  else
103  { // v2 < v3 < v1
104  return v3;
105  }
106  }
107  else
108  { // v3 < v2 < v1
109  return v2;
110  }
111  }
112  }
113 
115  {
116  return this->MedianOf3(vtkm::Pair<vtkm::Id, ValueType>(ids[0], this->Data[ids[0]]),
117  vtkm::Pair<vtkm::Id, ValueType>(ids[1], this->Data[ids[1]]),
118  vtkm::Pair<vtkm::Id, ValueType>(ids[2], this->Data[ids[2]]));
119  }
120 
122  {
123  return this->MedianOf3(
124  this->MedianOf3(ids), this->MedianOf3(ids + 3), this->MedianOf3(ids + 6));
125  }
126 
127  // Approximate the median of the range and return its index.
129  {
130  const vtkm::Id numVals = range[1] - range[0];
131  assert(numVals >= 9);
132 
133  // Pseudorandomize the pivot locations to avoid issues with periodic data
134  // (evenly sampling inputs with periodic values tends to cause the same
135  // value to be obtained for all samples)
136  const vtkm::Id seed = range[0] * 3 / 2 + range[1] * 11 / 3 + numVals * 10 / 7;
137  const vtkm::Id delta = (numVals / 9) * 4 / 3;
138 
139  vtkm::Id sampleLocations[9] = {
140  range[0] + ((seed + 0 * delta) % numVals), range[0] + ((seed + 1 * delta) % numVals),
141  range[0] + ((seed + 2 * delta) % numVals), range[0] + ((seed + 3 * delta) % numVals),
142  range[0] + ((seed + 4 * delta) % numVals), range[0] + ((seed + 5 * delta) % numVals),
143  range[0] + ((seed + 6 * delta) % numVals), range[0] + ((seed + 7 * delta) % numVals),
144  range[0] + ((seed + 8 * delta) % numVals)
145  };
146 
147  return this->PseudoMedianOf9(sampleLocations);
148  }
149 
150  // Select a pivot and partition data with it, returning the final location of
151  // the pivot element(s). We use Bentley-McIlroy three-way partitioning to
152  // improve handling of duplicate keys, so the pivot "location" is actually
153  // a range of identical keys, hence the vtkm::Id2 return type, which mark
154  // the [begin, end) of the pivot range.
156  {
157  using namespace std; // For ADL swap
158 
159  const vtkm::Pair<vtkm::Id, ValueType> pivotData = this->SelectPivot(range);
160  const vtkm::Id& origPivotIdx = pivotData.first;
161  const ValueType& pivotVal = pivotData.second;
162 
163  // Move the pivot to the end of the block while we partition the rest:
164  swap(this->Data[origPivotIdx], this->Data[range[1] - 1]);
165 
166  // Indices of the last partitioned keys:
167  vtkm::Id2 dataCursors(range[0] - 1, range[1] - 1);
168 
169  // Indices of the start/end of the keys equal to the pivot:
170  vtkm::Id2 pivotCursors(dataCursors);
171 
172  for (;;)
173  {
174  // Advance the data cursors past all keys that are already partitioned:
175  while (this->Compare(this->Data[++dataCursors[0]], pivotVal))
176  ;
177  while (this->Compare(pivotVal, this->Data[--dataCursors[1]]) && dataCursors[1] > range[0])
178  ;
179 
180  // Range is partitioned the cursors have crossed:
181  if (dataCursors[0] >= dataCursors[1])
182  {
183  break;
184  }
185 
186  // Both dataCursors are pointing at incorrectly partitioned keys. Swap
187  // them to place them in the proper partitions:
188  swap(this->Data[dataCursors[0]], this->Data[dataCursors[1]]);
189 
190  // If the elements we just swapped are actually equivalent to the pivot
191  // value, move them to the pivot storage locations:
192  if (!this->Compare(this->Data[dataCursors[0]], pivotVal))
193  {
194  ++pivotCursors[0];
195  swap(this->Data[pivotCursors[0]], this->Data[dataCursors[0]]);
196  }
197  if (!this->Compare(pivotVal, this->Data[dataCursors[1]]))
198  {
199  --pivotCursors[1];
200  swap(this->Data[pivotCursors[1]], this->Data[dataCursors[1]]);
201  }
202  }
203 
204  // Data is now partitioned as:
205  // | Equal | Less | Greater | Equal |
206  // Move the equal keys to the middle for the final partitioning:
207  // | Less | Equal | Greater |
208  // First the original pivot value at the end:
209  swap(this->Data[range[1] - 1], this->Data[dataCursors[0]]);
210 
211  // Update the cursors to either side of the pivot:
212  dataCursors = vtkm::Id2(dataCursors[0] - 1, dataCursors[0] + 1);
213 
214  for (vtkm::Id i = range[0]; i < pivotCursors[0]; ++i, --dataCursors[0])
215  {
216  swap(this->Data[i], this->Data[dataCursors[0]]);
217  }
218 
219  for (vtkm::Id i = range[1] - 2; i > pivotCursors[1]; --i, ++dataCursors[1])
220  {
221  swap(this->Data[i], this->Data[dataCursors[1]]);
222  }
223 
224  // Adjust the cursor so we can use them to construct the regions for the
225  // recursive call:
226  ++dataCursors[0];
227 
228  return dataCursors;
229  }
230 
231  void Sort(const vtkm::Id2 range)
232  {
233  const vtkm::Id numVals = range[1] - range[0];
234  if (numVals <= this->SerialSize)
235  {
236  std::sort(this->Data + range[0], this->Data + range[1], this->Compare);
237  return;
238  }
239 
240  const vtkm::Id2 pivots = this->PartitionData(range);
241  const vtkm::Id2 lhRange = vtkm::Id2(range[0], pivots[0]);
242  const vtkm::Id2 rhRange = vtkm::Id2(pivots[1], range[1]);
243 
244  // Intel compilers seem to have trouble following the 'this' pointer
245  // when launching tasks, resulting in a corrupt task environment.
246  // Explicitly copying the pointer into a local variable seems to fix this.
247  auto explicitThis = this;
248 
249  VTKM_OPENMP_DIRECTIVE(task default(none) firstprivate(rhRange, explicitThis))
250  {
251  explicitThis->Sort(rhRange);
252  }
253 
254  this->Sort(lhRange);
255  }
256 };
257 }
258 } // end namespace sort::quick
259 }
260 }
261 } // end namespace vtkm::cont::openmp
vtkm::cont::openmp::sort::quick::QuickSorter::PseudoMedianOf9
vtkm::Pair< vtkm::Id, ValueType > PseudoMedianOf9(const vtkm::Id ids[9]) const
Definition: ParallelQuickSortOpenMP.h:121
ArrayHandle.h
FunctorsGeneral.h
vtkm::cont::openmp::sort::quick::QuickSorter::SerialSize
vtkm::Id SerialSize
Definition: ParallelQuickSortOpenMP.h:42
vtkm
Groups connected points that have the same field value.
Definition: Atomic.h:19
Types.h
vtkm::cont::openmp::sort::quick::QuickSorter::SelectPivot
vtkm::Pair< vtkm::Id, ValueType > SelectPivot(const vtkm::Id2 range) const
Definition: ParallelQuickSortOpenMP.h:128
FunctorsOpenMP.h
DeviceAdapterTagOpenMP.h
vtkm::cont::openmp::sort::quick::QuickSorter::MedianOf3
vtkm::Pair< vtkm::Id, ValueType > MedianOf3(const vtkm::Pair< vtkm::Id, ValueType > &v1, const vtkm::Pair< vtkm::Id, ValueType > &v2, const vtkm::Pair< vtkm::Id, ValueType > &v3) const
Definition: ParallelQuickSortOpenMP.h:72
vtkm::Pair::first
FirstType first
The pair's first object.
Definition: Pair.h:50
vtkm::cont::openmp::sort::quick::QuickSorter::BinaryCompare
vtkm::cont::internal::WrappedBinaryOperator< bool, RawBinaryCompare > BinaryCompare
Definition: ParallelQuickSortOpenMP.h:37
vtkm::cont::openmp::sort::quick::QuickSorter::Compare
BinaryCompare Compare
Definition: ParallelQuickSortOpenMP.h:41
vtkm::Id
vtkm::Int64 Id
Base type to use to index arrays.
Definition: Types.h:227
VTKM_OPENMP_DIRECTIVE
#define VTKM_OPENMP_DIRECTIVE(directive)
Definition: FunctorsOpenMP.h:37
vtkm::cont::openmp::sort::quick::QuickSorter::QuickSorter
QuickSorter(IterType iter, RawBinaryCompare comp)
Definition: ParallelQuickSortOpenMP.h:44
vtkm::cont::openmp::sort::quick::QuickSorter::ValueType
typename std::iterator_traits< IterType >::value_type ValueType
Definition: ParallelQuickSortOpenMP.h:38
vtkm::Vec< vtkm::Id, 2 >
vtkm::cont::openmp::sort::quick::QuickSorter
Definition: ParallelQuickSortOpenMP.h:35
vtkm::cont::openmp::sort::quick::QuickSorter::Prepare
void Prepare(const vtkm::Id2)
Definition: ParallelQuickSortOpenMP.h:64
vtkm::cont::openmp::sort::quick::QuickSorter::Execute
void Execute(const vtkm::Id2 range)
Definition: ParallelQuickSortOpenMP.h:51
vtkm::cont::openmp::sort::quick::QuickSorter::PartitionData
vtkm::Id2 PartitionData(const vtkm::Id2 range)
Definition: ParallelQuickSortOpenMP.h:155
vtkm::cont::openmp::sort::quick::QuickSorter::Sort
void Sort(const vtkm::Id2 range)
Definition: ParallelQuickSortOpenMP.h:231
vtkm::cont::openmp::sort::quick::QuickSorter::MedianOf3
vtkm::Pair< vtkm::Id, ValueType > MedianOf3(const vtkm::Id ids[3]) const
Definition: ParallelQuickSortOpenMP.h:114
vtkm::Pair
A vtkm::Pair is essentially the same as an STL pair object except that the methods (constructors and ...
Definition: Pair.h:29
vtkm::Pair::second
SecondType second
The pair's second object.
Definition: Pair.h:55
vtkm::Id2
vtkm::Vec< vtkm::Id, 2 > Id2
Id2 corresponds to a 2-dimensional index.
Definition: Types.h:926
vtkm::cont::openmp::sort::quick::QuickSorter::Data
IterType Data
Definition: ParallelQuickSortOpenMP.h:40