是否有更快/更清晰的方法将结构复制到C#中的数组?

时间:2011-09-17 10:17:17

标签: c# arrays memory

我有一个float4x4结构,它只包含16个浮点数:

struct float4x4
{
        public float M11; public float M12; public float M13; public float M14;
        public float M21; public float M22; public float M23; public float M24;
        public float M31; public float M32; public float M33; public float M34;
        public float M41; public float M42; public float M43; public float M44;
}

我想将这些结构的数组复制到一大堆浮点数中。据我所知,这是一块内存的1:1副本

我所知道的是相当丑陋,而不是那么快:

        int n = 0;
        for (int i = 0; i < Length; i++)
        {
            array[n++] = value[i].M11;
            array[n++] = value[i].M12;
            array[n++] = value[i].M13;
            array[n++] = value[i].M14;

            array[n++] = value[i].M21;
            array[n++] = value[i].M22;
            array[n++] = value[i].M23;
            array[n++] = value[i].M24;

            array[n++] = value[i].M31;
            array[n++] = value[i].M32;
            array[n++] = value[i].M33;
            array[n++] = value[i].M34;

            array[n++] = value[i].M41;
            array[n++] = value[i].M42;
            array[n++] = value[i].M43;
            array[n++] = value[i].M44;
        }

如果我使用的是较低级别的语言,我只会使用memcpy,我可以在C#中使用什么作为等效语言?

5 个答案:

答案 0 :(得分:4)

您无法使用内存副本,因为您无法盲目地假设成员如何存储在结构中。 JIT编译器可以决定在它们之间存储几个字节的填充,如果这样可以使它更快。

无论如何,你的结构对于建议的结构大小来说太大了,所以你应该把它变成一个类。此外,结构不应该是可变的,这也适用于一个类。

如果在内部将属性存储在数组中,则可以使用它来复制值:

class float4x4 {

  public float[] Values { get; private set; } 

  public float4x4() {
    Values = new float[16];
  }

  public float M11 { get { return Values[0]; } set { Values[0] = value; } }
  public float M12 { get { return Values[0]; } set { Values[0] = value; } }
  ...
  public float M43 { get { return Values[14]; } set { Values[14] = value; } }
  public float M44 { get { return Values[15]; } set { Values[15] = value; } }

}

现在您可以从对象获取Values数组并使用Array.CopyTo方法复制到数组:

int n = 0;
foreach (float4x4 v in values) {
  v.Values.CopyTo(array, n);
  n += 16;
}

答案 1 :(得分:2)

这可能同样难看,但速度非常快。

using System.Runtime.InteropServices;

namespace ConsoleApplication23 {
  public class Program {
    public static void Main() {
      var values=new[] {
        new float4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16),
        new float4x4(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16)
      };
      var result=Transform(values);
    }

    public static unsafe float[] Transform(float4x4[] values) {
      var array=new float[values.Length*16];
      fixed(float* arrayStart=array) {
        var destp=arrayStart;
        fixed(float4x4* valuesStart=values) {
          int count=values.Length;
          for(var valuesp=valuesStart; count>0; ++valuesp, --count) {
            var sourcep=valuesp->data;
            for(var i=0; i<16/4; ++i) {
              *destp++=*sourcep++;
              *destp++=*sourcep++;
              *destp++=*sourcep++;
              *destp++=*sourcep++;
            }
          }
        }
        return array;
      }
    }

    [StructLayout(LayoutKind.Explicit)]
    public unsafe struct float4x4 {
      [FieldOffset(0)] public float M11;
      [FieldOffset(4)] public float M12;
      [FieldOffset(8)] public float M13;
      [FieldOffset(12)] public float M14;
      [FieldOffset(16)] public float M21;
      [FieldOffset(20)] public float M22;
      [FieldOffset(24)] public float M23;
      [FieldOffset(28)] public float M24;
      [FieldOffset(32)] public float M31;
      [FieldOffset(36)] public float M32;
      [FieldOffset(40)] public float M33;
      [FieldOffset(44)] public float M34;
      [FieldOffset(48)] public float M41;
      [FieldOffset(52)] public float M42;
      [FieldOffset(56)] public float M43;
      [FieldOffset(60)] public float M44;

      //notice the use of "fixed" keyword to make the array inline
      //and the use of the FieldOffset attribute to overlay that inline array on top of the other fields
      [FieldOffset(0)] public fixed float data[16];

      public float4x4(float m11, float m12, float m13, float m14,
        float m21, float m22, float m23, float m24,
        float m31, float m32, float m33, float m34,
        float m41, float m42, float m43, float m44) {
        M11=m11; M12=m12; M13=m13; M14=m14;
        M21=m21; M22=m22; M23=m23; M24=m24;
        M31=m31; M32=m32; M33=m33; M34=m34;
        M41=m41; M42=m42; M43=m43; M44=m44;
      }
    }
  }
}

答案 2 :(得分:1)

好的,这是我的测试工具。我的项目属性是Release Build,“优化代码”以及“允许不安全代码”。

令人惊讶的是(无论如何)在IDE内部和外部的性能都非常不同。从IDE运行时会有明显的差异(并且x64差异很大)。在IDE外部运行时,它是一个清洗。

所以这有点奇怪,我无法解释IDE + x64的结果。也许这对某些人来说很有意思,但是因为它不再声称能够回答海报的原始问题,或许这应该转移到其他一些话题上?

在IDE内部,平台设置为x86

pass 1: old 00:00:09.7505625 new 00:00:08.6897013 percent 0.1088

在IDE内部,平台设置为x64

pass 1: old 00:00:14.7584514 new 00:00:08.8835715 percent 0.398068858362741

从命令行运行,平台设置为x86

pass 1: old 00:00:07.6576469 new 00:00:07.2818252 percent 0.0490779615341104

从命令行运行,平台设置为x64

pass 1: old 00:00:07.2501032 new 00:00:07.3077479 percent -0.00795087992678504

这是代码:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication23 {
  public class Program {
    public static void Main() {
      const int repeatCount=20;
      const int arraySize=5000000;

      var values=MakeValues(arraySize);

      for(var pass=0; pass<2; ++pass) {
        Console.WriteLine("Starting old");
        var startOld=DateTime.Now;
        for(var i=0; i<repeatCount; ++i) {
          var result=TransformOld(values);
        }
        var elapsedOld=DateTime.Now-startOld;

        Console.WriteLine("Starting new");
        var startNew=DateTime.Now;
        for(var i=0; i<repeatCount; ++i) {
          var result=TransformNew(values);
        }
        var elapsedNew=DateTime.Now-startNew;

        var difference=elapsedOld-elapsedNew;
        var percentage=(double)difference.TotalMilliseconds/elapsedOld.TotalMilliseconds;

        Console.WriteLine("pass {0}: old {1} new {2} percent {3}", pass, elapsedOld, elapsedNew, percentage);
      }
      Console.Write("Press enter: ");
      Console.ReadLine();
    }

    private static float4x4[] MakeValues(int count) {
      var result=new float4x4[count];
      for(var i=0; i<count; ++i) {
        result[i]=new float4x4(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
      }
      return result;
    }

    public static float[] TransformOld(float4x4[] value) {
      var array=new float[value.Length*16];
      int n = 0;
      for(int i = 0; i < value.Length; i++) {
        array[n++] = value[i].M11;
        array[n++] = value[i].M12;
        array[n++] = value[i].M13;
        array[n++] = value[i].M14;

        array[n++] = value[i].M21;
        array[n++] = value[i].M22;
        array[n++] = value[i].M23;
        array[n++] = value[i].M24;

        array[n++] = value[i].M31;
        array[n++] = value[i].M32;
        array[n++] = value[i].M33;
        array[n++] = value[i].M34;

        array[n++] = value[i].M41;
        array[n++] = value[i].M42;
        array[n++] = value[i].M43;
        array[n++] = value[i].M44;
      }
      return array;
    }

    public static unsafe float[] TransformNew(float4x4[] values) {
      var array=new float[values.Length*16];
      fixed(float* arrayStart=array) {
        var destp=arrayStart;
        fixed(float4x4* valuesStart=values) {
          int count=values.Length;
          for(var valuesp=valuesStart; count>0; ++valuesp, --count) {
            var sourcep=valuesp->data;
            for(var i=0; i<16/4; ++i) {
              *destp++=*sourcep++;
              *destp++=*sourcep++;
              *destp++=*sourcep++;
              *destp++=*sourcep++;
            }
          }
        }
        return array;
      }
    }

    [StructLayout(LayoutKind.Explicit)]
    public unsafe struct float4x4 {
      [FieldOffset(0)] public float M11;
      [FieldOffset(4)] public float M12;
      [FieldOffset(8)] public float M13;
      [FieldOffset(12)] public float M14;
      [FieldOffset(16)] public float M21;
      [FieldOffset(20)] public float M22;
      [FieldOffset(24)] public float M23;
      [FieldOffset(28)] public float M24;
      [FieldOffset(32)] public float M31;
      [FieldOffset(36)] public float M32;
      [FieldOffset(40)] public float M33;
      [FieldOffset(44)] public float M34;
      [FieldOffset(48)] public float M41;
      [FieldOffset(52)] public float M42;
      [FieldOffset(56)] public float M43;
      [FieldOffset(60)] public float M44;

      //notice the use of "fixed" keyword to make the array inline
      //and the use of the FieldOffset attribute to overlay that inline array on top of the other fields
      [FieldOffset(0)] public fixed float data[16];

      public float4x4(float m11, float m12, float m13, float m14,
        float m21, float m22, float m23, float m24,
        float m31, float m32, float m33, float m34,
        float m41, float m42, float m43, float m44) {
        M11=m11; M12=m12; M13=m13; M14=m14;
        M21=m21; M22=m22; M23=m23; M24=m24;
        M31=m31; M32=m32; M33=m33; M34=m34;
        M41=m41; M42=m42; M43=m43; M44=m44;
      }
    }
  }
}

答案 3 :(得分:-1)

也许您可以使用浮点数组别名结构数组,并且绝对不会复制。检查this SO answer是否有起点

答案 4 :(得分:-1)

它不一定是1比1的副本。 CLR可以自由地以任何方式布局结构中的字段。它可能会对它们重新排序,重新对齐它们。

如果添加[StructLayout(LayoutKind.Sequential)],可能会有直接副本,但我仍然会使用类似于原始代码的内容。