Merge pull request #3647 from BrzVlad/fix-sgen-internal-alloc
[mono.git] / mcs / class / referencesource / System.Core / System / Linq / Parallel / Utils / Sorting.cs
1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
7 //
8 // Sorting.cs
9 //
10 // <OWNER>[....]</OWNER>
11 //
12 // Support for sorting.
13 //
14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
15 using System;
16 using System.Collections.Generic;
17 using System.Threading;
18 using System.Diagnostics.Contracts;
19
20 namespace System.Linq.Parallel
21 {
22
23     //---------------------------------------------------------------------------------------
24     // The sort helper abstraction hides the implementation of our parallel merge sort.  See
25     // comments below for more details.  In summary, there will be one sort helper per
26     // partition.  Each will, in parallel read the whole key/value set from its input,
27     // perform a local sort on this data, and then cooperatively merge with other concurrent
28     // tasks to generate a single sorted output.  The local sort step is done using a simple
29     // quick-sort algorithm.  Then we use a log(p) reduction to perform merges in parallel;
30     // during each round of merges, half of the threads will stop doing work and may return.
31     // At the end, one thread will remain and it holds the final sorted output.
32     //
33
34     internal abstract class SortHelper<TInputOutput>
35     {
36         internal abstract TInputOutput[] Sort();
37     }
38
39     internal class SortHelper<TInputOutput, TKey> : SortHelper<TInputOutput>, IDisposable
40     {
41
42         private QueryOperatorEnumerator<TInputOutput, TKey> m_source; // The data source from which to pull data.
43         private int m_partitionCount; // The partition count.
44         private int m_partitionIndex; // This helper's index.
45
46         // This data is shared among all partitions.
47         private QueryTaskGroupState m_groupState; // To communicate status, e.g. cancellation.
48         private int[][] m_sharedIndices; // Shared set of indices used during sorting.
49         private GrowingArray<TKey>[] m_sharedKeys; // Shared keys with which to compare elements.
50         private TInputOutput[][] m_sharedValues; // The actual values used for comparisons.
51         private Barrier[,] m_sharedBarriers; // A matrix of barriers used for synchronizing during merges.
52         private OrdinalIndexState m_indexState; // State of the order index
53         private IComparer<TKey> m_keyComparer; // Comparer for the order keys
54
55         //---------------------------------------------------------------------------------------
56         // Creates a single sort helper object.  This is marked private to ensure the only
57         // snippet of code that creates one is the factory, since creating many implies some
58         // implementation detail in terms of dependencies which other places in the codebase
59         // shouldn't need to worry about.
60         //
61
62         private SortHelper(QueryOperatorEnumerator<TInputOutput, TKey> source, int partitionCount, int partitionIndex,
63             QueryTaskGroupState groupState, int[][] sharedIndices,
64             OrdinalIndexState indexState, IComparer<TKey> keyComparer,
65             GrowingArray<TKey>[] sharedkeys, TInputOutput[][] sharedValues, Barrier[,] sharedBarriers)
66         {
67             Contract.Assert(source != null);
68             Contract.Assert(groupState != null);
69             Contract.Assert(sharedIndices != null);
70             Contract.Assert(sharedkeys != null);
71             Contract.Assert(sharedValues != null);
72             Contract.Assert(sharedBarriers != null);
73 #if !MONO
74             Contract.Assert(groupState.CancellationState.MergedCancellationToken != null);
75 #endif
76             Contract.Assert(sharedIndices.Length <= sharedkeys.Length);
77             Contract.Assert(sharedIndices.Length == sharedValues.Length);
78             Contract.Assert(sharedIndices.Length == sharedBarriers.GetLength(1));
79 #if !MONO            
80             Contract.Assert(groupState.CancellationState.MergedCancellationToken != null);
81 #endif
82
83             m_source = source;
84             m_partitionCount = partitionCount;
85             m_partitionIndex = partitionIndex;
86             m_groupState = groupState;
87             m_sharedIndices = sharedIndices;
88             m_indexState = indexState;
89             m_keyComparer = keyComparer;
90             m_sharedKeys = sharedkeys;
91             m_sharedValues = sharedValues;
92             m_sharedBarriers = sharedBarriers;
93
94             Contract.Assert(m_sharedKeys.Length >= m_sharedValues.Length);
95         }
96
97         //---------------------------------------------------------------------------------------
98         // Factory method to create a bunch of sort helpers that are all related.  Once created,
99         // these helpers must all run concurrently with one another.
100         //
101         // Arguments:
102         //     partitions    - the input data partitions to be sorted
103         //     groupState    - common state used for communication (e.g. cancellation)
104         //
105         // Return Value:
106         //     An array of helpers, one for each partition.
107         //
108
109         internal static SortHelper<TInputOutput, TKey>[] GenerateSortHelpers(
110             PartitionedStream<TInputOutput, TKey> partitions, QueryTaskGroupState groupState)
111         {
112             int degreeOfParallelism = partitions.PartitionCount;
113             SortHelper<TInputOutput, TKey>[] helpers = new SortHelper<TInputOutput, TKey>[degreeOfParallelism];
114
115             // Calculate the next highest power of two greater than or equal to the DOP.
116             // Also, calculate phaseCount = log2(degreeOfParallelismPow2)
117             int degreeOfParallelismPow2 = 1, phaseCount = 0;
118             while (degreeOfParallelismPow2 < degreeOfParallelism)
119             {
120                 phaseCount++;
121                 degreeOfParallelismPow2 <<= 1;
122             }
123
124             // Initialize shared objects used during sorting.
125             int[][] sharedIndices = new int[degreeOfParallelism][];
126             GrowingArray<TKey>[] sharedKeys = new GrowingArray<TKey>[degreeOfParallelism];
127             TInputOutput[][] sharedValues = new TInputOutput[degreeOfParallelism][];
128             Barrier[,] sharedBarriers = new Barrier[phaseCount, degreeOfParallelism];
129
130             if (degreeOfParallelism > 1)
131             {
132                 // Initialize the barriers we need.  Due to the logarithmic reduction, we don't
133                 // need to populate the whole matrix.
134                 int offset = 1;
135                 for (int i = 0; i < sharedBarriers.GetLength(0); i++)
136                 {
137                     for (int j = 0; j < sharedBarriers.GetLength(1); j++)
138                     {
139                         // As the phases increase, the barriers required become more and more sparse.
140                         if ((j % offset) == 0)
141                         {
142                             sharedBarriers[i, j] = new Barrier(2);
143                         }
144                     }
145                     offset *= 2;
146                 }
147             }
148
149             // Lastly populate the array of sort helpers.
150             for (int i = 0; i < degreeOfParallelism; i++)
151             {
152                 helpers[i] = new SortHelper<TInputOutput, TKey>(
153                     partitions[i], degreeOfParallelism, i,
154                     groupState, sharedIndices,
155                     partitions.OrdinalIndexState, partitions.KeyComparer,
156                     sharedKeys, sharedValues, sharedBarriers);
157             }
158
159             return helpers;
160         }
161
162         //---------------------------------------------------------------------------------------
163         // Disposes of this sort helper's expensive state.
164         //
165
166         public void Dispose()
167         {
168             // We only dispose of the barriers when the 1st partition finishes.  That's because
169             // all others depend on the shared barriers, so we can't get rid of them eagerly.
170             if (m_partitionIndex == 0)
171             {
172                 for (int i = 0; i < m_sharedBarriers.GetLength(0); i++)
173                 {
174                     for (int j = 0; j < m_sharedBarriers.GetLength(1); j++)
175                     {
176                         Barrier b = m_sharedBarriers[i, j];
177                         if (b != null)
178                         {
179                             b.Dispose();
180                         }
181                     }
182                 }
183             }
184         }
185
186         //---------------------------------------------------------------------------------------
187         // Sorts the data, possibly returning a result.
188         //
189         // Notes:
190         //     This method makes some pretty fundamental assumptions about what concurrency
191         //     exists in the system.  Namely, it assumes all SortHelpers are running in
192         //     parallel.  If they aren't Sort will end up waiting for certain events that
193         //     will never happen -- i.e. we will deadlock.
194         //
195
196         internal override TInputOutput[] Sort()
197         {
198             // Step 1.  Accumulate this partitions' worth of input.
199             GrowingArray<TKey> sourceKeys = null;
200             List<TInputOutput> sourceValues = null;
201
202             BuildKeysFromSource(ref sourceKeys, ref sourceValues);
203
204             Contract.Assert(sourceValues != null, "values weren't populated");
205             Contract.Assert(sourceKeys != null, "keys weren't populated");
206
207             // Step 2.  Locally sort this partition's key indices in-place.
208             QuickSortIndicesInPlace(sourceKeys, sourceValues, m_indexState);
209
210             // Step 3. Enter into the merging phases, each separated by several barriers.
211             if (m_partitionCount > 1)
212             {
213                 // We only need to merge if there is more than 1 partition.
214                 MergeSortCooperatively();
215             }
216
217             return m_sharedValues[m_partitionIndex];
218         }
219
220         //-----------------------------------------------------------------------------------
221         // Generates a list of values and keys from the data source.  After calling this,
222         // the keys and values lists will be populated; each key at index i corresponds to
223         // the value at index i in the other list.
224         //
225         // Notes:
226         //    Should only be called once per sort helper.
227         //
228
229         private void BuildKeysFromSource(ref GrowingArray<TKey> keys, ref List<TInputOutput> values)
230         {
231             values = new List<TInputOutput>();
232
233             // Enumerate the whole input set, generating a key set in the process.
234             CancellationToken cancelToken = m_groupState.CancellationState.MergedCancellationToken;
235             try
236             {
237                 TInputOutput current = default(TInputOutput);
238                 TKey currentKey = default(TKey);
239                 bool hadNext = m_source.MoveNext(ref current, ref currentKey);
240
241                 if (keys == null)
242                 {
243                     keys = new GrowingArray<TKey>();
244                 }
245
246                 if (hadNext)
247                 {
248                     int i = 0;
249                     do
250                     {
251                         if ((i++ & CancellationState.POLL_INTERVAL) == 0)
252                             CancellationState.ThrowIfCanceled(cancelToken);
253
254                         // Accumulate the keys and values so that we can sort them in a moment.
255                         keys.Add(currentKey);
256                         values.Add(current);
257                     }
258                     while (m_source.MoveNext(ref current, ref currentKey));
259                 }
260             }
261             finally
262             {
263                 m_source.Dispose();
264             }
265         }
266
267         //-----------------------------------------------------------------------------------
268         // Produces a list of indices and sorts them in place using a local sort.
269         //
270         // Notes:
271         //     Each element in the indices array is an index which refers to an element in
272         //     the key/value array.  After calling this routine, the indices will be ordered
273         //     such that the keys they refere to are in ascending or descending order,
274         //     according to the sort criteria used.
275         //
276
277         private void QuickSortIndicesInPlace(GrowingArray<TKey> keys, List<TInputOutput> values, OrdinalIndexState ordinalIndexState)
278         {
279             Contract.Assert(keys != null);
280             Contract.Assert(values != null);
281             Contract.Assert(keys.Count == values.Count);
282
283             // Generate a list of keys in forward order.  We will sort them in a moment.
284             int[] indices = new int[values.Count];
285             for (int i = 0; i < indices.Length; i++)
286             {
287                 indices[i] = i;
288             }
289
290             // Now sort the indices in place.
291             if (indices.Length > 1
292                 && ordinalIndexState.IsWorseThan(OrdinalIndexState.Increasing))
293             {
294                 QuickSort(0, indices.Length - 1, keys.InternalArray, indices, m_groupState.CancellationState.MergedCancellationToken);
295             }
296
297             if (m_partitionCount == 1)
298             {
299                 // If there is only one partition, we will produce the final value set now,
300                 // since there will be no merge afterward (which is where we usually do this).
301                 TInputOutput[] sortedValues = new TInputOutput[values.Count];
302                 for (int i = 0; i < indices.Length; i++)
303                 {
304                     sortedValues[i] = values[indices[i]];
305                 }
306                 m_sharedValues[m_partitionIndex] = sortedValues;
307             }
308             else
309             {
310                 // Otherwise, a merge will happen.  Generate the shared data structures.
311                 m_sharedIndices[m_partitionIndex] = indices;
312                 m_sharedKeys[m_partitionIndex] = keys;
313                 m_sharedValues[m_partitionIndex] = new TInputOutput[values.Count];
314
315                 // Copy local structures to shared space.
316                 values.CopyTo(m_sharedValues[m_partitionIndex]);
317             }
318         }
319
320         //-----------------------------------------------------------------------------------
321         // Works cooperatively with other concurrent sort helpers to produce a final sorted
322         // output list of data.  Here is an overview of the algorithm used.
323         //
324         // During each phase, we must communicate with a partner task.  As a simple
325         // illustration, imagine we have 8 partitions (P=8), numbered 0-7.  There will be
326         // Log2(O)+2 phases (separated by barriers), where O is the next power of two greater
327         // than or equal to P, in the sort operation:
328         //
329         //     Pairs:   (P = 8)
330         //        phase=L:     [0][1] [2][3] [4][5] [6][7]
331         //        phase=0:     [0,1]  [2,3]  [4,5]  [6,7]
332         //        phase=1:     [0,2]         [4,6]
333         //        phase=2:     [0,4]
334         //        phase=M:     [0]
335         //
336         // During phase L, each partition locally sorts its data.  Then, at each subsequent
337         // phase in the logarithmic reduction, two partitions are paired together and cooperate
338         // to accomplish a portion of the merge.  The left one then goes on to choose another
339         // partner, in the next phase, and the right one exits.  And so on, until phase M, when
340         // there is just one partition left (the 0th), which is when it may publish the final
341         // output from the sort operation.
342         //
343         // Notice we mentioned rounding up to the next power of two when determining the number
344         // of phases.  Values of P which aren't powers of 2 are slightly problematic, because
345         // they create a load imbalance in one of the partitions and heighten the depth of the
346         // logarithmic tree.  As an illustration, imagine this case:
347         //
348         //     Pairs:   (P = 5)
349         //        phase=L:    [0][1] [2][3] [4]
350         //        phase=0:    [0,1]  [2,3]  [4,X]  [X,X]
351         //        phase=1:    [0,2]         [4,X]
352         //        phase=2:    [0,4]
353         //        phase=M:    [0]
354         //
355         // Partition #4 in this example performs its local sort during phase L, but then has nothing
356         // to do during phases 0 and 2.  (I.e. it has nobody to merge with.)  Only during phase 2
357         // does it then resume work and help phase 2 perform its merge.  This is modeled a bit like
358         // there were actually 8 partitions, which is the next power of two greater than or equal to
359         // 5.  This example was chosen as an extreme case of imbalance.  We stall a processor (the 5th)
360         // for two complete phases.  If P = 6 or 7, the problem would not be nearly so bad, but if
361         // P = 9, the last partition would stall for yet another phase (and so on for every power of
362         // two boundary).  We handle these, cases, but note that an overabundance of them will probably
363         // negatively impact speedups.
364         //
365
366         private void MergeSortCooperatively()
367         {
368             CancellationToken cancelToken = m_groupState.CancellationState.MergedCancellationToken;
369
370             int phaseCount = m_sharedBarriers.GetLength(0);
371             for (int phase = 0; phase < phaseCount; phase++)
372             {
373                 bool isLastPhase = (phase == (phaseCount - 1));
374
375                 // Calculate our partner for this phase and the next.
376                 int partnerIndex = ComputePartnerIndex(phase);
377
378                 // If we have a partner (see above for non power of 2 cases and why the index returned might
379                 // be out of bounds), we will coordinate with the partner to produce the merged output.
380                 if (partnerIndex < m_partitionCount)
381                 {
382                     // Cache references to our local data.
383                     int[] myIndices = m_sharedIndices[m_partitionIndex];
384                     GrowingArray<TKey> myKeys = m_sharedKeys[m_partitionIndex];
385                     TKey[] myKeysArr = myKeys.InternalArray;
386
387                     TInputOutput[] myValues = m_sharedValues[m_partitionIndex];
388
389
390                     // First we must rendezvous with our merge partner so we know the previous sort
391                     // and merge phase has been completed.  By convention, we always use the left-most
392                     // partner's barrier for this; all that matters is that both uses the same.
393                     m_sharedBarriers[phase, Math.Min(m_partitionIndex, partnerIndex)].SignalAndWait(cancelToken);
394
395                     // Grab the two sorted inputs and then merge them cooperatively into one list.  One
396                     // worker merges from left-to-right until it's placed elements up to the half-way
397                     // point, and the other worker does the same, but only from right-to-left.
398                     if (m_partitionIndex < partnerIndex)
399                     {
400                         // Before moving on to the actual merge, the left-most partition will allocate data
401                         // to hold the merged indices and key/value pairs.
402
403                         // First, remember a copy of all of the partner's lists.
404                         int[] rightIndices = m_sharedIndices[partnerIndex];
405                         TKey[] rightKeys = m_sharedKeys[partnerIndex].InternalArray;
406                         TInputOutput[] rightValues = m_sharedValues[partnerIndex];
407
408                         // We copy the our own items into the right's (overwriting its values) so that it can
409                         // retrieve them after the barrier.  This is an exchange operation.
410                         m_sharedIndices[partnerIndex] = myIndices;
411                         m_sharedKeys[partnerIndex] = myKeys;
412                         m_sharedValues[partnerIndex] = myValues;
413
414                         int leftCount = myValues.Length;
415                         int rightCount = rightValues.Length;
416                         int totalCount = leftCount + rightCount;
417
418                         // Now allocate the lists into which the merged data will go.  Share this
419                         // with the other thread so that it can place data into it as well.
420                         int[] mergedIndices = null;
421                         TInputOutput[] mergedValues = new TInputOutput[totalCount];
422
423                         // Only on the last phase do we need to remember indices and keys.
424                         if (!isLastPhase)
425                         {
426                             mergedIndices = new int[totalCount];
427                         }
428
429                         // Publish our newly allocated merged data structures.
430                         m_sharedIndices[m_partitionIndex] = mergedIndices;
431                         m_sharedKeys[m_partitionIndex] = myKeys;
432                         m_sharedValues[m_partitionIndex] = mergedValues;
433
434                         Contract.Assert(myKeysArr != null);
435
436                         m_sharedBarriers[phase, m_partitionIndex].SignalAndWait(cancelToken);
437
438                         // Merge the left half into the shared merged space.  This is a normal merge sort with
439                         // the caveat that we stop merging once we reach the half-way point (since our partner
440                         // is doing the same for the right half).  Note that during the last phase we only
441                         // copy the values and not the indices or keys.
442                         int m = (totalCount + 1)/2;
443                         int i = 0, j0 = 0, j1 = 0;
444                         while (i < m)
445                         {
446                             if ((i & CancellationState.POLL_INTERVAL) == 0)
447                                 CancellationState.ThrowIfCanceled(cancelToken);
448
449                             if (j0 < leftCount && (j1 >= rightCount ||
450                                                    m_keyComparer.Compare(myKeysArr[myIndices[j0]],
451                                                                          rightKeys[rightIndices[j1]]) <= 0))
452                             {
453                                 if (isLastPhase)
454                                 {
455                                     mergedValues[i] = myValues[myIndices[j0]];
456                                 }
457                                 else
458                                 {
459                                     mergedIndices[i] = myIndices[j0];
460                                 }
461                                 j0++;
462                             }
463                             else
464                             {
465                                 if (isLastPhase)
466                                 {
467                                     mergedValues[i] = rightValues[rightIndices[j1]];
468                                 }
469                                 else
470                                 {
471                                     mergedIndices[i] = leftCount + rightIndices[j1];
472                                 }
473                                 j1++;
474                             }
475                             i++;
476                         }
477
478                         // If it's not the last phase, we just bulk propagate the keys and values.
479                         if (!isLastPhase && leftCount > 0)
480                         {
481                             Array.Copy(myValues, 0, mergedValues, 0, leftCount);
482                         }
483
484                         // And now just wait for the second half.  We never reuse the same barrier across multiple
485                         // phases, so we can always dispose of it when we wake up.
486                         m_sharedBarriers[phase, m_partitionIndex].SignalAndWait(cancelToken);
487                     }
488                     else
489                     {
490                         // Wait for the other partition to allocate the shared data.
491                         m_sharedBarriers[phase, partnerIndex].SignalAndWait(cancelToken);
492
493                         // After the barrier, the other partition will have made two things available to us:
494                         // (1) its own indices, keys, and values, stored in the cell that used to hold our data,
495                         // and (2) the arrays into which merged data will go, stored in its shared array cells.  
496                         // We will snag references to all of these things.
497                         int[] leftIndices = m_sharedIndices[m_partitionIndex];
498                         TKey[] leftKeys = m_sharedKeys[m_partitionIndex].InternalArray;
499                         TInputOutput[] leftValues = m_sharedValues[m_partitionIndex];
500                         int[] mergedIndices = m_sharedIndices[partnerIndex];
501                         GrowingArray<TKey> mergedKeys = m_sharedKeys[partnerIndex];
502                         TInputOutput[] mergedValues = m_sharedValues[partnerIndex];
503
504                         Contract.Assert(leftValues != null);
505                         Contract.Assert(leftKeys != null);
506
507                         int leftCount = leftValues.Length;
508                         int rightCount = myValues.Length;
509                         int totalCount = leftCount + rightCount;
510
511                         // Merge the right half into the shared merged space.  This is a normal merge sort with
512                         // the caveat that we stop merging once we reach the half-way point (since our partner
513                         // is doing the same for the left half).  Note that during the last phase we only
514                         // copy the values and not the indices or keys.
515                         int m = (totalCount + 1)/2;
516                         int i = totalCount - 1, j0 = leftCount - 1, j1 = rightCount - 1;
517                         while (i >= m)
518                         {
519                             if ((i & CancellationState.POLL_INTERVAL) == 0)
520                                 CancellationState.ThrowIfCanceled(cancelToken);
521
522                             if (j0 >= 0 && (j1 < 0 ||
523                                             m_keyComparer.Compare(leftKeys[leftIndices[j0]],
524                                                                   myKeysArr[myIndices[j1]]) > 0))
525                             {
526                                 if (isLastPhase)
527                                 {
528                                     mergedValues[i] = leftValues[leftIndices[j0]];
529                                 }
530                                 else
531                                 {
532                                     mergedIndices[i] = leftIndices[j0];
533                                 }
534                                 j0--;
535                             }
536                             else
537                             {
538                                 if (isLastPhase)
539                                 {
540                                     mergedValues[i] = myValues[myIndices[j1]];
541                                 }
542                                 else
543                                 {
544                                     mergedIndices[i] = leftCount + myIndices[j1];
545                                 }
546                                 j1--;
547                             }
548                             i--;
549                         }
550
551                         // If it's not the last phase, we just bulk propagate the keys and values.
552                         if (!isLastPhase && myValues.Length > 0)
553                         {
554                             mergedKeys.CopyFrom(myKeysArr, myValues.Length);
555                             Array.Copy(myValues, 0, mergedValues, leftCount, myValues.Length);
556                         }
557
558                         // Wait for our partner to finish copying too.
559                         m_sharedBarriers[phase, partnerIndex].SignalAndWait(cancelToken);
560
561                         // Now the greater of the two partners can leave, it's done.
562                         break;
563                     }
564                 }
565             }
566         }
567
568         //---------------------------------------------------------------------------------------
569         // Computes our partner index given the logarithmic reduction algorithm specified above.
570         //
571
572         private int ComputePartnerIndex(int phase)
573         {
574             int offset = 1 << phase;
575             return m_partitionIndex + ((m_partitionIndex % (offset * 2)) == 0 ? offset : -offset);
576         }
577
578         //---------------------------------------------------------------------------------------
579         // Sort algorithm used to sort key/value lists. After this has been called, the indices
580         // will have been placed in sorted order based on the keys provided.
581         //
582
583         private void QuickSort(int left, int right, TKey[] keys, int[] indices, CancellationToken cancelToken)
584         {
585             Contract.Assert(keys != null, "need a non-null keyset");
586             Contract.Assert(keys.Length >= indices.Length);
587             Contract.Assert(left <= right);
588             Contract.Assert(0 <= left && left < keys.Length);
589             Contract.Assert(0 <= right && right < keys.Length);
590
591             // cancellation check.
592             // only test for intervals that are wider than so many items, else this test is 
593             // relatively expensive compared to the work being performend.
594             if (right - left > CancellationState.POLL_INTERVAL)
595                 CancellationState.ThrowIfCanceled(cancelToken);
596
597             do
598             {
599                 int i = left;
600                 int j = right;
601                 int pivot = indices[i + ((j - i) >> 1)];
602                 TKey pivotKey = keys[pivot];
603
604                 do
605                 {
606                     while (m_keyComparer.Compare(keys[indices[i]], pivotKey) < 0) i++;
607                     while (m_keyComparer.Compare(keys[indices[j]], pivotKey) > 0) j--;
608
609                     Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) sort failed - bogus IComparer?");
610
611                     if (i > j)
612                     {
613                         break;
614                     }
615
616                     if (i < j)
617                     {
618                         // Swap the indices.
619                         int tmp = indices[i];
620                         indices[i] = indices[j];
621                         indices[j] = tmp;
622                     }
623
624                     i++;
625                     j--;
626                 }
627                 while (i <= j);
628
629                 if (j - left <= right - i)
630                 {
631                     if (left < j)
632                     {
633                         QuickSort(left, j, keys, indices, cancelToken);
634                     }
635                     left = i;
636                 }
637                 else
638                 {
639                     if (i < right)
640                     {
641                         QuickSort(i, right, keys, indices, cancelToken);
642                     }
643                     right = j;
644                 }
645             }
646             while (left < right);
647         }
648     }
649 }