SIMD向量-复数?

时间:2018-12-07 22:51:09

标签: c# .net f#

我想在.NET Core中使用System.Numerics.Vector名称空间,但是遇到了不支持复数向量的问题。就目前而言,Vector类型将适用于任何原始类型,从字节到双精度。我不是行业程序员,因此我可能不了解一些低级/概念性的东西,但是为什么不支持复数呢?据我所知,也许唯一的问题是Complex类型是托管结构。我不仅可以将Register类型扩展为包括[FieldOffset(0)] internal Complex complex_0并以此为基础构建新功能吗?

我愿意亲自进行类型扩展,但想问一问为什么没有将其放在首位,因为似乎很多信号处理工作将从复数向量中受益匪浅和SIMD。

1 个答案:

答案 0 :(得分:0)

System.Numerics.Vector对于您的目标没有用。您可以依赖System.Runtime.Intrinsics命名空间中的Vector256类。必要的说明在System.Runtime.Intrinsics.X86.Avx类中公开。 (Vector128在这里几乎没有什么帮助,因为它代表一个复数。我无法找到任何能帮助我将复数重新解释为Vector128而无需复制的魔术,否则我们将能够增强内置运算符为Complex类提供)

所以,想法是

  1. 将复杂数据表示为跨度
  2. 在这些Span上实现了算术运算符
  3. 内部,将这些跨度转换为Vector256
  4. 并使用Avx操作

请参见下面的示例代码:

public static class ComplexAvx
    {
        public static Span<Complex> Add(ReadOnlySpan<Complex> left, ReadOnlySpan<Complex> right)
        {
            var result = new Complex[Math.Min(left.Length, right.Length)].AsSpan();
            var vectorRes = MemoryMarshal.Cast<Complex, Vector256<double>>(result);
            var vectorLeft = MemoryMarshal.Cast<Complex, Vector256<double>>(left);
            var vectorRight = MemoryMarshal.Cast<Complex, Vector256<double>>(right);
            for (int i = 0; i < vectorRes.Length; i++)
                vectorRes[i] = Avx.Add(vectorLeft[i], vectorRight[i]);

            for (int i = 2 * vectorRes.Length; i < result.Length; i++)
                result[i] = left[i] + right[i];
            return result;
        }

        public static Span<Complex> Subtract(ReadOnlySpan<Complex> left, ReadOnlySpan<Complex> right)
        {
            var result = new Complex[Math.Min(left.Length, right.Length)].AsSpan();
            var vectorRes = MemoryMarshal.Cast<Complex, Vector256<double>>(result);
            var vectorLeft = MemoryMarshal.Cast<Complex, Vector256<double>>(left);
            var vectorRight = MemoryMarshal.Cast<Complex, Vector256<double>>(right);
            for (int i = 0; i < vectorRes.Length; i++)
                vectorRes[i] = Avx.Subtract(vectorLeft[i], vectorRight[i]);

            for (int i = 2 * vectorRes.Length; i < result.Length; i++)
                result[i] = left[i] - right[i];
            return result;
        }

        public static Span<Complex> Multiply(ReadOnlySpan<Complex> left, ReadOnlySpan<Complex> right)
        {
            var result = new Complex[Math.Min(left.Length, right.Length)].AsSpan();
            var vectorRes = MemoryMarshal.Cast<Complex, Vector256<double>>(result);
            var vectorLeft = MemoryMarshal.Cast<Complex, Vector256<double>>(left);
            var vectorRight = MemoryMarshal.Cast<Complex, Vector256<double>>(right);
            for (int i = 0; i < vectorRes.Length; i++)
            {
                var l = vectorLeft[i];  
                var r = vectorRight[i]; 
                vectorRes[i] = Avx.HorizontalAdd(
                    Avx.Multiply(
                        Avx.Multiply(l, r), 
                        Vector256.Create(1.0, -1.0, 1.0, -1.0)), 
                    Avx.Multiply(
                        l, 
                        Avx.Permute(r, 0b0101)
                        )); 
            }
            for (int i = 2 * vectorRes.Length; i < result.Length; i++)
                result[i] = left[i] * right[i];
            return result;
        }

    }

此代码执行5个AVX操作以将两个复数相乘,而使用System.Numerics.Complex.Multiply将消耗8个乘法和4个加法。 (当然,代码应检查Avx.IsEnabled-为简便起见,跳过了该步骤) 我没有为基准测试感到烦恼,因此不确定在现代CPU上会产生多少(如果有的话)收益。 Avx512将允许使用相同的5条指令一次处理4个复合体。但这尚待CLR支持。