简体   繁体   English

有没有更快/更干净的方法将结构复制到C#中的数组?

[英]Is there a faster/cleaner way to copy structs to an array in C#?

I have a float4x4 struct which simply containts 16 floats: 我有一个仅包含16个浮点数的float4x4结构:

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;
}

I want to copy an array of these structs into a big array of floats. 我想将这些结构的数组复制到大量的浮点数中。 This is as far as I know a 1:1 copy of a chunk of memory 据我所知,这是一块内存的1:1副本

What I do know is rather ugly, and not that fast: 我所知道的是很丑陋的,而且还不那么快:

        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;
        }

If I was using a lower level language, I would simply use memcpy, what can I use as an equivilant in C#? 如果我使用的是较低级的语言,则只需使用memcpy,在C#中可以用作等效项吗?

You can't use a memory copy, as you can't blindly assume anything about how the members are stored inside the structure. 您不能使用内存副本,因为您不能盲目地假设有关成员如何在结构内部存储的任何信息。 The JIT compiler could decide to store them with a few bytes of padding between them, if that would make it faster. JIT编译器可以决定使用它们之间的一些填充字节来存储它们,如果这样做可以更快的话。

Your structure is way too large for the recommended size of a structure anyway, so you should make it a class. 无论如何,对于建议的结构大小而言,您的结构太大了,因此您应该使其成为一个类。 Also, structures should not be mutable, which also talks for a class. 同样,结构不应该是可变的,这也代表了一个类。

If you store the properties in an array internally, you can use that for copying the values: 如果将属性存储在内部数组中,则可以使用该属性复制值:

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; } }

}

Now you can get the Values array from the object and copy to the array using the Array.CopyTo method: 现在,您可以从对象获取Values数组,并使用Array.CopyTo方法复制到该数组:

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

This is perhaps equally ugly, but is very fast. 这也许同样难看,但是非常快。

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;
      }
    }
  }
}

OK this is my test harness. 好的,这是我的测试工具。 My project properties are Release Build, "optimize code" and also "Allow unsafe code" checked. 我的项目属性是Release Build,“优化代码”和“允许不安全代码”选中。

Surprisingly (to me anyway) the performance is very different inside and outside the IDE. 出乎意料的是(无论如何对我来说)IDE内部和外部的性能都大不相同。 When run from the IDE there are noticeable differences (and the x64 difference is huge). 从IDE运行时,会有明显的差异(x64差异很大)。 When run outside the IDE, it's a wash. 在IDE之外运行时,需要洗钱。

So this is kind of weird, and I can't explain the results for IDE+x64. 因此,这有点奇怪,我无法解释IDE + x64的结果。 Maybe this is interesting to some people, but because it no longer purports to provide an answer to the poster's original question, maybe this should be moved to some other topic? 也许这对某些人来说很有趣,但是由于它不再旨在为海报的原始问题提供答案,也许应该将其移至其他主题?

Inside IDE, platform set to x86 在IDE内部,平台设置为x86

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

Inside IDE, platform set to x64 在IDE内部,平台设置为x64

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

Running from command line, platform set to x86 从命令行运行,平台设置为x86

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

Running from command line, platform set to x64 从命令行运行,平台设置为x64

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

And this is the code: 这是代码:

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;
      }
    }
  }
}

Maybe you could alias the array of structs with an array of floats and do absolutely no copying. 也许您可以用浮点数数组为结构体数组取别名,并且绝对不进行复制。 Check this SO answer for a starting point 检查此SO答案作为起点

It's not necessarily a 1 to 1 copy. 不一定是一对一的副本。 The CLR is free to layout the fields in a struct in whichever way it likes. CLR可以随意使用其喜欢的方式在结构中布局字段。 It might reorder them, realign them. 它可能会重新排列它们,重新排列它们。

If you add a [StructLayout(LayoutKind.Sequential)] a direct copy might be possible, but I'd still go with something similar to your original code. 如果添加[StructLayout(LayoutKind.Sequential)]可以直接复制,但我仍然会选择与原始代码类似的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM