【问题标题】:Fastest safe sorting algorithm implementation最快的安全排序算法实现
【发布时间】:2020-06-14 13:44:52
【问题描述】:

我花了一些时间在 C# 中实现快速排序算法。 完成后,我比较了我的实现速度和 C# 的 Array.Sort-Method。

我只是比较处理随机 int 数组的速度。

这是我的实现:

static void QuickSort(int[] data, int left, int right)
{
    int i = left - 1,
        j = right;

    while (true)
    {
        int d = data[left];
        do i++; while (data[i] < d);
        do j--; while (data[j] > d);

        if (i < j) 
        {
            int tmp = data[i];
            data[i] = data[j];
            data[j] = tmp;
        }
        else
        {
            if (left < j)    QuickSort(data, left, j);
            if (++j < right) QuickSort(data, j, right);
            return;
        }
    }
}

性能(对长度为 100000000 的随机 int[] 进行排序时):
- 我的算法:14.21 秒
- .Net Array.Sort: 14.84 秒

有人知道如何更快地实现我的算法吗?
或者任何人都可以提供更快的实现(不必是快速排序!)我运行得更快?

注意:
- 请不要使用使用多个内核/处理器来提高性能的算法
- 只有有效的 C# 源代码

如果我在线,我会在几分钟内测试所提供算法的性能。

编辑:
您认为对小于 8 的零件使用理想的排序网络会提高性能吗?

【问题讨论】:

  • 当一个数组已经排序时,尝试重复你的计时......然后使用一个只有两个项目顺序错误的数组......
  • 性能提升 4.3% - 你这样做是出于学术原因吗?
  • Ian 关于已经排序的数组是正确的。您选择的枢轴元素将具有可怕的最坏情况性能。话虽如此,加速快速排序是相当简单的。使用 MedianOfThree 方法选择更好的枢轴,并为小分区使用更合适的排序算法。我假设您这样做是为了个人研究,因为使用系统库排序方法几乎总是正确的答案。
  • 您可以展开其中一个递归分支。我不确定 C#/JIT 是否会这样做。
  • 我刚刚完成:尝试对已排序的数组进行排序时,我的算法变得非常慢。

标签: c# algorithm sorting


【解决方案1】:

二元插入排序几乎总是在短期运行(约 10 项)中获胜。由于简化的分支结构,它通常比理想的排序网络更好。

Dual pivot quicksort 比快速排序更快。链接的论文包含一个 Java 实现,您大概可以适应它。

如果您只对整数进行排序,radix sort 在长数组上可能会更快。

【讨论】:

    【解决方案2】:

    有谁知道如何实现我的 算法更快?

    通过将您的代码转换为使用指针,我能够将执行时间缩短 10%。

        public unsafe static void UnsafeQuickSort(int[] data)
        {
            fixed (int* pdata = data)
            {
                UnsafeQuickSortRecursive(pdata, 0, data.Length - 1);
            }
        }
    
        private unsafe static void UnsafeQuickSortRecursive(int* data, int left, int right)
        {
            int i = left - 1;
            int j = right;
    
            while (true)
            {
                int d = data[left];
                do i++; while (data[i] < d);
                do j--; while (data[j] > d);
    
                if (i < j)
                {
                    int tmp = data[i];
                    data[i] = data[j];
                    data[j] = tmp;
                }
                else
                {
                    if (left < j) UnsafeQuickSortRecursive(data, left, j);
                    if (++j < right) UnsafeQuickSortRecursive(data, j, right);
                    return;
                }
            }
        }
    

    【讨论】:

    • 有一个小错误,但升级非常好 (+1),在我的硬件上快了 1.1 秒!
    • 这与标题中的“安全”要求不冲突吗?
    • 说起来有点难……这也够简单的……我只是想确定我没有得到任何高度优化的 C/C++ 代码。
    • @Gabe:我在发帖之前也想过这个问题。我不确定“安全”是否与“稳定”同义。但是,考虑到快速排序并不稳定,无论如何这可能是一个有争议的问题。我还是决定继续发帖。
    • 我可能会拥有顶级函数,要么没有左/右参数,要么在调用之前进行一些边界检查。
    【解决方案3】:

    一种更快的随机整数数组排序算法是 LSD 基数排序:

        public static int[] SortRadix(this int[] inputArray)
        {
            const int bitsPerDigit = 8;
            const uint numberOfBins = 1 << bitsPerDigit;
            uint numberOfDigits = (sizeof(uint) * 8 + bitsPerDigit - 1) / bitsPerDigit;
            int d;
            var outputArray = new int[inputArray.Length];
    
            int[][] startOfBin = new int[numberOfDigits][];
            for (int i = 0; i < numberOfDigits; i++)
                startOfBin[i] = new int[numberOfBins];
            bool outputArrayHasResult = false;
    
            const uint bitMask = numberOfBins - 1;
            const uint halfOfPowerOfTwoRadix = PowerOfTwoRadix / 2;
            int shiftRightAmount = 0;
    
            uint[][] count = HistogramByteComponents(inputArray, 0, inputArray.Length - 1);
    
            for (d = 0; d < numberOfDigits; d++)
            {
                startOfBin[d][0] = 0;
                for (uint i = 1; i < numberOfBins; i++)
                    startOfBin[d][i] = startOfBin[d][i - 1] + (int)count[d][i - 1];
            }
    
            d = 0;
            while (d < numberOfDigits)
            {
                int[] startOfBinLoc = startOfBin[d];
    
                if (d != 3)
                    for (uint current = 0; current < inputArray.Length; current++)
                        outputArray[startOfBinLoc[((uint)inputArray[current] >> shiftRightAmount) & bitMask]++] = inputArray[current];
                else
                    for (uint current = 0; current < inputArray.Length; current++)
                        outputArray[startOfBinLoc[((uint)inputArray[current] >> shiftRightAmount) ^ halfOfPowerOfTwoRadix]++] = inputArray[current];
    
                shiftRightAmount += bitsPerDigit;
                outputArrayHasResult = !outputArrayHasResult;
                d++;
    
                int[] tmp = inputArray;       // swap input and output arrays
                inputArray = outputArray;
                outputArray = tmp;
            }
            return outputArrayHasResult ? outputArray : inputArray;
        }
        [StructLayout(LayoutKind.Explicit)]
        internal struct Int32ByteUnion
        {
            [FieldOffset(0)]
            public byte byte0;
            [FieldOffset(1)]
            public byte byte1;
            [FieldOffset(2)]
            public byte byte2;
            [FieldOffset(3)]
            public byte byte3;
    
            [FieldOffset(0)]
            public Int32 integer;
        }
        public static uint[][] HistogramByteComponents(int[] inArray, Int32 l, Int32 r)
        {
            const int numberOfBins = 256;
            const int numberOfDigits = sizeof(ulong);
            uint[][] count = new uint[numberOfDigits][];
            for (int i = 0; i < numberOfDigits; i++)
                count[i] = new uint[numberOfBins];
    
            var union = new Int32ByteUnion();
            for (int current = l; current <= r; current++)    // Scan the array and count the number of times each digit value appears - i.e. size of each bin
            {
                union.integer = inArray[current];
                count[0][union.byte0]++;
                count[1][union.byte1]++;
                count[2][union.byte2]++;
                count[3][((uint)inArray[current] >> 24) ^ 128]++;
            }
            return count;
        }
    

    它在单核上以接近 100 MegaInt32s/sec 的速度运行 - 比 Array.Sort() 快约 7 倍,在单核上比 Linq.OrderBy() 快 25 倍,比 Linq.AsParallel().OrderBy( ) 在 6 个内核上。

    此实现取自 nuget.org 上的 HPCsharp nuget 包,该包也有用于排序 uint[]、long[] 和 ulong[] 数组的版本,以及添加 float[] 的 MSD Radix Sort和 double[] 数组并且是就地的。

    【讨论】:

    • 最近添加到 HPCsharp nuget 包(开源和免费)中的另一种算法是并行混合合并排序,其中 Array.Sort() 作为递归的叶节点。该算法在 32 核 AMD 处理器上运行速度比 Array.Sort() 快 40 倍以上,并且仍然是通用排序算法
    【解决方案4】:

    看看剪切排序和奇事件转置排序:http://www.cs.rit.edu/~atk/Java/Sorting/sorting.htmlhttp://home.westman.wave.ca/~rhenry/sort/

    这里有一个剪切排序的 C# 实现:http://www.codeproject.com/KB/recipes/cssorters.aspx

    示例使用 Java 编写,但与 C# 非常接近。它们是并行排序,因为它们在多个内核上运行得更快,但仍然应该非常快。

    【讨论】:

      【解决方案5】:

      在对具有重复项的数组进行排序时,第一个(可能是第二个)快速排序算法会中断。我用this 一个,效果很好。

      【讨论】:

        【解决方案6】:

        这对我来说更快更简单。

        unsafe static void Sort(int* a, int length)
        {
            int negLength = length - 1;
            for (int i = 0; i < negLength; ++i)
            for (int n = i + 1; n < length; ++n)
            {
                int value = a[i];
                int next = a[n];
                if (value > next)
                {
                    a[i] = next;
                    a[n] = value;
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2023-03-30
          • 1970-01-01
          • 2023-01-31
          • 2018-09-20
          • 2022-06-21
          • 2010-11-28
          • 2019-05-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多