【问题标题】:How to pin a generic Span<T> instance to work on it with Parallel.For?如何固定通用 Span<T> 实例以使用 Parallel.For 处理它?
【发布时间】:2018-06-13 11:04:57
【问题描述】:

我正在使用新的 Span&lt;T&gt; 类型重写我的一些扩展方法,但我无法找到正确固定通用实例以便能够使用并行代码处理它的方法。

例如,考虑这个扩展方法:

public static unsafe void Fill<T>(this Span<T> span, [NotNull] Func<T> provider) where T : struct
{
    int
        cores = Environment.ProcessorCount,
        batch = span.Length / cores,
        mod = span.Length % cores,
        sizeT = Unsafe.SizeOf<T>();
    //fixed (void* p0 = &span.DangerousGetPinnableReference()) // This doesn't work, can't pin a T object
    void* p0 = Unsafe.AsPointer(ref span.DangerousGetPinnableReference());
    {
        byte* p = (byte*)p0; // Local copy for the closure
        Parallel.For(0, cores, i =>
        {
            byte* start = p + i * batch * sizeT;
            for (int j = 0; j < batch; j++)
                Unsafe.Write(start + sizeT * j, provider());
        });

        // Remaining values
        if (mod == 0) return;
        for (int i = span.Length - mod; i < span.Length; i++)
            span[i] = provider();
    }
}

在这里,我只想使用一些值提供程序填充输入 Span&lt;T&gt;,并且由于这些向量可能非常大,我想并行填充它们。

这只是一个例子,所以即使在这里使用并行代码也不是 100%必要,问题仍然存在,因为我需要使用并行 反正迟早要重新编码。

现在,这段代码确实可以工作,但是因为我从来没有真正固定输入范围,并且考虑到它很可能指向一些托管的 T[] 向量,这可以是GC 一直在移动,我想我可能很幸运地看到它在我的测试中运行良好。

所以,我的问题是:

有没有办法固定一个通用的Span&lt;T&gt; 实例并获得一个简单的void* 指针,以便我可以在闭包中传递它以并行代码处理Span&lt;T&gt; 实例?

谢谢!

【问题讨论】:

  • 了解Span&lt;T&gt; 何时无法继续工作而您必须切换到Memory&lt;T&gt; 非常重要。顺便说一句,非常丑陋的抽象泄漏,这并没有使 C# 变得更好。有一个关于它的视频:channel9.msdn.com/Events/Connect/2017/T125
  • @HansPassant 我知道我不能在闭包中使用Span&lt;T&gt;,这对我来说很好,因为无论如何我已经打算使用指针来提高它们的性能,我只是在寻找一种方法固定一个ref T 值(或Span&lt;T&gt;),这样就可以安全地使用指向它的指针,而不必担心GC 会移动东西。有什么方法可以实现(可能通过GCHandle 或其他技巧)?我的意思是,出于同样的性能原因,我仍然更愿意从 Span&lt;T&gt; 获取指针并在跨度中的实际设置器上使用它,即使没有关闭问题。
  • @HansPassant 我已经打开了另一个有关使用 IL 方法固定 ref T 变量的相关问题,这是链接:stackoverflow.com/questions/48095145/… 你介意看一下吗?谢谢!

标签: c# .net cil c#-7.2


【解决方案1】:

我想我可能已经找到了使用Unsafe 类中的一种新方法的解决方法,我已经对其进行了测试,到目前为止它似乎有效。这里是:

public static unsafe void Fill<T>(this Span<T> span, [NotNull] Func<T> provider) where T : struct
{
    int
        cores = Environment.ProcessorCount,
        batch = span.Length / cores,
        mod = span.Length % cores,
        size = Unsafe.SizeOf<T>();
    ref T r0 = ref span.DangerousGetPinnableReference();
    fixed (byte* p0 = &Unsafe.As<T, byte>(ref r0))
    {
        byte* p = p0;
        Parallel.For(0, cores, i =>
        {
            byte* pi = p + i * batch * size;
            for (int j = 0; j < batch; j++, pi += size)
                Unsafe.Write(pi, provider());
        }).AssertCompleted();

        // Remaining values
        if (mod < 1) return;
        for (int i = span.Length - mod; i < span.Length; i++)
            Unsafe.Write(p + i * size, provider());
    }
}

基本上,由于我无法固定ref T 值,因此我尝试使用Unsafe.As&lt;T, byte&gt;(ref T value) 获取ref byte 变量并改为固定该变量。由于它指向同一个地址,我认为(希望)它被固定得很好,它应该在 IL 中做同样的事情。

【讨论】:

  • 你是对的,固定变量不依赖于它的类型,只依赖于地址。事实上,CIL 甚至支持 void&amp; pinned 并且它可以毫无问题地工作(我用它来固定 typedref)。我猜 Unsafe.As 只是重新解释它收到的地址,所以这应该可以工作。
  • 太棒了,找到一个实际上不需要 IL 实现的解决方案真是太好了,这只是更容易使用(并在 .NET Standard 2.0 库中提供)。谢谢您的帮助! ?
猜你喜欢
  • 2018-06-14
  • 2019-11-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
  • 2018-07-16
  • 2012-03-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多