通用的逐元素向量化内核。
SimdEngine
00 Remarks
这个类型是整个 SIMD 模块的计算主干,所有运算都以 Vector 为基础:它由 JIT 自动映射到 SSE2/AVX/AVX2(x86) 或者 Advanced SIMD(ARM64), 因而对“按位宽自动取最大向量”这件事是跨平台正确的。
长度契约:这些逐元素函数假设 v1 与 v2 长度一致, 不额外做长度校验(与旧版实现保持一致,避免在热路径上引入分支)。 相邻数组越界会由运行时自身的边界检查捕获。
尾部处理:当长度不能被向量宽度整除时,会使用“最后一个向量块与 末尾重叠”的技巧一次性覆盖剩余元素,因此循环体内不需要任何标量尾部补算 (这也是旧实现里 Mod 取余 + 标量循环的主要性能损失点之一)。
零长度:传入长度为 0 的数组会安全地返回 Array.Empty(), 修正了旧实现中 New Double(-1) {} 导致的越界异常。
01 Syntax
02 Methods
| Name | Overloads | Summary |
|---|---|---|
| NewArray | 1 | 分配结果数组,跳过运行时的零初始化。 |
| CanVectorize | 1 | 判断长度 len 的数据是否可以走到量化路径。 |
| ScalarLane | 1 | 当无法走向量化路径时,使用“单通道向量”完成一次类型安全的逐元素运算。 |
| Add | 1 | 逐元素相加:out(i) = v1(i) + v2(i) |
| Subtract | 1 | 逐元素相减:out(i) = v1(i) - v2(i) |
| Multiply | 1 | 逐元素相乘:out(i) = v1(i) * v2(i) |
| Min | 1 | 逐元素取最小值:out(i) = Min(v1(i), v2(i)) |
| Max | 1 | 逐元素取最大值:out(i) = Max(v1(i), v2(i)) |
| Compute | 1 | 以委托形式给出的逐元素二元运算通用实现。 |
| AddScalar | 1 | 向量加标量:out(i) = v(i) + scalar |
| SubtractScalar | 1 | 向量减标量:out(i) = v(i) - scalar |
| ScalarSubtract | 1 | 标量减向量:out(i) = scalar - v(i) |
| MultiplyScalar | 1 | 标量乘向量:out(i) = scalar * v(i) |
| AddInPlace | 1 | 就地累加:v(i) += operand(i) |
| SubtractInPlace | 1 | 就地相减:v(i) -= operand(i) |
| MultiplyInPlace | 1 | 就地累乘:v(i) *= operand(i) |
| AddScalarInPlace | 1 | 就地累加标量:v(i) += scalar |
| MultiplyScalarInPlace | 1 | 就地累乘标量:v(i) *= scalar |
| Divide | 2 | 逐元素相除(Double):out(i) = v1(i) / v2(i) |
| DivideZeroSafe | 1 | 带“分子为零则结果为零”语义的逐元素除法。 |
| DivideScalar | 2 | 向量除以标量(Double):out(i) = v(i) / scalar |
| ScalarDivide | 2 | 标量除以向量(Double):out(i) = scalar / v(i) |
| CanVectorize | 1 | |
| Add | 1 | |
| Subtract | 1 | |
| Multiply | 1 | |
| Min | 1 | |
| Max | 1 | |
| Compute | 1 | |
| AddScalar | 1 | |
| SubtractScalar | 1 | |
| ScalarSubtract | 1 | |
| MultiplyScalar | 1 | |
| AddInPlace | 1 | |
| SubtractInPlace | 1 | |
| MultiplyInPlace | 1 | |
| AddScalarInPlace | 1 | |
| MultiplyScalarInPlace | 1 |
03 Members
Int32)分配结果数组,跳过运行时的零初始化。
New T(n) {} 会让运行时先把整块内存清零,再被我们的循环完整覆盖一次。 对于大数组(例如 1e7 个 Double 就是 80MB)这次多余的清零会占用 与真正计算同量级的内存带宽,是实测中最主要的开销之一。 GC.AllocateUninitializedArray() 可以跳过这一步。
安全性:只有当返回数组的每一个元素都会被显式写入时才可以使用这个方法。 本模块中所有调用点都满足该前提(向量块 + 重叠末块,或者完整的标量循环, 覆盖了 [0, length) 的全部下标);对于元素类型包含对象引用的情况, 运行时本身也会强制清零,因此不存在抛出未初始化引用的问题。
Int32)判断长度 len 的数据是否可以走到量化路径。
当无法走向量化路径时,使用“单通道向量”完成一次类型安全的逐元素运算。
向量在这里只是一个栈上的 struct,不会产生堆分配;这条路径只在 SIMDConfiguration.disable 或者数据长度小于向量宽度时使用。
``0(), ``0())逐元素相加:out(i) = v1(i) + v2(i)
``0(), ``0())逐元素相减:out(i) = v1(i) - v2(i)
``0(), ``0())逐元素相乘:out(i) = v1(i) * v2(i)
``0(), ``0())逐元素取最小值:out(i) = Min(v1(i), v2(i))
语义等同于 Vector.Min()。
``0(), ``0())逐元素取最大值:out(i) = Max(v1(i), v2(i))
以委托形式给出的逐元素二元运算通用实现。
因为引入了间接调用,这个方法不适合放在最热点的主干上; SimdEngine.Add() 等高频算子都是手写展开的循环。
``0(), ``0)向量加标量:out(i) = v(i) + scalar
``0(), ``0)向量减标量:out(i) = v(i) - scalar
``0, ``0())标量减向量:out(i) = scalar - v(i)
``0, ``0())标量乘向量:out(i) = scalar * v(i)
``0(), ``0())就地累加:v(i) += operand(i)
``0(), ``0())就地相减:v(i) -= operand(i)
``0(), ``0())就地累乘:v(i) *= operand(i)
``0(), ``0)就地累加标量:v(i) += scalar
``0(), ``0)就地累乘标量:v(i) *= scalar
Double(), Double())逐元素相除(Double):out(i) = v1(i) / v2(i)
Single(), Single())逐元素相除(Single):out(i) = v1(i) / v2(i)
Double(), Double())带“分子为零则结果为零”语义的逐元素除法。
这个语义来自旧版的 Divide.f64_op_divide_f64 实现:当分子为 0 时 直接输出 0,从而避免 0 / 0 产生 Double。 向量化实现先用掩码找出分子为 0 的通道,再用 Vector.ConditionalSelect() 把这些通道覆盖为 0。
Double(), Double)向量除以标量(Double):out(i) = v(i) / scalar
Single(), Single)向量除以标量(Single)
Double, Double())标量除以向量(Double):out(i) = scalar / v(i)
Single, Single())标量除以向量(Single)
Int32)T(), T())T(), T())T(), T())T(), T())T(), T())T(), T(), SimdEngine+VectorBinaryOp(Of T))T(), T)T(), T)T, T())T, T())T(), T())T(), T())T(), T())T(), T)T(), T)