I'm working on an IEqualityComparer
which is supposed to compare arrays of primitive types really fast. My plan is to obtain pointers to the arrays and memcmp
them. Like this:
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);
}
}
The fixed statement does not allow me to pin either Array
or T[]
.
There error messages are:
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')
Now, I don't actually care how I make this work (I'm not committed to this exact approach). How can I memcmp
two T[]
where I know that T
is a primitive/blittable type?
I really want to avoid switching on the type and creating a specialized (and duplicated) code version for each interesting type. Any kind of reflection solution is not workable due to performance constraints (and yes I really need the performance here - no premature optimization warnings apply as it is customary on Stack Overflow).
where I know that T is a primitive/blittable type
You know it, the compiler doesn't know that. The CLR demands that everything in a pinned object can no longer be moved by the garbage collector. For an Array, that includes its array elements. Only kind of T that qualifies is a simple value type, one that is blittable. Generics does not give you a way to constrain T to a blittable type.
You'd normally declare the arguments of memcmp() as byte[]. The pinvoke marshaller then already does the right thing and will pin the byte[] arrays before calling memcmp(). This however will not work either since you cannot easily convert a T[] to a byte[] either. You'll have to pin yourself with GCHandle. Declare the memcmp() arguments as IntPtr instead of byte[] accordingly.
The subset of types which can work is in practice small enough to consider simply writing method overloads instead of a generic method. Which now enables the pinvoke marshaller to take care of the pinning, overload the memcmp() function declarations accordingly.