nuget server logo nuget api documents
↑

API Docs / Microsoft.VisualBasic.Runtime / SimdEngine

SimdEngine

Full name Microsoft.VisualBasic.Math.SIMD.SimdEngine Assembly Microsoft.VisualBasic.Runtime Members 41

通用的逐元素向量化内核。

00 Remarks

这个类型是整个 SIMD 模块的计算主干,所有运算都以 Vector 为基础:它由 JIT 自动映射到 SSE2/AVX/AVX2(x86) 或者 Advanced SIMD(ARM64), 因而对“按位宽自动取最大向量”这件事是跨平台正确的。

长度契约:这些逐元素函数假设 v1 与 v2 长度一致, 不额外做长度校验(与旧版实现保持一致,避免在热路径上引入分支)。 相邻数组越界会由运行时自身的边界检查捕获。

尾部处理:当长度不能被向量宽度整除时,会使用“最后一个向量块与 末尾重叠”的技巧一次性覆盖剩余元素,因此循环体内不需要任何标量尾部补算 (这也是旧实现里 Mod 取余 + 标量循环的主要性能损失点之一)。

零长度:传入长度为 0 的数组会安全地返回 Array.Empty(), 修正了旧实现中 New Double(-1) {} 导致的越界异常。

01 Syntax

Microsoft.VisualBasic.Math.SIMD.SimdEngine

02 Methods

NameOverloadsSummary
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

method NewArray #
NewArray``1(Int32)

分配结果数组,跳过运行时的零初始化。

Remarks

New T(n) {} 会让运行时先把整块内存清零,再被我们的循环完整覆盖一次。 对于大数组(例如 1e7 个 Double 就是 80MB)这次多余的清零会占用 与真正计算同量级的内存带宽,是实测中最主要的开销之一。 GC.AllocateUninitializedArray() 可以跳过这一步。

安全性:只有当返回数组的每一个元素都会被显式写入时才可以使用这个方法。 本模块中所有调用点都满足该前提(向量块 + 重叠末块,或者完整的标量循环, 覆盖了 [0, length) 的全部下标);对于元素类型包含对象引用的情况, 运行时本身也会强制清零,因此不存在抛出未初始化引用的问题。

method CanVectorize #
CanVectorize``1(Int32)

判断长度 len 的数据是否可以走到量化路径。

method ScalarLane #
ScalarLane``1(VectorBinaryOp(Of ``0), ``0, ``0)

当无法走向量化路径时,使用“单通道向量”完成一次类型安全的逐元素运算。

Remarks

向量在这里只是一个栈上的 struct,不会产生堆分配;这条路径只在 SIMDConfiguration.disable 或者数据长度小于向量宽度时使用。

method Add #
Add``1(``0(), ``0())

逐元素相加:out(i) = v1(i) + v2(i)

method Subtract #
Subtract``1(``0(), ``0())

逐元素相减:out(i) = v1(i) - v2(i)

method Multiply #
Multiply``1(``0(), ``0())

逐元素相乘:out(i) = v1(i) * v2(i)

method Min #
Min``1(``0(), ``0())

逐元素取最小值:out(i) = Min(v1(i), v2(i))

Remarks

语义等同于 Vector.Min()。

method Max #
Max``1(``0(), ``0())

逐元素取最大值:out(i) = Max(v1(i), v2(i))

method Compute #
Compute``1(``0(), ``0(), VectorBinaryOp(Of ``0))

以委托形式给出的逐元素二元运算通用实现。

Remarks

因为引入了间接调用,这个方法不适合放在最热点的主干上; SimdEngine.Add() 等高频算子都是手写展开的循环。

method AddScalar #
AddScalar``1(``0(), ``0)

向量加标量:out(i) = v(i) + scalar

method SubtractScalar #
SubtractScalar``1(``0(), ``0)

向量减标量:out(i) = v(i) - scalar

method ScalarSubtract #
ScalarSubtract``1(``0, ``0())

标量减向量:out(i) = scalar - v(i)

method MultiplyScalar #
MultiplyScalar``1(``0, ``0())

标量乘向量:out(i) = scalar * v(i)

method AddInPlace #
AddInPlace``1(``0(), ``0())

就地累加:v(i) += operand(i)

method SubtractInPlace #
SubtractInPlace``1(``0(), ``0())

就地相减:v(i) -= operand(i)

method MultiplyInPlace #
MultiplyInPlace``1(``0(), ``0())

就地累乘:v(i) *= operand(i)

method AddScalarInPlace #
AddScalarInPlace``1(``0(), ``0)

就地累加标量:v(i) += scalar

method MultiplyScalarInPlace #
MultiplyScalarInPlace``1(``0(), ``0)

就地累乘标量:v(i) *= scalar

method Divide #
Divide(Double(), Double())

逐元素相除(Double):out(i) = v1(i) / v2(i)

method Divide overload 2 #
Divide(Single(), Single())

逐元素相除(Single):out(i) = v1(i) / v2(i)

method DivideZeroSafe #
DivideZeroSafe(Double(), Double())

带“分子为零则结果为零”语义的逐元素除法。

Remarks

这个语义来自旧版的 Divide.f64_op_divide_f64 实现:当分子为 0 时 直接输出 0,从而避免 0 / 0 产生 Double。 向量化实现先用掩码找出分子为 0 的通道,再用 Vector.ConditionalSelect() 把这些通道覆盖为 0。

method DivideScalar #
DivideScalar(Double(), Double)

向量除以标量(Double):out(i) = v(i) / scalar

method DivideScalar overload 2 #
DivideScalar(Single(), Single)

向量除以标量(Single)

method ScalarDivide #
ScalarDivide(Double, Double())

标量除以向量(Double):out(i) = scalar / v(i)

method ScalarDivide overload 2 #
ScalarDivide(Single, Single())

标量除以向量(Single)

method CanVectorize #
CanVectorize(Int32)
method Add #
Add(T(), T())
method Subtract #
Subtract(T(), T())
method Multiply #
Multiply(T(), T())
method Min #
Min(T(), T())
method Max #
Max(T(), T())
method Compute #
Compute(T(), T(), SimdEngine+VectorBinaryOp(Of T))
method AddScalar #
AddScalar(T(), T)
method SubtractScalar #
SubtractScalar(T(), T)
method ScalarSubtract #
ScalarSubtract(T, T())
method MultiplyScalar #
MultiplyScalar(T, T())
method AddInPlace #
AddInPlace(T(), T())
method SubtractInPlace #
SubtractInPlace(T(), T())
method MultiplyInPlace #
MultiplyInPlace(T(), T())
method AddScalarInPlace #
AddScalarInPlace(T(), T)
method MultiplyScalarInPlace #
MultiplyScalarInPlace(T(), T)