可以将泛型传递给 .NET Core 硬件内在函数方法吗?
Can you pass generics to .NET Core hardware intrinsics methods?
我正在编写一个基本库来试验 C# 硬件内在函数(System.Runtime.Intrinsics*
命名空间),并且有一个方法可以支持任何 'hardware' 类型(Byte
、SByte
... UInt64
, Double
)
当尝试使用泛型签名时,编译器无法使用泛型并且无法选择正确的重载;例如:
public static unsafe void GenericSimd<T>(T value, ReadOnlySpan<T> span) where T : unmanaged
{
fixed (T* fixedSpan = span)
{
Vector128<T> vec0 = Vector128.Create(value); // CS1503, Cannot convert T to byte
Vector128<T> vec1 = Sse2.LoadVector128(fixedSpan); // CS1503, Cannot convert T* to byte*
}
}
参考:CS1503
我认为这是由于 unmanaged
约束允许额外的非 'hardware' 类型(Decimal
、enum
等),因此限制不够保证存在适当的过载。
定义一个接口作为附加约束与 unmanaged
一起使用也是行不通的,因为它需要部分内置类型。
有没有办法使用泛型实现此方法并避免为每种类型编写重载?
一般来说,您不能对泛型执行此操作。至少因为 Vectors 没有通用的创建方法或转换选项。但是 Span<T>
.
有一个选项
public static unsafe void GenericSimd<T>(ReadOnlySpan<T> span)
where T : struct
{
ReadOnlySpan<byte> bytes = MemoryMarshal.Cast<T, byte>(span); // no data copy here involved, it's lightning fast
fixed (byte* fixedSpan = bytes)
{
// this way
Vector128<byte> vec1 = *(Vector128<byte>*)fixedSpan;
// or this way
Vector128<byte> vec2 = Sse2.LoadVector128(fixedSpan);
}
}
但请确保您在 Span
中有足够的字节(16 或更多)来填充完整的 Vector128<byte>
。
你也可能得到 T
的尺码
int size = Marshal.SizeOf(typeof(T));
然后 switch-case
取决于变量的大小。但是处理整数和浮点数所需数据的行为不同。
很多切换逻辑不适合 SSE/AVX 代码。至少因为它必须尽可能快,但是 switch
或 if
,即使 Cast
也会消耗 CPU 资源。
我建议您制作类似于 .NET SSE/AVX 方法的非泛型重载。
顺便说一句,如果您需要纯通用硬件加速 Vector<T>
- welcome to System.Numerics.Vectors
。我测试过,在大多数情况下,它在我的 Core i7 上显示出与 Intrinsics 相同的性能。
public static void GenericSimd<T>(T value, ReadOnlySpan<T> span)
where T : struct
{
Vector<T> vector1 = new Vector<T>(value); // fine
Vector<T> vector2 = new Vector<T>(span); // also fine
}
您也可以检查例如Vector<int>.Count
获取向量的容量。
我正在编写一个基本库来试验 C# 硬件内在函数(System.Runtime.Intrinsics*
命名空间),并且有一个方法可以支持任何 'hardware' 类型(Byte
、SByte
... UInt64
, Double
)
当尝试使用泛型签名时,编译器无法使用泛型并且无法选择正确的重载;例如:
public static unsafe void GenericSimd<T>(T value, ReadOnlySpan<T> span) where T : unmanaged
{
fixed (T* fixedSpan = span)
{
Vector128<T> vec0 = Vector128.Create(value); // CS1503, Cannot convert T to byte
Vector128<T> vec1 = Sse2.LoadVector128(fixedSpan); // CS1503, Cannot convert T* to byte*
}
}
参考:CS1503
我认为这是由于 unmanaged
约束允许额外的非 'hardware' 类型(Decimal
、enum
等),因此限制不够保证存在适当的过载。
定义一个接口作为附加约束与 unmanaged
一起使用也是行不通的,因为它需要部分内置类型。
有没有办法使用泛型实现此方法并避免为每种类型编写重载?
一般来说,您不能对泛型执行此操作。至少因为 Vectors 没有通用的创建方法或转换选项。但是 Span<T>
.
public static unsafe void GenericSimd<T>(ReadOnlySpan<T> span)
where T : struct
{
ReadOnlySpan<byte> bytes = MemoryMarshal.Cast<T, byte>(span); // no data copy here involved, it's lightning fast
fixed (byte* fixedSpan = bytes)
{
// this way
Vector128<byte> vec1 = *(Vector128<byte>*)fixedSpan;
// or this way
Vector128<byte> vec2 = Sse2.LoadVector128(fixedSpan);
}
}
但请确保您在 Span
中有足够的字节(16 或更多)来填充完整的 Vector128<byte>
。
你也可能得到 T
int size = Marshal.SizeOf(typeof(T));
然后 switch-case
取决于变量的大小。但是处理整数和浮点数所需数据的行为不同。
很多切换逻辑不适合 SSE/AVX 代码。至少因为它必须尽可能快,但是 switch
或 if
,即使 Cast
也会消耗 CPU 资源。
我建议您制作类似于 .NET SSE/AVX 方法的非泛型重载。
顺便说一句,如果您需要纯通用硬件加速 Vector<T>
- welcome to System.Numerics.Vectors
。我测试过,在大多数情况下,它在我的 Core i7 上显示出与 Intrinsics 相同的性能。
public static void GenericSimd<T>(T value, ReadOnlySpan<T> span)
where T : struct
{
Vector<T> vector1 = new Vector<T>(value); // fine
Vector<T> vector2 = new Vector<T>(span); // also fine
}
您也可以检查例如Vector<int>.Count
获取向量的容量。