【问题标题】:Reading from nativeptr<T> in System.Numerics.Vector<T> chunks从 System.Numerics.Vector<T> 块中的 nativeptr<T> 读取
【发布时间】:2017-03-02 16:43:33
【问题描述】:

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

open FSharp.NativeInterop
let x = NativePtr.read p

其中pnativeptr&lt;'a&gt;

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

那么,简单地将p 重新解释为nativeptr&lt;Vector&lt;'a&gt;&gt; 怎么样?

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&lt;_&gt;as a generic data type 则没有。

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

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

【问题讨论】:

  • 您不能将原始内存“重新解释”为特定类型的对象,即使 C# 编译器允许。这不是 C 语言。 CLR 类型不仅仅是编译时构造,它们还具有运行时表示。 unmanaged 约束是有原因的。
  • Of course I can(并且,正如我所描述的,C# 编译器确实“让我”,但不是 F# 编译器。)。 Vector&lt;T&gt; 是 blittable(T 始终是非托管的,因为仅支持原始类型)并且归结为内存中的 128 (SSE) 或 256 位 (AVX) SIMD 步长,这是在其上实现高效 SIMD 内在函数的唯一方法.
  • 我不认为你的程序做你认为它做的事情。 IntPtr.Read 扩展方法并没有将内存“重新解释”为Vector&lt;double&gt;,它实际上将几个字节复制到一个新位置。
  • p.Read&lt;Vector&lt;double&gt;&gt; 将 16 (SSE) 或 32 (AVX) 字节复制到堆栈上,然后将其视为 Vector。它做的正是它应该做的。
  • @Fyodor 顺便说一句。看来您错过了我的问题的重点。 不是关于将内存块“转换”为其他类型,例如Vector 的托管数组或类似的东西。一点也不。

标签: .net pointers f# unmanaged simd


【解决方案1】:

在 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&lt;'T&gt; 取消引用为 Vector&lt;'T&gt;

# 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

【讨论】:

  • 向潜在用户澄清一下:这与从void* 和偏移量创建Vector&lt;T&gt; 的不安全(私有)System.Numerics.Vector&lt;T&gt; constructor 完全相同。唯一的区别是我们不需要显式类型切换,因为我们可以使用NativeInterop 进行泛型指针操作。
猜你喜欢
  • 2019-09-19
  • 2016-06-01
  • 2021-02-20
  • 2012-05-06
  • 2017-05-27
  • 1970-01-01
  • 1970-01-01
  • 2016-04-06
  • 1970-01-01
相关资源
最近更新 更多