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

C# .NET SIMD底层图像处理性能优化及技术路线咨询

24位BGR转8位灰度图的性能优化问题

测试背景与困惑

我针对3840×2160的Format24bppRgb格式图像(约800万像素)做了性能测试:

  • 空循环1036800次耗时3-5ms;
  • 仅执行Avx.LoadVector256操作耗时4-8ms;
  • 添加Vector256.Shuffle后耗时升至12ms左右;
  • 再加入Avx2.UnpackLow、Avx2.UnpackHigh等操作后耗时达20ms(仅完成24位转8位灰度图);
  • 未启用Parallel.For时耗时约90ms。

而OpenCVSharp完成相同格式转换仅需3-6ms,我的空循环耗时居然和OpenCVSharp完整转换耗时差不多,这让我很头疼。我想知道有没有优化方法能把性能提升到3-6ms,或者我的实现思路是不是完全错了?同时希望得到底层图像处理的相关指导,了解当前C# .NET实现还有没有优化空间,或者转向其他语言会不会更合适。

测试代码

const ushort mulBlue = (ushort)(0.114 * 0x10000);
const ushort mulGreen = (ushort)(0.587 * 0x10000);
const ushort mulRed = (ushort)(0.299 * 0x10000);

public static void BGR24Bit2Gray(byte* src, byte* dst, int count)
{
    var CoeB = Vector128.Create(mulBlue);
    var CoeG = Vector128.Create(mulGreen);
    var CoeR = Vector128.Create(mulRed);

    byte[] Seqdata1 = new byte[32] { 0, 3, 6, 9, 12, 15, 18, 21, 1, 4, 7, 10, 13, 16, 19, 22, 2, 5, 8, 11, 14, 17, 20, 23, 0, 0, 0, 0, 0, 0, 0, 0 };
    byte[] Seqdata2 = new byte[32] { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 2, 5, 8, 11, 14, 17, 20, 23, 0, 0, 0, 0, 0, 0, 0, 0 };

    int allPixels = count * 3;
    byte* srcEnd = src + allPixels;
    int stride = 24;
    int loopCount = (int)((srcEnd - src) / stride);

    ParallelOptions pOptions = new ParallelOptions();
    pOptions.MaxDegreeOfParallelism = Environment.ProcessorCount;

    fixed (byte* pa1 = Seqdata1)
    fixed (byte* pa2 = Seqdata2)
    {
        byte* par1 = pa1;
        byte* par2 = pa2;
        byte* tdst = dst;
        Vector256<byte> seq1 = Avx.LoadVector256(par1);
        Vector128<byte> seq2 = Sse2.LoadVector128(par2);

        Stopwatch sw = Stopwatch.StartNew();

        //for (int i = 0; i < 1036800; i++)
        //for (int i = 0; i < loopCount; i++)
        Parallel.For(0, loopCount - 1, pOptions, i =>
        {
            var _1st24bytes = Avx.LoadVector256(src + i * stride);
            var rslt256 = Vector256.Shuffle(_1st24bytes, seq1);
            Vector256<ushort> v1l = Avx2.UnpackLow(rslt256, Vector256<byte>.Zero).AsUInt16();
            Vector256<ushort> v1h = Avx2.UnpackHigh(rslt256, Vector256<byte>.Zero).AsUInt16();

            var Bdata = v1l.GetLower();
            var Gdata = v1h.GetLower();
            var Rdata = v1l.GetUpper();

            var B = Sse2.MultiplyHigh(Bdata, CoeB);
            var G = Sse2.MultiplyHigh(Gdata, CoeG);
            var R = Sse2.MultiplyHigh(Rdata, CoeR);

            var Gray = Sse2.Add(B, Sse2.Add(G, R));
            var GrayByt = Vector128.AsByte(Gray);

            var rslt128 = Ssse3.Shuffle(GrayByt, seq2);
            var frslt = rslt128.GetLower();

            Sse2.Store(dst + i * 8, rslt128);
        }
        );
        sw.Stop();
        var tp = sw.ElapsedMilliseconds;
        tslblStatus.Text = tp.ToString() + "ms";
    }
}

//Test Method:
private unsafe void ConvertionTest()
{
    Bitmap orgpic = pcbxOrg.Image as Bitmap;
    //Do nothing when the original image is null
    if(orgpic==null) return;
    //Only for 24 bit image processing
    if(orgpic.PixelFormat != PixelFormat.Format24bppRgb) return;
    
    int width = orgpic.Width;
    int height = orgpic.Height;
    int count = width * height; //Pixel count
    
    //Define the gray image
    Bitmap despic = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
    
    lock (lcobj)
    {
        //Lock the image data
        BitmapData orgData = orgpic.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, orgpic.PixelFormat);
        BitmapData desData = despic.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
        //Get the image pointer
        byte* orgPtr = (byte*)orgData.Scan0.ToPointer();
        byte* desPtr = (byte*)desData.Scan0.ToPointer();
        
        //Start convertion
        Stopwatch sw = Stopwatch.StartNew();
        BGR24Bit2Gray(orgPtr, desPtr, count);
        sw.Stop();
        
        //Unlock the image data
        orgpic.UnlockBits(orgData);
        despic.UnlockBits(desData);

        var tp = sw.ElapsedMilliseconds;
        tslblStatus.Text = tp.ToString() + "ms";
    }
    //ColorPalette for the 8 bit image
    ColorPalette palette = despic.Palette;
    for (int i = 0; i != palette.Entries.Length; i++)
    {
        palette.Entries[i] = Color.FromArgb(i, i, i);
    }
    despic.Palette = palette;
    pcbxDes.Image = despic;
}

优化方向建议

  • 重构向量化逻辑:当前Shuffle和Unpack操作过于繁琐,可直接用Avx2的Permute或Shuffle提取B/G/R通道到独立向量,减少数据拆分合并开销;尽量用256位向量完成计算,避免频繁切换128位向量。
  • 优化并行策略:按行拆分任务(而非8像素块),减少线程调度开销;确保每个任务处理连续内存块,提升缓存命中率。
  • 内存对齐优化:使用Avx.LoadAlignedVector256加载对齐内存,LockBits时指定对齐方式或手动调整指针,非对齐访问会大幅降低性能。
  • 简化计算指令:用8位整数系数(29、150、76,总和255)替代定点乘法,利用Avx2的MultiplyAddAdjacent指令一步完成加权求和,避免MultiplyHigh的额外开销。
  • 常量复用:将CoeB/CoeG/CoeR、seq1/seq2等常量设为静态只读变量,避免每次调用方法重复创建。
  • 底层实现对比:OpenCVSharp依赖C优化代码或IPP硬件加速,若.NET优化到极限仍不达标,可考虑用C/CLI编写核心函数,或使用.NET Native AOT编译提升性能。

内容的提问来源于stack exchange,提问作者Mweidy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:36:03