You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化C# Unsafe快速排序代码,缩小与C++的性能差距

C# Unsafe快速排序性能优化方案(对比C++差距过大)

当前你的C# unsafe快速排序处理1000万条int数据耗时550ms,逻辑一致的C++版本仅需230ms,差距远超预期,以下是针对性的优化建议:

现有代码

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace EditorPlayer
{
    internal static class Program
    {
        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        public unsafe static void UnsafeQuickSort(int[] data)
        {
            fixed(int* pData = data)
            {
                UnsafeQuickSortRecursive(pData, 0, data.Length - 1);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
        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;
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        internal static void Main(string[] args)
        {
            int[] array = new int[10000000];
            Random rnd = new Random();
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = rnd.Next(100000);
            }

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            UnsafeQuickSort(array);
            stopwatch.Stop();

            Console.WriteLine($"Time took: {stopwatch.Elapsed.TotalMilliseconds}");
            Console.ReadLine();
        }
    }
}

优化建议

1. 优化基准值选择,避免最坏情况

当前代码固定选left位置的元素作为基准,在数组已排序/接近排序的场景下,会导致递归深度达到O(n),性能暴跌。改用三数取中(取left、mid、right的中位数)作为基准,能大幅降低最坏情况的概率,同时提升平均性能:

private unsafe static void UnsafeQuickSortRecursive(int* data, int left, int right)
{
    // 小数据量改用插入排序,减少递归开销
    if (right - left < 16)
    {
        UnsafeInsertionSort(data, left, right);
        return;
    }

    // 三数取中选基准,调整位置简化边界判断
    int mid = left + (right - left) / 2;
    if (data[mid] < data[left]) { int tmp = data[left]; data[left] = data[mid]; data[mid] = tmp; }
    if (data[right] < data[left]) { int tmp = data[left]; data[left] = data[right]; data[right] = tmp; }
    if (data[right] < data[mid]) { int tmp = data[mid]; data[mid] = data[right]; data[right] = tmp; }
    
    int pivot = data[mid];
    int tmpVal = data[mid]; data[mid] = data[right - 1]; data[right - 1] = tmpVal;
    
    int i = left;
    int j = right - 1;

    while (true)
    {
        while (data[++i] < pivot);
        while (data[--j] > pivot);
        if (i < j)
        {
            tmpVal = data[i]; data[i] = data[j]; data[j] = tmpVal;
        }
        else
        {
            // 将基准移回正确分区位置
            tmpVal = data[i]; data[i] = data[right - 1]; data[right - 1] = tmpVal;
            // 递归处理左半部分,右半部分用循环消除尾递归
            if (left < i - 1) UnsafeQuickSortRecursive(data, left, i - 1);
            left = i + 1;
            if (left >= right) return;
        }
    }
}

// 小数据量插入排序实现
private unsafe static void UnsafeInsertionSort(int* data, int left, int right)
{
    for (int i = left + 1; i <= right; i++)
    {
        int val = data[i];
        int j = i - 1;
        while (j >= left && data[j] > val)
        {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = val;
    }
}

2. 消除尾递归,减少栈开销

C#的递归调用栈开销比C++更明显,将第二个递归调用改为循环处理(尾递归消除),能减少栈帧创建的开销,提升性能。上面的代码已经实现了这一点:分区完成后仅递归处理左半部分,右半部分通过更新left变量在当前循环中继续处理。

3. 减少内存重复访问

原代码在while(true)循环内每次都重新读取data[left]作为基准,将基准值的读取和调整移到循环外,能减少不必要的内存访问次数,提升缓存命中率。

4. 编译与运行环境优化

  • 确保项目以Release模式编译,开启所有优化选项(默认Release模式已开启)。
  • 目标平台选择x64,避免32位环境的内存限制和性能损耗。
  • 启用ReadyToRun编译:在项目文件中添加以下配置,提前将IL编译为机器码,减少JIT编译开销:
    <PropertyGroup>
      <PublishReadyToRun>true</PublishReadyToRun>
      <PublishTrimmed>true</PublishTrimmed>
    </PropertyGroup>
    
  • 启用服务器GC:在runtimeconfig.json中添加配置,提升大内存场景的GC性能:
    {
      "runtimeOptions": {
        "configProperties": {
          "System.GC.Server": true
        }
      }
    }
    

5. 优化随机数组生成

原代码使用Random.Next生成随机数,Random不是线程安全的,循环中调用会产生锁开销。改用RandomNumberGenerator生成更高效的随机数组,避免影响排序计时:

int[] array = new int[10000000];
byte[] bytes = new byte[array.Length * 4];
using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
{
    rng.GetBytes(bytes);
}
Buffer.BlockCopy(bytes, 0, array, 0, bytes.Length);
// 将数值范围限制到0-99999
for (int i = 0; i < array.Length; i++)
{
    array[i] = Math.Abs(array[i]) % 100000;
}

6. 尝试用Span替代Unsafe指针

.NET的Span<T>提供了接近unsafe指针的性能,同时更安全,编译器对Span的优化也非常充分。可以尝试将代码改为Span版本,性能可能接近甚至超过unsafe实现:

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void SpanQuickSort(Span<int> data)
{
    if (data.Length <= 16)
    {
        InsertionSort(data);
        return;
    }

    // 三数取中选基准
    int mid = data.Length / 2;
    if (data[mid] < data[0]) Swap(ref data[0], ref data[mid]);
    if (data[^1] < data[0]) Swap(ref data[0], ref data[^1]);
    if (data[^1] < data[mid]) Swap(ref data[mid], ref data[^1]);
    
    int pivot = data[mid];
    Swap(ref data[mid], ref data[^2]);
    
    int i = 0;
    int j = data.Length - 2;
    while (true)
    {
        while (data[++i] < pivot);
        while (data[--j] > pivot);
        if (i < j)
        {
            Swap(ref data[i], ref data[j]);
        }
        else
        {
            Swap(ref data[i], ref data[^2]);
            SpanQuickSort(data[..i]);
            data = data[(i + 1)..];
            if (data.Length <= 1) return;
        }
    }
}

private static void InsertionSort(Span<int> data)
{
    for (int i = 1; i < data.Length; i++)
    {
        int val = data[i];
        int j = i - 1;
        while (j >= 0 && data[j] > val)
        {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = val;
    }
}

private static void Swap(ref int a, ref int b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

测试建议

优化后建议多次运行测试取平均值,避免单次运行的偶然性。同时可以对比.NET内置的Array.Sort方法性能,作为参考基准。

内容的提问来源于stack exchange,提问作者Barış Üçkardeş

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 06:37:02