【问题标题】:Copy array to struct array as fast as possible in C#在 C# 中尽可能快地将数组复制到结构数组
【发布时间】:2023-03-30 15:04:02
【问题描述】:

我正在使用 Unity 4.5,将图像抓取为字节数组(每个字节代表一个通道,每个像素(rgba)占用 4 个字节,并将它们显示在纹理上,将数组转换为 Color32 数组,使用以下循环:

   img = new Color32[byteArray.Length / nChannels]; //nChannels being 4
   for (int i=0; i< img.Length; i++) {
        img[i].r = byteArray[i*nChannels];
        img[i].g = byteArray[i*nChannels+1];
        img[i].b = byteArray[i*nChannels+2];
        img[i].a = byteArray[i*nChannels+3];
    }

然后,使用以下方法将其应用于纹理:

tex.SetPixels32(img);

但是,这会显着减慢应用程序的速度(此循环在每一帧上执行),我想知道是否有任何其他方法可以加快复制过程。我发现有些人(Fast copy of Color32[] array to byte[] array)使用Marshal.Copy 函数来执行相反的过程(Color32 到字节数组),但我无法将字节数组复制到Color32 数组。有人知道更快的方法吗?

提前谢谢你!

【问题讨论】:

  • 那么你可以让你的 i++, i+=nChannels....

标签: c# arrays unity3d marshalling


【解决方案1】:

是的,Marshal.Copy 是要走的路。我已经回答了类似的问题here

这是一个从 struct[] 复制到 byte[] 的通用方法,反之亦然

private static byte[] ToByteArray<T>(T[] source) where T : struct
{
    GCHandle handle = GCHandle.Alloc(source, GCHandleType.Pinned);
    try
    {
        IntPtr pointer = handle.AddrOfPinnedObject();
        byte[] destination = new byte[source.Length * Marshal.SizeOf(typeof(T))];
        Marshal.Copy(pointer, destination, 0, destination.Length);
        return destination;
    }
    finally
    {
        if (handle.IsAllocated)
            handle.Free();
    }
}

private static T[] FromByteArray<T>(byte[] source) where T : struct
{
    T[] destination = new T[source.Length / Marshal.SizeOf(typeof(T))];
    GCHandle handle = GCHandle.Alloc(destination, GCHandleType.Pinned);
    try
    {
        IntPtr pointer = handle.AddrOfPinnedObject();
        Marshal.Copy(source, 0, pointer, source.Length);
        return destination;
    }
    finally
    {
        if (handle.IsAllocated)
            handle.Free();
    }
}

将其用作:

[StructLayout(LayoutKind.Sequential)]
public struct Demo
{
    public double X;
    public double Y;
}

private static void Main()
{
    Demo[] array = new Demo[2];
    array[0] = new Demo { X = 5.6, Y = 6.6 };
    array[1] = new Demo { X = 7.6, Y = 8.6 };

    byte[] bytes = ToByteArray(array);
    Demo[] array2 = FromByteArray<Demo>(bytes);
}

【讨论】:

    【解决方案2】:

    此代码需要 unsafe 开关,但应该很快。我认为您应该对这些答案进行基准测试...

    var bytes = new byte[] { 1, 2, 3, 4 };
    
    var colors = MemCopyUtils.ByteArrayToColor32Array(bytes);
    

    public class MemCopyUtils
    {
        unsafe delegate void MemCpyDelegate(byte* dst, byte* src, int len);
        static MemCpyDelegate MemCpy;
    
        static MemCopyUtils()
        {
            InitMemCpy();
        }
    
        static void InitMemCpy()
        {
            var mi = typeof(Buffer).GetMethod(
                name: "Memcpy",
                bindingAttr: BindingFlags.NonPublic | BindingFlags.Static,
                binder:  null,
                types: new Type[] { typeof(byte*), typeof(byte*), typeof(int) },
                modifiers: null);
            MemCpy = (MemCpyDelegate)Delegate.CreateDelegate(typeof(MemCpyDelegate), mi);
        }
    
        public unsafe static Color32[] ByteArrayToColor32Array(byte[] bytes)
        {
            Color32[] colors = new Color32[bytes.Length / sizeof(Color32)];
    
            fixed (void* tempC = &colors[0])
            fixed (byte* pBytes = bytes)
            {
                byte* pColors = (byte*)tempC;
                MemCpy(pColors, pBytes, bytes.Length);
            }
            return colors;
        }
    }
    

    【讨论】:

    • 我真的很喜欢这个 - 在这种情况下调用 Memcpy 是否有任何开销?
    • @ChrisMantle 我不认为,因为委托只创建一次
    • 对 Unity3D 开发者的一点警告(标记在 OPs 问题中),Unity3D 不支持 IIRC unsafe,或者至少不支持所有平台。
    【解决方案3】:

    使用Parallel.For 可能会显着提高性能。

    img = new Color32[byteArray.Length / nChannels]; //nChannels being 4
    Parallel.For(0, img.Length, i =>
    {
        img[i].r = byteArray[i*nChannels];
        img[i].g = byteArray[i*nChannels+1];
        img[i].b = byteArray[i*nChannels+2];
        img[i].a = byteArray[i*nChannels+3];
    });
    

    Example on MSDN

    【讨论】:

    • 遗憾的是,Mono 框架不包含不包含“System.Threading.Tasks”程序集。然而,并行化循环似乎是个好主意。我会寻找解决方法!
    【解决方案4】:

    我没有对其进行分析,但使用fixed 来确保您的内存不会被移动并删除对数组访问的边界检查可能会带来一些好处:

    img = new Color32[byteArray.Length / nChannels]; //nChannels being 4
    fixed (byte* ba = byteArray)
    {
        fixed (Color32* c = img)
        {
            byte* byteArrayPtr = ba;
            Color32* colorPtr = c;
            for (int i = 0; i < img.Length; i++)
            {
                (*colorPtr).r = *byteArrayPtr++;
                (*colorPtr).g = *byteArrayPtr++;
                (*colorPtr).b = *byteArrayPtr++;
                (*colorPtr).a = *byteArrayPtr++;
                colorPtr++;
            }
        }
    }
    

    它可能不会在 64 位系统上提供更多好处 - 我相信边界检查更加优化。另外,这是一个unsafe 操作,所以要小心。

    【讨论】:

      【解决方案5】:
      public Color32[] GetColorArray(byte[] myByte)
      {
          if (myByte.Length % 1 != 0) 
             throw new Exception("Must have an even length");
      
          var colors = new Color32[myByte.Length / nChannels];
      
          for (var i = 0; i < myByte.Length; i += nChannels)
          {
             colors[i / nChannels] = new Color32(
                 (byte)(myByte[i] & 0xF8),
                 (byte)(((myByte[i] & 7) << 5) | ((myByte[i + 1] & 0xE0) >> 3)),
                 (byte)((myByte[i + 1] & 0x1F) << 3),
                 (byte)1);
          }
      
          return colors;
      }
      

      工作速度比 i++ 快大约 30-50 倍。 “额外”只是造型。这段代码在 for 循环中的一个“行”中执行您在 4 行中声明的内容,而且速度要快得多。干杯:)

      引用 + 引用代码:Here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-10
        • 2015-01-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多