从 System.Numerics.Vector<T> 块中的 nativeptr<T> 读取

Reading from nativeptr<T> in System.Numerics.Vector<T> chunks

在 F# 中,我们可以取消引用指向 'a 类型值的指针,就像这样

open FSharp.NativeInterop
let x = NativePtr.read p

其中 pnativeptr<'a>

现在假设这个指针指向一个 'a 值的数组,并且我们想通过 System.Numerics.Vector<_> 使用 SIMD 处理这个数组。为此,我们必须将 n 个连续的 'a 值加载到 Vector<'a> 结构中。对于 .NET/managed 数组,这可以通过使用适当的 Vector<_> 构造函数来实现。但不幸的是,我们只有一个 pointer(在这种特殊情况下,它实际上指向非托管堆),所以我们不能使用现有的构造函数重载之一。

那么,简单地将 p 重新解释为 nativeptr<Vector<'a>> 怎么样?

let inline cast<'T, 'U when 'U : unmanaged and 'T: unmanaged> (ptr: nativeptr<'T>) =
    ptr |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<'U>

let v = p |> NativePtr.cast<'a, Vector<'a>> |> NativePtr.read

遗憾的是,这行不通,因为 nativeptr 的类型参数必须满足 unmanaged 约束 - 而 Vector<_>as a generic data type 则不能。

现在,解决此问题的一种方法是结合使用 C# 和 NativeInterop,因为 C# 编译器不强制执行 unmanaged 约束。然而,我真的很想留在 F#。

是否希望这可以在 F# 中高效工作?或者等待 Vector<_> 的唯一选择是使用支持从指针加载的构造函数进行扩展?

F# 本身似乎没有解决此问题的方法。因此,我在 C# 中实现了一个小包装器,它基本上只是转发到 NativeInteropEx.NativePtr,但去掉了 C# 编译器未知的 unmanaged 约束:

using NativeInteropEx;
using System.Numerics;
using System.Runtime.CompilerServices;

public struct VectorView<T> where T: struct
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Vector<T> Get(IntPtr p, int idx) {
        return p.Get<Vector<T>>(idx);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Vector<T> Get(IntPtr p, long idx) {
        return p.Get<Vector<T>>(idx);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Set(IntPtr p, int idx, Vector<T> value) {
        p.Set(idx, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Set(IntPtr p, long idx, Vector<T> value) {
        p.Set(idx, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Vector<T> Read(IntPtr p) {
        return p.Read<Vector<T>>();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Write(IntPtr p, Vector<T> value) {
        p.Write(value);
    }
}

使用这个中间层,我们现在可以从 F#:

中将 nativeptr<'T> 取消引用为 Vector<'T>
# baseAddress: nativeptr<'T> when 'T: unmanaged
# idx: int64
let v = VectorView<'T>.Get(baseAddress, idx)
# v: Vector<'T> of Vector<'T>.Count 'T values starting with baseAddress + sizeof<'T> * idx