2011-09-17 31 views
5

Tôi có một struct float4x4 mà chỉ đơn giản containts 16 nổi:Có cách nào nhanh hơn/sạch hơn để sao chép cấu trúc vào một mảng trong C#?

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

Tôi muốn sao chép một mảng các cấu trúc thành một mảng lớn của phao nổi. Đây là như xa như tôi biết 1: 1 bản sao của một đoạn bộ nhớ

Những gì tôi biết là khá xấu xí, và không phải là nhanh:

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

Nếu tôi đã sử dụng một ngôn ngữ cấp thấp hơn, Tôi chỉ đơn giản là sử dụng memcpy, những gì tôi có thể sử dụng như một equivilant trong C#?

+0

Vâng, bạn có thể sử dụng mã không an toàn. Nếu bạn nhận được một con trỏ tới 'value [i]', bạn có thể sử dụng 'Marshal.Copy' để sao chép nó vào' mảng'. Cho dù đó là sạch hơn, tôi không chắc chắn .. – harold

+0

Là một cấu trúc, đó là khá quá trọng lượng ... cấu trúc có kích thước hướng dẫn, FWIW –

+0

nếu bạn sử dụng StructLayout với Explicit và xác định kích thước và bố cục thì bạn có thể sử dụng một lớp C++/CLI đơn giản để diễn giải lại một con trỏ được ghim vào mục nhập đầu tiên trong mảng và memcpy. Điều này có tất cả các loại báo trước nhưng rất nhanh. Tuy nhiên, tôi sẽ lập luận rằng, nếu bạn không có khả năng viết một điều như vậy cho mình, bạn có lẽ không nên làm điều đó cho đến khi bạn đã học được như thế nào. – ShuggyCoUk

Trả lời

2

Điều này có lẽ là xấu xí, nhưng rất nhanh.

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

Rất nhanh được đo tương đối so với cái gì? –

+0

So với mã gốc của poster –

+0

Ok. O_o. Tôi hoài nghi rằng nó sẽ có hiệu ứng đáng chú ý. –

-1

Có thể bạn có thể đặt bí danh mảng cấu trúc với một mảng phao và hoàn toàn không sao chép. Kiểm tra this SO answer để biết điểm xuất phát

+2

Việc diễn giải lại tài liệu tham khảo được quản lý là ** EVIL ** – CodesInChaos

+0

Tôi vừa đề xuất rằng FieldOffset có thể lưu bản sao. Vì đó là suy nghĩ xấu xa, tôi chắc chắn sẽ đề cập đến nó trong lời thú nhận tiếp theo của mình và yêu cầu sự tha thứ .. – renick

-1

Không nhất thiết phải là bản sao từ 1 đến 1. CLR là miễn phí để bố trí các lĩnh vực trong một cấu trúc trong bất cứ cách nào nó thích. Nó có thể sắp xếp lại chúng, sắp xếp lại chúng.

Nếu bạn thêm [StructLayout(LayoutKind.Sequential)] một bản sao trực tiếp có thể là có thể, nhưng tôi vẫn sẽ đi với một cái gì đó tương tự như mã ban đầu của bạn.

4

Bạn không thể sử dụng bản sao bộ nhớ, vì bạn không thể giả thiết bất cứ điều gì về cách các thành viên được lưu trữ bên trong cấu trúc. Trình biên dịch JIT có thể quyết định lưu trữ chúng với một vài byte đệm giữa chúng, nếu điều đó sẽ làm cho nó nhanh hơn.

Cấu trúc của bạn quá lớn so với kích thước được đề xuất của cấu trúc, vì vậy bạn nên biến nó thành một lớp. Ngoài ra, các cấu trúc không nên biến đổi, mà còn nói cho một lớp.

Nếu bạn lưu trữ các thuộc tính trong một mảng nội bộ, bạn có thể sử dụng để sao chép các giá trị:

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

} 

Bây giờ bạn có thể nhận được các mảng Values từ đối tượng và sao chép vào mảng bằng cách sử dụng phương pháp Array.CopyTo:

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

Việc chọn tạo ma trận là một loại giá trị có thể là IMO hợp lý. Ví dụ XNA chọn để làm điều đó. Nếu bạn chọn một lớp bạn cần làm cho nó không thay đổi được, nếu không bạn sẽ không nhận được ngữ nghĩa giá trị mà IMO là cần thiết cho một ma trận. Và nếu bạn làm cho nó bất biến, bạn sẽ cần phải tạo các trường hợp mới quá thường xuyên. Vì vậy, tôi tin rằng đây là một trong những trường hợp vi phạm các nguyên tắc và sử dụng cấu trúc lớn hơn có thể mang lại lợi ích. – CodesInChaos

+0

Bạn ** có thể giả sử bố cục nếu bạn chỉ định bố cục. Ví dụ. '[StructLayout (LayoutKind.Sequential)]'. 1 để biến nó thành một lớp, bởi vì các cấu trúc như thế này không hiệu quả (lý do DirectX/XNA/etc chuyển chúng theo ref). –

+0

@CodeInChaos: Vì cấu trúc có thể thay đổi được, bạn không có ngữ nghĩa giá trị ... – Guffa

1

OK đây là bộ phận thử nghiệm của tôi. Thuộc tính dự án của tôi là Release Build, "tối ưu hóa mã" và cũng "Cho phép mã không an toàn" được chọn.

Đáng ngạc nhiên (với tôi dù sao) hiệu suất là rất khác nhau bên trong và bên ngoài IDE. Khi chạy từ IDE có sự khác biệt đáng chú ý (và sự khác biệt x64 là rất lớn). Khi chạy bên ngoài IDE, đó là một rửa.

Vì vậy, điều này thật lạ và tôi không thể giải thích kết quả cho IDE + x64. Có lẽ điều này là thú vị đối với một số người, nhưng bởi vì nó không còn là mục đích cung cấp câu trả lời cho câu hỏi ban đầu của người đăng, có lẽ điều này nên được chuyển sang một chủ đề khác không?

Bên trong IDE, nền tảng thiết lập để x86

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

Bên trong IDE, nền tảng thiết lập để x64

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

Chạy từ dòng lệnh, nền tảng thiết lập để x86

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

Chạy từ dòng lệnh, nền tảng được đặt thành x64

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

Và đây là đoạn 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; 
     } 
    } 
    } 
} 
+0

Sự khác biệt hiệu suất là * rất lớn * nếu bạn đang so sánh mã * đang được sửa lỗi * với mã không được sửa lỗi; là vấn đề bạn đang thấy? Jitter tạo ra ít mã được tối ưu hóa mạnh hơn, và CLR thực hiện rất nhiều công việc phụ, nếu thời gian chạy biết rằng nó đang được sửa lỗi. –

+0

Có; cho đến bây giờ tôi không biết có sự khác biệt đáng kể đến mức nào. –

Các vấn đề liên quan