4141#ifndef CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_
4242#define CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_
4343
44+ #if defined(_OPENMP) && defined(__GNUC__) && !defined(__clang__)
45+ #include < parallel/algorithm>
46+ #define SORT __gnu_parallel::sort
47+ #else
48+ #define SORT std::sort
49+ #endif
50+ // TODO(h-2): for clang use std::experimenta::parallel if available
51+
4452namespace SEQAN_NAMESPACE_MAIN
4553{
4654
@@ -161,52 +169,55 @@ struct _ZeroBucketComparator<TSAValue, Nothing const>
161169// struct RadixSortContext_
162170// ----------------------------------------------------------------------------
163171
164- template <typename TAccessFunctor, // text accessor
165- typename TOrderFunctor, // For seperate sort of the 0 bucket.
172+ template <typename TSAValue,
173+ typename TText,
166174 typename TSize, // type of depth and bucketCount a.s.o
167175 unsigned Q> // alph size = ValueSize + 1
168176struct RadixSortContext_
169177{
170- static_assert (Q < 256 , " Alphabet size must be smaller 256!" ); // TODO really?
171- typedef typename TAccessFunctor::argument_type TSAValue;
172- typedef typename TAccessFunctor::result_type TOrdValue; // unsigned
178+ typedef typename StringSetLimits<TText const >::Type TLimitsString; // "Nothing" for Strings
179+ typedef RadixTextAccessor<TSAValue, TText> TAccessFunctor;
180+ typedef _ZeroBucketComparator<TSAValue, TLimitsString> TOrderFunctor;
181+ typedef typename TAccessFunctor::result_type TOrdValue;
173182
183+ static_assert (Q < 256 , " Alphabet size must be smaller 256!" ); // TODO really?
174184 static const unsigned ORACLESIZE = 256 ;
175- TAccessFunctor textAccess;
176- TOrderFunctor comp;
185+
186+ TText const & text;
187+ TAccessFunctor textAccess;
188+ TOrderFunctor comp;
177189
178190 TSize bucketSize[Q];
179191 std::array<TSAValue*,Q> bucketEnd;
180192
181- RadixSortContext_ (TAccessFunctor const & f, TOrderFunctor const & c ) :
182- textAccess (f ), comp(c )
193+ RadixSortContext_ (TText const & t ) :
194+ text (t), textAccess(t ), comp(stringSetLimits(t) )
183195 {}
184196};
185197
186- template <typename TAccessFunctor ,
187- typename TOrderFunctor ,
198+ template <typename TSAValue ,
199+ typename TText ,
188200 typename TSize,
189201 unsigned Q>
190202inline void
191- clear (RadixSortContext_<TAccessFunctor, TOrderFunctor , TSize, Q> & context)
203+ clear (RadixSortContext_<TSAValue, TText , TSize, Q> & context)
192204{
193205 memset (context.bucketSize , 0 , sizeof (TSize)*Q);
194206}
195207
196208// ----------------------------------------------------------------------------
197- // Function radixSort ()
209+ // Function _radixSort ()
198210// ----------------------------------------------------------------------------
199211
200212template <typename TSAValue, typename TSize,
201- typename TAccessFunctor, typename TOrderFunctor , unsigned Q>
213+ typename TText , unsigned Q>
202214inline void
203- radixSort (std::vector<std::tuple<TSAValue*, TSAValue*, TSize> > & stack,
204- RadixSortContext_<TAccessFunctor, TOrderFunctor , TSize, Q> & context,
205- std::tuple<TSAValue*, TSAValue*, TSize> const & item)
215+ _radixSort (std::vector<std::tuple<TSAValue*, TSAValue*, TSize> > & stack,
216+ RadixSortContext_<TSAValue, TText , TSize, Q> & context,
217+ std::tuple<TSAValue*, TSAValue*, TSize> const & item)
206218{
207- typedef RadixSortContext_<TAccessFunctor, TOrderFunctor , TSize, Q> TContext;
219+ typedef RadixSortContext_<TSAValue, TText , TSize, Q> TContext;
208220 typedef typename TContext::TOrdValue TOrdValue;
209- static_assert (std::is_same<TSAValue, typename TContext::TSAValue>::value, " TSAValue mismatch!" );
210221
211222 clear (context);
212223
@@ -261,85 +272,121 @@ radixSort(std::vector<std::tuple<TSAValue*, TSAValue*, TSize> > & stack,
261272}
262273
263274// ----------------------------------------------------------------------------
264- // Function inplaceFullRadixSort() [default]
275+ // Function _radixSortWrapper()
265276// ----------------------------------------------------------------------------
277+ // switch to quicksort if the interval is sufficiently small
266278
267279// TODO: play with this value
280+ #ifndef _RADIX_SORT_SWITCH_TO_QUICKSORT_AT
268281#define _RADIX_SORT_SWITCH_TO_QUICKSORT_AT 100
282+ #endif
283+
284+ template <typename TSAValue, typename TSize,
285+ typename TText, unsigned Q>
286+ inline void
287+ _radixSortWrapper (std::vector<std::tuple<TSAValue*, TSAValue*, TSize> > & stack,
288+ RadixSortContext_<TSAValue, TText, TSize, Q> & context,
289+ std::tuple<TSAValue*, TSAValue*, TSize> const & i)
290+ {
291+ if (std::get<1 >(i) - std::get<0 >(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT)
292+ std::sort (std::get<0 >(i), std::get<1 >(i), SuffixLess_<TSAValue, TText const >(context.text , std::get<2 >(i)));
293+ else if (std::get<1 >(i) - std::get<0 >(i) >= 2 )
294+ _radixSort (stack, context, i);
295+ }
296+
297+ // ----------------------------------------------------------------------------
298+ // Function inplaceFullRadixSort() [default]
299+ // ----------------------------------------------------------------------------
269300
270301#ifdef _OPENMP
271- #define N_THREADS omp_get_max_threads ()
272- #define I_THREAD omp_get_thread_num ()
302+ #define N_THREADS omp_get_max_threads ()
303+ #define I_THREAD omp_get_thread_num ()
304+ #define MIN_BUCKETS 512
273305#else
274- #define N_THREADS 1
275- #define I_THREAD 0
306+ #define N_THREADS 1
307+ #define I_THREAD 0
308+ #define MIN_BUCKETS 100 // for somewhat decent progress reporting
276309#endif
277310
278311// TODO: serial version
279312// TODO: possibly do multiple runs of "secondStep" if alphabet size to small
280313// TODO: possibly quicksort directly on buckets in third steps, if buckets have been made small enough
314+ // TODO: double-check the effects of the new "secondStep"
281315
282316template <typename TSA , typename TText, typename TLambda>
283- void inPlaceRadixSort (TSA & sa, TText const & str , TLambda const & progressCallback = [] (unsigned ) {})
317+ void inPlaceRadixSort (TSA & sa, TText const & text , TLambda const & progressCallback = [] (unsigned ) {})
284318{
285- typedef typename Value<typename Concatenator<TText>::Type>::Type TAlphabet;
286- typedef typename Value<TSA >::Type TSAValue;
287- typedef typename Size<TText>::Type TSize;
288- typedef std::tuple<TSAValue*, TSAValue*, TSize> TItem;
289- typedef typename StringSetLimits<TText const >::Type TLimitsString; // "Nothing" for Strings
290-
291- typedef RadixTextAccessor<TSAValue, TText> TAccessor;
292- typedef _ZeroBucketComparator<TSAValue,TLimitsString> TZeroComp;
319+ typedef typename Value<typename Concatenator<TText>::Type>::Type TAlphabet;
320+ typedef typename Value<TSA >::Type TSAValue;
321+ typedef typename Size<TText>::Type TSize;
322+ typedef std::tuple<TSAValue*, TSAValue*, TSize> TItem;
293323
294324 static const unsigned SIGMA = static_cast <unsigned >(ValueSize<TAlphabet>::VALUE ) + 1 ;
295325 SEQAN_ASSERT_LT_MSG (SIGMA , 1000u , " Attention: inplace radix sort is not suited for large alphabets" );
296326
297- typedef RadixSortContext_<TAccessor, TZeroComp , TSize, SIGMA > TContext;
327+ typedef RadixSortContext_<TSAValue, TText , TSize, SIGMA > TContext;
298328
299- if (empty (sa)) return ; // otherwise access sa[0] fails
329+ if (empty (sa))
330+ return ; // otherwise access sa[0] fails
300331
301332 /* stacks */
302333 std::vector<TItem> firstStack;
303334 firstStack.reserve (SIGMA );
304335 std::vector<TItem> secondStack;
305- secondStack.reserve (SIGMA * SIGMA );
336+ secondStack.reserve (1000 );
306337 std::vector<std::vector<TItem>> lStack (N_THREADS ); // one per thread
307338 // reduce memory allocations in threads by reserving space
308339 for (auto & stack : lStack)
309340 stack.reserve (length (sa) / 1000 );
310341
311342 /* contexts */
312- TContext firstSecondContext{TAccessor (str), TZeroComp ( stringSetLimits (str)) };
313- std::vector<TContext> lContext (N_THREADS , TContext{TAccessor (str), TZeroComp ( stringSetLimits (str)) });
343+ TContext firstSecondContext{text };
344+ std::vector<TContext> lContext (N_THREADS , TContext{text });
314345
346+ // FIRST STEP
315347 // sort by the first character
316- radixSort (firstStack, firstSecondContext, TItem (&sa[0 ], &sa[0 ]+length (sa), 0 ));
317-
348+ _radixSortWrapper (firstStack, firstSecondContext, TItem (&sa[0 ], &sa[0 ]+length (sa), 0 ));
318349 progressCallback (5 ); // 5% progress guess after first char
319350
320- // sort by second character
321- SEQAN_OMP_PRAGMA (parallel for schedule (dynamic))
322- for (unsigned j = 0 ; j < length (firstStack); ++j)
351+ // SECOND STEP
352+ // sort by next n characters until the stack has reached a good size for distinct parallelization
353+ // NOTE that for small alphabets in combination with small texts, this step might sort the entire SA
354+ while (!firstStack.empty ())
323355 {
324- TItem & i = firstStack[j];
325- if (std::get<1 >(i) - std::get<0 >(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT)
326- std::sort (std::get<0 >(i),
327- std::get<1 >(i),
328- SuffixLess_<TSAValue, TText const >(str, std::get<2 >(i)));
329- else if (std::get<1 >(i) - std::get<0 >(i) >= 2 )
330- radixSort (lStack[I_THREAD ], lContext[I_THREAD ], i);
331- }
356+ SEQAN_OMP_PRAGMA (parallel for schedule (dynamic))
357+ for (unsigned j = 0 ; j < length (firstStack); ++j)
358+ _radixSortWrapper (lStack[I_THREAD ], lContext[I_THREAD ], firstStack[j]);
332359
333- // merge local stacks and clear for next round
334- for (auto & stack : lStack)
335- {
336- secondStack.insert (secondStack.end (), stack.begin (), stack.end ());
337- stack.clear ();
338- }
360+ // merge local stacks and clear for next round or next step
361+ for (auto & stack : lStack)
362+ {
363+ secondStack.insert (secondStack.end (), stack.begin (), stack.end ());
364+ stack.clear ();
365+ }
339366
340- progressCallback (10 ); // 10% progress guess after second char
367+ // sort the stack by interval size so that large intervals are moved to front
368+ // this improves parallelization of dynamic schedule
369+ SORT (secondStack.begin (), secondStack.end (),
370+ [] (TItem const & l, TItem const & r)
371+ {
372+ return (std::get<1 >(l) - std::get<0 >(l)) > (std::get<1 >(r) - std::get<0 >(r));
373+ });
374+
375+ // check if largest interval "fits" in one thread efficiently
376+ // this works independently of alphabet size and just depends on the data
377+ // MIN_BUCKETS check additionally guarantees a degree of granularity
378+ if ((uint64_t (std::get<1 >(secondStack.front ()) - std::get<0 >(secondStack.front ())) <= (length (sa) / N_THREADS ))
379+ && (secondStack.size () >= MIN_BUCKETS ))
380+ break ;
381+
382+ // switch buffers for next round
383+ firstStack.clear ();
384+ std::swap (firstStack, secondStack);
385+ }
386+ progressCallback (10 ); // 10% progress guess after second step
341387
342- // sort rest
388+ // THIRD STEP
389+ // sort the remaining intervals distinctly; here no locking and syncing is required anymore
343390 SEQAN_OMP_PRAGMA (parallel for schedule (dynamic))
344391 for (unsigned j = 0 ; j < secondStack.size (); ++j)
345392 {
@@ -349,21 +396,15 @@ void inPlaceRadixSort(TSA & sa, TText const & str, TLambda const & progressCallb
349396 {
350397 TItem i = lStack[I_THREAD ].back ();
351398 lStack[I_THREAD ].pop_back ();
352-
353- if (std::get<1 >(i) - std::get<0 >(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT)
354- std::sort (std::get<0 >(i),
355- std::get<1 >(i),
356- SuffixLess_<TSAValue, TText const >(str, std::get<2 >(i)));
357- else if (std::get<1 >(i) - std::get<0 >(i) >= 2 )
358- radixSort (lStack[I_THREAD ], lContext[I_THREAD ], i);
399+ _radixSortWrapper (lStack[I_THREAD ], lContext[I_THREAD ], i);
359400 }
360401
361402 // progressCallback must be thread safe and cope with smaller numbers after big numbers
362403 // remaining characters alloted 90% of total progress
363404 progressCallback (10 + (j * 90 ) / secondStack.size ());
364405 }
365406
366- progressCallback (100 ); // done
407+ // progressCallback(100); // done
367408}
368409
369410}
0 commit comments