假设我想将数组的一部分向右移动1.我可以使用Array.Copy
或者只是逐个复制元素:
private static void BuiltInCopy<T>(T[] arg, int start) {
int length = arg.Length - start - 1;
Array.Copy(arg, start, arg, start + 1, length);
}
private static void ElementByElement<T>(T[] arg, int start) {
for (int i = arg.Length - 1; i > start; i--) {
arg[i] = arg[i - 1];
}
}
private static void ElementByElement2<T>(T[] arg, int start) {
int i = arg.Length - 1;
while (i > start)
arg[i] = arg[--i];
}
(Matt Howells建议使用ElementByElement2
。)
我使用Minibench对其进行了测试,结果让我感到非常惊讶。
internal class Program {
private static int smallArraySize = 32;
public static void Main(string[] args) {
BenchArrayCopy();
}
private static void BenchArrayCopy() {
var smallArrayInt = new int[smallArraySize];
for (int i = 0; i < smallArraySize; i++)
smallArrayInt[i] = i;
var smallArrayString = new string[smallArraySize];
for (int i = 0; i < smallArraySize; i++)
smallArrayString[i] = i.ToString();
var smallArrayDateTime = new DateTime[smallArraySize];
for (int i = 0; i < smallArraySize; i++)
smallArrayDateTime[i] = DateTime.Now;
var moveInt = new TestSuite<int[], int>("Move part of array right by 1: int")
.Plus(BuiltInCopy, "Array.Copy()")
.Plus(ElementByElement, "Element by element (for)")
.Plus(ElementByElement2, "Element by element (while)")
.RunTests(smallArrayInt, 0);
var moveString = new TestSuite<string[], string>("Move part of array right by 1: string")
.Plus(BuiltInCopy, "Array.Copy()")
.Plus(ElementByElement, "Element by element (for)")
.Plus(ElementByElement2, "Element by element (while)")
.RunTests(smallArrayString, "0");
moveInt.Display(ResultColumns.All, moveInt.FindBest());
moveString.Display(ResultColumns.All, moveInt.FindBest());
}
private static T ElementByElement<T>(T[] arg) {
ElementByElement(arg, 1);
return arg[0];
}
private static T ElementByElement2<T>(T[] arg) {
ElementByElement2(arg, 1);
return arg[0];
}
private static T BuiltInCopy<T>(T[] arg) {
BuiltInCopy(arg, 1);
return arg[0];
}
private static void BuiltInCopy<T>(T[] arg, int start) {
int length = arg.Length - start - 1;
Array.Copy(arg, start, arg, start + 1, length);
}
private static void ElementByElement<T>(T[] arg, int start) {
for (int i = arg.Length - 1; i > start; i--) {
arg[i] = arg[i - 1];
}
}
private static void ElementByElement2<T>(T[] arg, int start) {
int i = arg.Length - 1;
while (i > start)
arg[i] = arg[--i];
}
}
请注意,这里没有测量分配。所有方法都只是复制数组元素。由于我在32位操作系统上,int
和string
引用占用了相同的堆栈空间。
这是我期望看到的:
BuiltInCopy
应该是最快的,原因有两个:1)它可以做内存复制; 2)List<T>.Insert
使用Array.Copy
。另一方面,它是非泛型的,当数组有不同的类型时它可以做很多额外的工作,所以也许它没有充分利用1)。 ElementByElement
和int
,string
应该同样快。 BuiltInCopy
和int
,string
要么同样快,要么int
要慢一些(如果必须做一些拳击)。 但是,所有这些假设都是错误的(至少在我的.NET 3.5 SP1机器上)!
BuiltInCopy<int>
明显慢于ElementByElement<int>
。当尺寸增加时,BuiltInCopy<int>
变得更快。ElementByElement<string>
比ElementByElement<int>
慢4倍。 BuiltInCopy<int>
比BuiltInCopy<string>
快。 有人可以解释这些结果吗?
更新:来自CLR代码生成小组blog post on array bounds check elimination:
建议4:当您复制中型到大型数组时,请使用Array.Copy,而不是显式复制循环。首先,所有范围检查将被“提升”到循环外的单个检查。 如果数组包含对象引用,您还将有效地“提升”与存储到对象类型数组相关的两个额外费用:通常可以通过检查来消除与数组协方差相关的每元素“存储检查”关于数组的动态类型,垃圾收集相关的写屏障将被聚合并变得更有效。最后,我们将能够使用更高效的“memcpy”式复制循环。 (在即将到来的多核世界中,如果阵列足够大,甚至可能采用并行性!)
最后一列是分数(以刻度/迭代次数计算的总持续时间,按最佳结果标准化)。
两次smallArraySize = 32
:
f:\MyProgramming\TimSort\Benchmarks\bin\Release>Benchmarks.exe
============ Move part of array right by 1: int ============
Array.Copy() 468791028 0:30.350 1,46
Element by element (for) 637091585 0:29.895 1,06
Element by element (while) 667595468 0:29.549 1,00
============ Move part of array right by 1: string ============
Array.Copy() 432459039 0:30.929 1,62
Element by element (for) 165344842 0:30.407 4,15
Element by element (while) 150996286 0:28.399 4,25
f:\MyProgramming\TimSort\Benchmarks\bin\Release>Benchmarks.exe
============ Move part of array right by 1: int ============
Array.Copy() 459040445 0:29.262 1,38
Element by element (for) 645863535 0:30.929 1,04
Element by element (while) 651068500 0:30.064 1,00
============ Move part of array right by 1: string ============
Array.Copy() 403684808 0:30.191 1,62
Element by element (for) 162646202 0:30.051 4,00
Element by element (while) 160947492 0:30.945 4,16
两次smallArraySize = 256
:
f:\MyProgramming\TimSort\Benchmarks\bin\Release>Benchmarks.exe
============ Move part of array right by 1: int ============
Array.Copy() 172632756 0:30.128 1,00
Element by element (for) 91403951 0:30.253 1,90
Element by element (while) 65352624 0:29.141 2,56
============ Move part of array right by 1: string ============
Array.Copy() 153426720 0:28.964 1,08
Element by element (for) 19518483 0:30.353 8,91
Element by element (while) 19399180 0:29.793 8,80
f:\MyProgramming\TimSort\Benchmarks\bin\Release>Benchmarks.exe
============ Move part of array right by 1: int ============
Array.Copy() 184710866 0:30.456 1,00
Element by element (for) 92878947 0:29.959 1,96
Element by element (while) 73588500 0:30.331 2,50
============ Move part of array right by 1: string ============
Array.Copy() 157998697 0:30.336 1,16
Element by element (for) 19905046 0:29.995 9,14
Element by element (while) 18838572 0:29.382 9,46
答案 0 :(得分:5)
需要注意几点:
BuiltInCopy
,每次迭代还有一个方法调用 - 第一个方法调用另一个然后调用Array.Copy
的重载。这是一点开销。Array.Copy
采用一般Array
引用,可以是多维,不同类型等。您的方法只能在单个数组上工作。Array.Copy
会对排名,类型兼容性等进行大量检查。您的方法不会。我不知道如何解释引用类型和值类型之间的区别,但上面应该解释为什么内置副本和例程之间的公平比较并不公平。
答案 1 :(得分:1)
System.Buffer.BlockCopy更接近C的memcpy但仍有开销。对于小案例,您自己的方法通常会更快,而对于大型案例,BlockCopy会更快。
复制引用比复制int更慢,因为在分配引用时,.NET在大多数情况下必须做一些额外的工作 - 这个额外的工作与垃圾收集有关。
为了演示这一事实,请查看下面的代码,其中包含用于复制每个字符串元素的本机代码与复制每个int元素(本机代码在注释中)。请注意,它实际上进行函数调用以将字符串引用赋值给src [i],而int是内联完成的:
static void TestStrings()
{
string[] src = new string[5];
for (int i = 0; i < src.Length; i++)
src[i] = i.ToString();
string[] dst = new string[src.Length];
// Loop forever so we can break into the debugger when run
// without debugger.
while (true)
{
for (int i = 0; i < src.Length; i++)
/*
* 0000006f push dword ptr [ebx+esi*4+0Ch]
* 00000073 mov edx,esi
* 00000075 mov ecx,dword ptr [ebp-14h]
* 00000078 call 6E9EC15C
*/
dst[i] = src[i];
}
}
static void TestInts()
{
int[] src = new int[5];
for (int i = 0; i < src.Length; i++)
src[i] = i;
int[] dst = new int[src.Length];
// Loop forever so we can break into the debugger when run
// without debugger.
while (true)
{
for (int i = 0; i < src.Length; i++)
/*
* 0000003d mov ecx,dword ptr [edi+edx*4+8]
* 00000041 cmp edx,dword ptr [ebx+4]
* 00000044 jae 00000051
* 00000046 mov dword ptr [ebx+edx*4+8],ecx
*/
dst[i] = src[i];
}
}
答案 2 :(得分:0)
为了删除一些变量,我在VB 2008中尝试了相同的测试,没有使用函数调用,而是使用StopWatch对象而不是Minibench。
数组大小32
蜱
1529 - 整数,逐元素复制
2613 - 字符串,逐个元素复制
3619 - 整数,array.copy
3649 - string,array.copy
然而,当我尝试数组大小为3,200,000时,array.copy仍然较慢!也许array.copy不使用memcopy等价物。 vb与c ++之间可能存在一些差异,函数调用可能会在32个成员的测试中发挥重要作用。
阵列大小3,200,000
蜱
55,750,010 - 整数,逐元素复制
55,462,881 - 字符串,逐元素复制
69,500,804 - 整数,array.copy
81,102,288 - string,array.copy
来源:
Dim clock As New Stopwatch
Dim t(4) As Integer
Dim iSize As Integer = 3200000
Dim smallArrayInt(iSize) As Integer
Dim smallArrayString(iSize) As String
For i = LBound(smallArrayInt) To UBound(smallArrayInt)
smallArrayInt(i) = i
Next i
For i = LBound(smallArrayString) To UBound(smallArrayString)
smallArrayString(i) = Str(i)
Next i
clock.Reset() : clock.Start()
t(0) = clock.ElapsedTicks
For i = 1 To iSize
smallArrayInt(i - 1) = smallArrayInt(i)
Next i
t(1) = clock.ElapsedTicks - t(0)
For i = 1 To iSize
smallArrayInt(i - 1) = smallArrayInt(i)
Next i
t(2) = clock.ElapsedTicks - t(1)
Array.Copy(smallArrayInt, 1, smallArrayInt, 0, iSize - 1)
t(3) = clock.ElapsedTicks - t(2)
Array.Copy(smallArrayString, 1, smallArrayString, 0, iSize - 1)
t(4) = clock.ElapsedTicks - t(3)
MsgBox(t(1) & ", " & t(2) & ", " & t(3) & ", " & t(4))