【问题标题】:Fast array copy in C#C#中的快速数组复制
【发布时间】:2014-04-23 15:27:47
【问题描述】:

我有一个 C# 类,其中包含一个 int[] 数组(以及几个其他字段,但数组是主要内容)。代码通常会创建此类的副本,并且分析显示复制此数组的 Array.Copy() 调用需要大量时间。我该怎么做才能让它更快?

数组大小非常小且恒定:12 个元素。所以理想情况下,我想要一个 C 风格的数组:类本身内部的单个内存块(不是指针)。这在 C# 中可能吗? (如果需要,我可以使用不安全的代码。)

我已经试过了:

1) 使用 UIn64 和位移而不是数组。 (每个元素的值也非常小。)这确实加快了复制速度,但会减慢整个程序的速度。

2) 为每个数组元素使用单独的字段:int element0、int element1、int element2 等。同样,当我必须访问给定索引处的元素时,这总体上会变慢。

【问题讨论】:

  • 为什么小数据集需要快速的数组拷贝?优化不是最好用在其他地方吗?
  • 多一点上下文(可能还有代码)可能有助于解决这个问题。正如@mason 所说,很难相信复制 12 个元素是一个瓶颈,但我假设你经常这样做,也许是在一个循环中。所以你做这件事的背景可能会带来一些更好的答案。
  • 是的,它被频繁调用,在一个循环中。

标签: c# arrays performance unsafe


【解决方案1】:

如果您真的关心速度,我会查看System.Buffer.BlockCopy

http://msdn.microsoft.com/en-us/library/system.buffer.blockcopy.aspx

简单示例:

  int[] a = new int[] {1,2,3,4,5,6,7,8};
  int[] b = new int[a.Length];
  int size = sizeof(int);
  int length = a.Length * size;               
  System.Buffer.BlockCopy(a, 0, b, 0, length);

在这里进行精彩的讨论:Array.Copy vs Buffer.BlockCopy

【讨论】:

  • 感谢 Buffer.BlockCopy - 它加快了约 10% 的速度(总体而言,不仅仅是复制),因此对于所涉及的工作而言,这绝对是一个很好的改进。 :)
【解决方案2】:

这篇文章很旧,但是任何与 OP 处于类似情况的人都应该看看结构中的固定大小缓冲区。它们正是 OP 所要求的:直接存储在类中的具有恒定大小的原始类型数组。

您可以创建一个结构来表示您的集合,其中将包含固定大小的缓冲区。数据将直接存储在结构中,结构将直接存储在您的类中。你可以通过简单的分配来复制。

它们带有一些警告:

  • 它们只能用于原始类型。
  • 它们需要在您的结构中使用“不安全”关键字。
  • 在编译时必须知道大小。

过去,您必须使用 fixed 关键字和指针来访问它们,但最近对 C# 的更改以迎合性能编程已经使这变得不必要了。您现在可以像使用数组一样使用它们。

public unsafe struct MyIntContainer
{
    private fixed int myIntegers[12];

    public int this[int index]
    {
        get => this.myIntegers[index];
        set => this.myIntegers[index] = value;
    }
}

没有内置的绑定检查,因此最好将自己包含在这样的属性中,将任何跳过绑定检查的功能封装在方法内。我在移动设备上,或者我会在我的示例中使用它。

【讨论】:

  • 我在公共 this[int index] 中的“this”上收到“类型预期”编译器错误,针对 .NET 4.7.2
  • 但是注释掉这个函数,对于 1024 字节数组大小,结构的复制比元素复制快 80 倍
  • @Markus 谢谢马库斯。我已经更正了示例代码。多么愚蠢的错误!
【解决方案3】:

您询问了托管数组。如果您满足于使用fixed / unsafe,这会非常快。

struct 是可赋值的,就像任何原语一样。由于没有方法调用开销,几乎可以肯定比Buffer.BlockCopy() 或任何其他方法快:

public unsafe struct MyStruct //the actual struct used, contains all
{
    public int a;
    public unsafe fixed byte buffer[16];
    public ulong b;
    //etc.
}

public unsafe struct FixedSizeBufferWrapper //contains _only_ the buffer
{
    public unsafe fixed byte buffer[16];
}

unsafe 
{
    fixed (byte* bufferA = myStructA.buffer, bufferB = myStructB.buffer)
    {
        *((FixedSizeBufferWrapper*)bufferA) =
        *((FixedSizeBufferWrapper*)bufferB);
    }
}

我们将fixed-size byte 缓冲区从您的每个原始结构转换为包装指针类型并取消引用每个指针,以便我们可以通过值将一个分配给另一个;直接分配fixed 缓冲区是不可能的,因此包装器基本上是零开销(它只会影响无论如何完成的指针运算中使用的值)。该包装器仅用于强制转换。

我们必须进行强制转换,因为(至少在我的 C# 版本中)我们不能将除原始类型(通常为 byte[])之外的任何东西分配为缓冲区,并且不允许在 fixed(...) 内进行强制转换。

编辑:这似乎被翻译成对 Buffer.Memcpy() 的调用(特别是 Buffer.memcpy4() 在我的情况下,在 Unity / Mono 中)进行复制。

【讨论】:

  • 聪明!我当然不再处理该代码,但我仍然感谢您发布我 7 岁问题的答案。很想尝试一下,看看它有多快。
  • 更多的是与社区分享它,而不是您的问题存在多长时间。 Stackoverflow 是一个共享知识库,这就是回答没有过期时间的原因。
猜你喜欢
  • 2022-01-18
  • 2021-02-21
  • 2016-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-21
  • 2014-12-10
相关资源
最近更新 更多