代码之家  ›  专栏  ›  技术社区  ›  Sergio0694

如何固定泛型Span实例以使用Parallel处理它。对于

  •  9
  • Sergio0694  · 技术社区  · 8 年前

    我正在使用新的 Span<T> 类型,我很难找到一种方法来正确地固定泛型实例,以便能够使用并行代码来处理它。

    例如,考虑以下扩展方法:

    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();
        }
    }
    

    这里我只想填写一个输入 跨度(<);T> 使用一些值提供程序,由于这些向量可能非常大,我想并行填充它们。

    这只是一个例子,所以即使在这里使用并行代码 完全有必要,这个问题仍然存在,因为我需要使用并行 无论如何,早晚都要重新编码。

    现在,这个代码 确实有效 ,但由于我从未实际固定输入范围,并且考虑到它很可能指向一些托管 T[] 向量,它可以随时被GC移动,我想我很幸运在我的测试中看到它工作得很好。

    所以,我的问题是:

    有没有办法锁定一个通用的 跨度(<);T> 实例并获取一个简单的 void* 指向它的指针,以便我可以在闭包中传递它来处理 跨度(<);T> 并行代码中的实例?

    谢谢

    1 回复  |  直到 8 年前
        1
  •  4
  •   Sergio0694    8 年前

    我想我可能已经找到了使用 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 价值,我试着 ref byte 变量使用 Unsafe.As<T, byte>(ref T value) 而是把那个钉上。由于它指向相同的地址,我认为(希望)它被钉住了,它应该在IL中做同样的事情。