4
votes

I am new to parallel programming and I am unsure why the QuickSortParallel method is slower than my sequential version (Without the Parallel.Invoke). I have a jagged array that consists of a hundred thousand 9 digit numbers that I pass to be sorted. Unfortunately, when I use the QuickSortParallel method it ends up being almost 5 times slower than the sequential version.

Could I do more than just using Parallel.Invoke on the data source?

    public static void QuickSort_Parallel<T>(T[] array2) where T : IComparable<T>
    {
        QuickSortParallel(array2, 0, array2.Length - 1);
    }

    private static void QuickSortParallel<T>(T[] array2, int left, int right)
        where T : IComparable<T>
    {
        if (left >= right)
        {
            return;
        }

        SwapElements(array2, left, (left + right) / 2); //median pivot
        int last = left;
        for (int current = left + 1; current <= right; ++current)
        {
            //CompareTo, compares current array index value with
            if (array2[current].CompareTo(array2[left]) < 0)
            {
                ++last;
                SwapElements(array2, last, current);
            }
        }

        SwapElements(array2, left, last);
        //Recursive
        //Executes each of the provided actions in parallel.
        Parallel.Invoke(
            () => QuickSortParallel(array2, left, last - 1),
            () => QuickSortParallel(array2, last + 1, right)
        );
    }

    static void SwapElements<T>(T[] array2, int i, int j)
    {
        T temp = array2[i];
        array2[i] = array2[j];
        array2[j] = temp;
    }
3
What are you actual timings like? Are we talking milliseconds or seconds? (I'm hoping ms for just 100K elements.) - Larry OBrien
Sequential 1.4sec, Parallel 5.8sec - Colin Roe
What about checking recursive depth and going sequencial (calling QuickSortParallel() sequencially) after it exceed a certain thresold (ex : greater than the NumberOfPhysicalCPU * someconstant ) ? - tigrou

3 Answers

2
votes

More than likely, you're problems are coming from overhead with the threads.

Using threads normally makes CPU intensive work faster, however starting a new thread involves substantial overhead, and if you're giving too many threads too little work, then you can make your program run slower.

When you run the following line:

    Parallel.Invoke(
        () => QuickSortParallel(array2, left, last - 1),
        () => QuickSortParallel(array2, last + 1, right)
    );

...you are possibly causing the current thread to spawn two more threads (depending on how Parallel.Invoke is implemented). If my mental math is correct (and if Parallel.Invoke does indeed create new threads), you're creating n * log(n) threads--an enormous number!

If you want to see performance gains, you need to balance the # of threads--more is not always better. A good way to limit the number of threads by using the system thread pool:

System.Threading.ThreadPool.QueueUserWorkItem(
        () => QuickSortParallel(array2, left, last - 1));
System.Threading.ThreadPool.QueueUserWorkItem(
        () => QuickSortParallel(array2, last + 1, right));

...or something along those lines. You could also implement your own threading pool, if you feel inclined to do so. a

Another option is to limit the depth of recursion, thus limiting the number of threads.

0
votes

Contention surrounding the use of the SwapElements method? Try dropping the logic of the SwapElements method right into your method/loop, rather than calling out to it...this will allow it to be a part of what happens on each parallell thread that is spawned, rather than being a potential bottleneck. See if that get's you anything...just a suggestion though, not sure if it will prove useful.