我正在研究一个IEqualityComparer
,它应该可以非常快速地比较基本类型的数组。我的计划是获取指向数组的指针和memcmp
它们。像这样:
public unsafe override bool Equals(T[] x, T[] y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if (x.Length != y.Length) return false;
var xArray = (Array)x;
var yArray = (Array)y;
fixed (void* xPtr = xArray) //compiler error 1
fixed (T* yPtr = y) //compiler error 2
{
return memcmp(xPtr, yPtr, x.Length * this.elementSize);
}
}
固定语句不允许我固定Array
或T[]
。
错误信息是:
1. Cannot implicitly convert type 'System.Array' to 'void*'
2. Cannot take the address of, get the size of, or declare a pointer to a managed type ('T')
现在,我实际上并不关心我是如何完成这项工作的(我并没有采用这种方法)。我怎么知道memcmp
两个T[]
,我知道T
是一个原始/ blittable类型?
我真的想避免为每种有趣的类型切换类型并创建专门的(和重复的)代码版本。由于性能限制,任何类型的反射解决方案都是不可行的(是的,我真的需要这里的性能 - 没有过早的优化警告,因为它在Stack Overflow上是习惯的。)
答案 0 :(得分:5)
我知道T是原始/ blittable类型
你知道,编译器不知道。 CLR要求垃圾收集器不再移动固定对象中的所有。对于包含其数组元素的Array。只有符合条件的T才是一种简单的值类型,一种是 blittable 。泛型并没有给你一种方法来将T限制为一个blittable类型。
您通常将memcmp()的参数声明为byte []。然后pinvoke marshaller已经做了正确的事情,并在调用memcmp()之前固定byte []数组。然而,这也不起作用,因为你不能轻易地将T []转换为字节[]。你必须用GCHandle固定自己。相应地将memcmp()参数声明为IntPtr而不是byte []。
可以工作的类型子集实际上足够小,可以考虑简单地编写方法重载而不是泛型方法。现在,pinvoke marshaller可以处理钉扎,相应地重载memcmp()函数声明。
答案 1 :(得分:2)
您可以为要比较的每种数组编写单独的P / Invoke前端。我知道你真的不想专注于T,但我认为开销不是太大。
这有点像黑客,因为我为同一个API函数定义了多个具有不同签名的P / Invoke方法,但通过这样做,我可以利用P / Invoke编组支持。
(注意,如果源数据确实是一个字节数组,那么memcmp返回值的符号实际上才有意义。如果你给它一个int数组,你应该只将return值与零比较并忽略它所暗示的顺序对于整数没有意义。)
例如,以下代码为我打印以下内容(RELEASE构建,而不是调试版本):
MemCmp with ints took 00:00:08.0768666
ManagedMemCmp with ints took 00:00:10.3750453
MemCmp with bytes took 00:00:01.8740001
ManagedMemCmp with bytes took 00:00:09.2885763
请注意,byte []测试使用的是字节,因此比较int []测试使用的字节数的四分之一。托管代码进行相同数量的比较,因此速度相对较慢。
比较int数组的时间之间并没有太大差异,但是比较字节数组有很大的不同。这告诉我,可能存在一个托管优化,它使用 fixed 从字节数组中获取一个指向int的指针,以便一次比较4个字节(对于可能的额外字节,可能会有一些错误)不适合int的结尾。)
我还认为你可以编写一个多线程托管版本(使用“int *”优化来比较字节数组),这比非托管memcmp()要快得多,这当然不是多线程的(据我所知) )。
无论如何,这是我的测试代码。记住,RELEASE构建,而不是调试!
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Demo
{
public static class Program
{
private static void Main(string[] args)
{
int[] a1 = new int[1000000];
int[] a2 = new int[1000000];
for (int i = 0; i < a1.Length-1; ++i)
{
a1[i] = i;
a2[i] = i;
}
a1[a1.Length-1] = 1;
a2[a1.Length-1] = 2;
byte[] b1 = new byte[1000000];
byte[] b2 = new byte[1000000];
for (int i = 0; i < b1.Length-1; ++i)
{
b1[i] = (byte)i;
b2[i] = (byte)i;
}
b1[a1.Length-1] = 1;
b2[a1.Length-1] = 2;
Stopwatch sw = Stopwatch.StartNew();
testWithMemCmp(a1, a2);
sw.Stop();
Console.WriteLine("MemCmp with ints took " + sw.Elapsed);
sw.Restart();
testWithManagedMemCmp(a1, a2);
sw.Stop();
Console.WriteLine("ManagedMemCmp with ints took " + sw.Elapsed);
sw.Restart();
testWithMemCmp(b1, b2);
sw.Stop();
Console.WriteLine("MemCmp with bytes took " + sw.Elapsed);
sw.Restart();
testWithManagedMemCmp(b1, b2);
sw.Stop();
Console.WriteLine("ManagedMemCmp with bytes took " + sw.Elapsed);
}
private static void testWithMemCmp(int[] a1, int[] a2)
{
for (int j = 0; j < COUNT; ++j)
{
MemCmp(a1, a2);
}
}
private static void testWithMemCmp(byte[] a1, byte[] a2)
{
for (int j = 0; j < COUNT; ++j)
{
MemCmp(a1, a2);
}
}
private static void testWithManagedMemCmp(int[] a1, int[] a2)
{
for (int j = 0; j < COUNT; ++j)
{
ManagedMemCmp(a1, a2);
}
}
private static void testWithManagedMemCmp(byte[] a1, byte[] a2)
{
for (int j = 0; j < COUNT; ++j)
{
ManagedMemCmp(a1, a2);
}
}
public static bool ManagedMemCmp(int[] a1, int[] a2)
{
if (a1 == null || a2 == null || a1.Length != a2.Length)
{
throw new InvalidOperationException("Arrays are null or different lengths.");
}
for (int i = 0; i < a1.Length; ++i)
{
if (a1[i] != a2[i])
{
return false;
}
}
return true;
}
public static bool ManagedMemCmp(byte[] a1, byte[] a2)
{
if (a1 == null || a2 == null || a1.Length != a2.Length)
{
throw new InvalidOperationException("Arrays are null or different lengths.");
}
for (int i = 0; i < a1.Length; ++i)
{
if (a1[i] != a2[i])
{
return false;
}
}
return true;
}
public static bool MemCmp(byte[] a1, byte[] a2)
{
if (a1 == null || a2 == null || a1.Length != a2.Length)
{
throw new InvalidOperationException("Arrays are null or different lengths.");
}
return memcmp(a1, a2, new UIntPtr((uint)a1.Length)) == 0;
}
public static bool MemCmp(int[] a1, int[] a2)
{
if (a1 == null || a2 == null || a1.Length != a2.Length)
{
throw new InvalidOperationException("Arrays are null or different lengths.");
}
return memcmp(a1, a2, new UIntPtr((uint)(a1.Length * sizeof(int)))) == 0;
}
[DllImport("msvcrt.dll")]
private static extern int memcmp(byte[] a1, byte[] a2, UIntPtr count);
[DllImport("msvcrt.dll")]
private static extern int memcmp(int[] a1, int[] a2, UIntPtr count);
private const int COUNT = 10000;
}
}
答案 2 :(得分:1)
我同意Daniel A. White的评论告诉你,它可能不会导致性能提升,但是由于编组非托管代码等的开销而导致性能下降。
话虽如此,您应该可以使用GCHandle.Alloc
:
public unsafe bool Equals(T[] x, T[] y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if (x.Length != y.Length) return false;
GCHandle handleOfX = default(GCHandle);
GCHandle handleOfY = default(GCHandle);
handleOfX = GCHandle.Alloc(x, GCHandleType.Pinned);
handleOfY = GCHandle.Alloc(y, GCHandleType.Pinned);
try
{
return memcmp(handleOfX.AddrOfPinnedObject(),
handleOfY.AddrOfPinnedObject(),
x.Length * this.elementSize);
}
finally
{
if(handleOfX != default(GCHandle)) handleOfX.Free();
if(handleOfY != default(GCHandle)) handleOfY.Free();
}
}