如何在C#中快速将二维数组转换为一维数组?

4
我有一个多维的double[,]数组,其大小为[1,N](假设N已知)。最快的方法是将其转换成长度为N的一维double[]数组?
我是C#的新手,使用它与Matlab函数交互。我使用的Matlab函数返回一个一维行向量,但在C#中它被视为object[,],我只能将其转换为double[,] 类型。然而,我需要它作为另一个函数的输入类型为double[]。有没有一种快速的方法将这个二维double数组转换为具有相同元素和相同顺序的一维数组?
由于我正在处理实时应用程序,所以需要尽可能快速地进行转换。

2D数组的大小是(1,N)。假设在第一维中它有[1,2,3,4],而第二维中没有其他元素,那么1D数组也将是相同的[1,2,3,4]。但是它将是长度为N的double[]数组,而不是大小为(1,N)的double[,]数组。 - gtak
@vasily.sib 你自己试一下,看看为什么不行 ;) - Loocid
是的,不是一个锯齿数组。 - gtak
3个回答

10

最快的方法是直接内存复制,比如使用 Buffer.BlockCopyMarshal.Copy 等方法。

示例

var ary1 = new int[3,3];
ary1[0, 0] = 0;
ary1[0, 1] = 1;
ary1[0, 2] = 2;
ary1[1, 0] = 3;
ary1[1, 1] = 4;
ary1[1, 2] = 5;
ary1[2, 0] = 6;
ary1[2, 1] = 7;
ary1[2, 2] = 8;

var ary2 = new int[9];

Buffer.BlockCopy(ary1, 0, ary2, 0, 9 * sizeof(int));

Console.WriteLine(string.Join(",", ary2));

输出

0,1,2,3,4,5,6,7,8

您也可以使用memcpy,但是这将导致从PInvoke产生的初始开销。

[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);

基准测试

基准测试进行了100次,每次运行后进行垃圾回收,并相互验证以确保准确性。结果从最快的运行时间的前75%中汇总,比例尺度为多维数组的维度长度,即double[scale,scale]

┌──────────────────┬────────────────────────────────────────────┐
│        Test Mode │ Release (64Bit)                            │
│   Test Framework │ .NET Framework 4.7.1 (CLR 4.0.30319.42000) │
╞══════════════════╪════════════════════════════════════════════╡
│ Operating System │ Microsoft Windows 10 Pro                   │
│          Version │ 10.0.17763                                 │
├──────────────────┼────────────────────────────────────────────┤
│       CPU System │ Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz    │
│  CPU Description │ Intel64 Family 6 Model 158 Stepping 9      │
├──────────────────┼──────────┬──────────────────┬──────────────┤
│  Cores (Threads) │ 4 (8)    │     Architecture │ x64          │
│      Clock Speed │ 3600 MHz │        Bus Speed │ 100 MHz      │
│          L2Cache │ 1 MB     │          L3Cache │ 8 MB         │
└──────────────────┴──────────┴──────────────────┴──────────────┘

字节数组的结果

┌── Standard input ──────────────────────────────────────────────────────────────────────┐
│ ValueAverageFastestCyclesGarbageTestGain │
├── Scale 256 ────────────────────────────────────────────────────────────── 0.628 sec ──┤
│ MarshalCopy6.450 µs │   4.300 µs │  26.698 K0.000 BPass24.21 % │
│ memcpy          │   7.992 µs │   4.700 µs │  32.758 K0.000 BPass6.09 % │
│ BlockCopy8.511 µs │   4.600 µs │  37.053 K0.000 BBase0.00 % │
│ ElemCopy Unsafe26.124 µs │  24.400 µs │  97.794 K0.000 BPass-206.96 % │
│ ElemCopy75.426 µs │  72.300 µs │ 273.201 K0.000 BPass-786.27 % │
│ Linq7.619 ms │   7.078 ms │  27.103 M0.000 BPass-89,429.16 % │
├── Scale 512 ────────────────────────────────────────────────────────────── 1.826 sec ──┤
│ MarshalCopy17.939 µs │  17.300 µs │  68.142 K0.000 BPass1.33 % │
│ BlockCopy18.182 µs │  17.300 µs │  69.770 K0.000 BBase0.00 % │
│ memcpy          │  25.897 µs │  19.200 µs │  97.357 K0.000 BPass-42.44 % │
│ ElemCopy Unsafe128.776 µs │ 102.400 µs │ 471.381 K0.000 BPass-608.28 % │
│ ElemCopy293.237 µs │ 285.400 µs │   1.055 M0.000 BPass-1,512.82 % │
│ Linq31.057 ms │  29.750 ms │ 110.869 M0.000 BPass-170,714.99 % │
├── Scale 1,024 ──────────────────────────────────────────────────────────── 6.579 sec ──┤
│ memcpy          │ 268.747 µs │ 255.600 µs │ 972.409 K0.000 BPass12.28 % │
│ BlockCopy306.371 µs │ 291.500 µs │   1.104 M0.000 BBase0.00 % │
│ MarshalCopy307.868 µs │ 293.100 µs │   1.111 M0.000 BPass-0.49 % │
│ ElemCopy Unsafe583.684 µs │ 561.100 µs │   2.103 M0.000 BPass-90.52 % │
│ ElemCopy1.325 ms │   1.305 ms │   4.768 M0.000 BPass-332.50 % │
│ Linq122.561 ms │ 120.700 ms │ 439.940 M0.000 BPass-39,904.01 % │
├── Scale 2,048 ─────────────────────────────────────────────────────────── 26.084 sec ──┤
│ memcpy          │   1.179 ms │   1.129 ms │   4.230 M0.000 BPass17.50 % │
│ MarshalCopy1.397 ms │   1.346 ms │   5.029 M0.000 BPass2.25 % │
│ BlockCopy1.429 ms │   1.360 ms │   5.135 M0.000 BBase0.00 % │
│ ElemCopy Unsafe2.441 ms │   2.312 ms │   8.757 M0.000 BPass-70.88 % │
│ ElemCopy5.466 ms │   5.264 ms │  19.587 M0.000 BPass-282.61 % │
│ Linq497.788 ms │ 489.885 ms │   1.786 B0.000 BPass-34,743.98 % │
├── Scale 4,096 ─────────────────────────────────────────────────────────── 42.833 sec ──┤
│ memcpy          │   5.218 ms │   4.889 ms │  18.633 M0.000 BPass15.45 % │
│ BlockCopy6.172 ms │   5.887 ms │  22.141 M0.000 BBase0.00 % │
│ MarshalCopy6.255 ms │   5.871 ms │  22.350 M0.000 BPass-1.35 % │
│ ElemCopy Unsafe9.972 ms │   9.535 ms │  35.716 M0.000 BPass-61.57 % │
│ ElemCopy22.149 ms │  21.741 ms │  79.508 M0.000 BPass-258.87 % │
│ Linq1.969 s │    1.948 s │   7.067 B0.000 BPass-31,796.88 % │
└────────────────────────────────────────────────────────────────────────────────────────┘

双重数组的结果

┌── Standard input ─────────────────────────────────────────────────────────────────────┐
│ ValueAverageFastestCyclesGarbageTestGain │
├── Scale 256 ───────────────────────────────────────────────────────────── 0.688 sec ──┤
│ BlockCopy35.116 µs │  32.000 µs │ 131.112 K0.000 BBase0.00 % │
│ MarshalCopy43.879 µs │  34.900 µs │ 162.031 K0.000 BPass-24.96 % │
│ ElemCopy Unsafe44.182 µs │  39.500 µs │ 162.891 K0.000 BPass-25.82 % │
│ memcpy          │  60.113 µs │  58.200 µs │ 219.950 K0.000 BPass-71.19 % │
│ ElemCopy94.418 µs │  86.100 µs │ 356.173 K0.000 BPass-168.88 % │
│ Linq7.402 ms │   7.123 ms │  26.638 M0.000 BPass-20,979.54 % │
├── Scale 512 ───────────────────────────────────────────────────────────── 2.237 sec ──┤
│ memcpy          │ 612.603 µs │ 552.000 µs │   2.199 M0.000 BPass10.20 % │
│ ElemCopy Unsafe641.013 µs │ 586.200 µs │   2.312 M0.000 BPass6.03 % │
│ MarshalCopy675.192 µs │ 621.200 µs │   2.434 M0.000 BPass1.02 % │
│ BlockCopy682.161 µs │ 622.700 µs │   2.458 M0.000 BBase0.00 % │
│ ElemCopy745.692 µs │ 708.800 µs │   2.687 M0.000 BPass-9.31 % │
│ Linq33.579 ms │  31.039 ms │ 119.974 M0.000 BPass-4,822.46 % │
├── Scale 1,024 ─────────────────────────────────────────────────────────── 7.830 sec ──┤
│ memcpy          │   2.708 ms │   2.488 ms │   9.712 M0.000 BPass20.63 % │
│ ElemCopy Unsafe3.156 ms │   2.789 ms │  11.324 M0.000 BPass7.51 % │
│ MarshalCopy3.208 ms │   2.979 ms │  11.508 M0.000 BPass5.97 % │
│ ElemCopy3.342 ms │   3.091 ms │  12.021 M0.000 BPass2.05 % │
│ BlockCopy3.412 ms │   2.959 ms │  12.234 M0.000 BBase0.00 % │
│ Linq125.854 ms │ 122.872 ms │ 451.735 M0.000 BPass-3,588.76 % │
├── Scale 2,048 ────────────────────────────────────────────────────────── 29.876 sec ──┤
│ memcpy          │  10.989 ms │  10.288 ms │  39.509 M0.000 BPass15.14 % │
│ ElemCopy Unsafe12.075 ms │  11.418 ms │  43.436 M0.000 BPass6.76 % │
│ BlockCopy12.950 ms │  12.462 ms │  46.578 M0.000 BBase0.00 % │
│ MarshalCopy13.032 ms │  12.427 ms │  46.876 M0.000 BPass-0.64 % │
│ ElemCopy13.469 ms │  12.689 ms │  48.471 M0.000 BPass-4.01 % │
│ Linq502.897 ms │ 497.335 ms │   1.805 B0.000 BPass-3,783.35 % │
├── Scale 4,096 ────────────────────────────────────────────────────────── 58.669 sec ──┤
│ memcpy          │  45.901 ms │  44.148 ms │ 164.750 M0.000 BPass15.80 % │
│ ElemCopy Unsafe51.889 ms │  50.497 ms │ 186.137 M0.000 BPass4.82 % │
│ MarshalCopy53.237 ms │  51.847 ms │ 191.248 M0.000 BPass2.34 % │
│ BlockCopy54.514 ms │  52.417 ms │ 195.778 M0.000 BBase0.00 % │
│ ElemCopy56.551 ms │  54.674 ms │ 203.163 M0.000 BPass-3.74 % │
│ Linq2.004 s │    1.976 s │   7.192 B0.000 BPass-3,575.84 % │
└───────────────────────────────────────────────────────────────────────────────────────┘

测试代码

[Test("BlockCopy", "", true)]
public double[] Test1(double[,] input, int scale)
{
   var width = input.GetLength(0);
   var height = input.GetLength(1);
   var size = width * height;
   var result = new double[size];
   Buffer.BlockCopy(input, 0, result, 0, size * sizeof(double));
   return result;
}

[Test("MarshalCopy", "", false)]
public unsafe double[] Test2(double[,] input, int scale)
{
   var width = input.GetLength(0);
   var height = input.GetLength(1);
   var size = width * height;
   var result = new double[size];
   fixed (double* pInput = input)
      Marshal.Copy((IntPtr)pInput, result, 0, size );
   return result;
}

[Test("ElemCopy", "", false)]
public double[] Test3(double[,] input, int scale)
{
   var width = input.GetLength(0);
   var height = input.GetLength(1);
   var size = width * height;

   var result = new double[size];
   for (var i = 0; i < width; i++)
      for (var j = 0; j < height; j++)
         result[i * height + j] = input[i,j];
   return result;
}
[Test("ElemCopy Unsafe", "", false)]
unsafe public double[] Test4(double[,] input, int scale)
{
   var width = input.GetLength(0);
   var height = input.GetLength(1);
   var size = width * height;

   var result = new double[size];
   fixed (double* pInput = input, pResult = result)
      for (var i = 0; i < width; i++)
         for (var j = 0; j < height; j++)
            *(pResult + i * height + j) = *(pInput + i * height + j);
   return result;
}
[Test("memcpy", "", false)]
unsafe public double[] Test5(double[,] input, int scale)
{
   var width = input.GetLength(0);
   var height = input.GetLength(1);
   var size = width * height;

   var result = new double[size];
   fixed (double* pInput = input, pResult = result)
      memcpy((IntPtr)pResult, (IntPtr)pInput, (UIntPtr)(size * sizeof(double)));
   return result;
}
[Test("Linq", "", false)]
unsafe public double[] Test6(double[,] input, int scale)
{
   return input.OfType<double>().ToArray();
}

注意:您应该在自己的规格PC、框架等上运行这些测试,并只将其用作指南。


2
我只想知道你用什么工具来构建这样好看的表格? - shingo
1
@shingo 我有一个可以绘制盒子和表格的库,它能实现一些花哨的效果。我会在有机会对其进行一些整理之后将其放在GitHub和NuGet上。 - TheGeneral

4

一开始我想的是不规则数组,所以这个问题看起来太简单了 :) 不过,对于二维数组来说,也不算太难:

using System.Linq;

var twoDArray = new double[1,5] { { 1, 2, 3, 4, 5 } };
var oneDArray = twoDArray.OfType<double>().ToArray();
// oneDArray == { 1, 2, 3, 4, 5 }

它还可以处理任何大小的2D数组:

var twoDArray2 = new double[2,3] { { 1, 2, 3 }, { 4, 5, 6 } };
var oneDArray2 = twoDArray2.OfType<double>().ToArray();
// oneDArray2 == { 1, 2, 3, 4, 5, 6 }

关于性能的抒情漫谈:

LINQ比直接内存操作要慢得多,这是确定的。但你应该记住,LINQ(以及C#)并不是关于“执行时间优化”,而是关于“开发便利性”。对我来说,易于阅读的一行LINQ表达式比快速的内存操作更可取,即使它将double[1, 65535]转换为35.116微秒而不是7.402毫秒的double[65535]

C#由于其受管理的特性而较慢,因此如果您需要运行非常快的算法-建议您切换到C++


点赞,我完全同意,这个一行代码非常方便易懂。 - TheGeneral
1
我将另一个答案标记为解决方案,因为从技术上讲,它是对我的原始问题的确切且优秀的答案。然而,我认识到了优化与便利之间的权衡,并且暂时在我的 C# 应用程序中使用了这个答案。同时,我已经开始迁移所有内容到 C++ 中,以实现更快速运行的算法。 - gtak

0
我们可以使用下面的简单逻辑将C#中的二维数组转换为一维数组。这个过程不需要使用LINQ或其他命名空间。
/* C# Code */
int m=2, n=3;
int[,] a= new int[2,3] {
    {  3,  7,  9 },
    {  6,  88,  9 },
};
int[] b=new int[6];;
int k = 0;

for (int i = 0; i < m; i++)
{
    for (int j = 0; j < n; j++)
    {
       Array1D[k++] = Array2D[i, j];
    }
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接