我想出了一个解决方案,它满足了我的大多数要求(看起来)。它的灵感来自
TheInnerLight's ideas
(包装
Vector<'T>
),还添加了一个包装器(称为
ScalarField
)用于基础数组数据类型。通过这种方式,我们可以跟踪单元,而下面我们只处理原始数据,并且可以使用不知道单元的
System.Numerics.Vector
API。
一个简化的、简单的、快速的、肮脏的实现将如下所示:
// units-aware wrapper for System.Numerics.Vector<'T>
type PackedScalars<[<Measure>] 'm> = struct
val public Data: Vector<float>
new (d: Vector<float>) = {Data = d}
static member inline (*) (u: PackedScalars<'m1>, v: PackedScalars<'m2>) = u.Data * v.Data |> PackedScalars<'m1*'m2>
end
// unit-ware type, wrapping a raw array for easy stream processing
type ScalarField<[<Measure>] 'm> = struct
val public Data: float[]
member self.Item with inline get i = LanguagePrimitives.FloatWithMeasure<'m> self.Data.[i]
and inline set i (v: float<'m>) = self.Data.[i] <- (float v)
member self.Packed
with inline get i = Vector<float>(self.Data, i) |> PackedScalars<'m>
and inline set i (v: PackedScalars<'m>) = v.Data.CopyTo(self.Data, i)
new (d: float[]) = {Data = d}
new (count: int) = {Data = Array.zeroCreate count}
end
现在,我们可以使用这两种数据结构在
相当地
优雅、高效的方式:
let xs = Array.init (simdWidth * 10) float |> ScalarField<m>
let mutable rs = Array.zeroCreate (xs.Data |> Array.length) |> ScalarField<m^2>
let chunks = (xs.Data |> Array.length) / simdWidth
for i = 0 to chunks - 1 do
let j = i * simdWidth
let v = xs.Packed(j) // PackedScalars<m>
let u = v * v // PackedScalars<m^2>
rs.Packed(j) <- u
最重要的是,重新实现单元感知的常规数组操作可能很有用
标量字段
包装,例如。
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module ScalarField =
let map f (sf: ScalarField<_>) =
let mutable res = Array.zeroCreate sf.Data.Length |> ScalarField
for i = 0 to sf.Data.Length do
res.[i] <- f sf.[i]
res
等
缺点:对于基础数字类型而言不是泛型的(
float
),因为没有通用的替代品
floatWithMeasure
为了使其通用,我们必须实现第三个包装器
Scalar
还包装了基础图元:
type Scalar<'a, [<Measure>] 'm> = struct
val public Data: 'a
new (d: 'a) = {Data = d}
end
type PackedScalars<'a, [<Measure>] 'm
when 'a: (new: unit -> 'a)
and 'a: struct
and 'a :> System.ValueType> = struct
val public Data: Vector<'a>
new (d: Vector<'a>) = {Data = d}
static member inline (*) (u: PackedScalars<'a, 'm1>, v: PackedScalars<'a, 'm2>) = u.Data * v.Data |> PackedScalars<'a, 'm1*'m2>
end
type ScalarField<'a, [<Measure>] 'm
when 'a: (new: unit -> 'a)
and 'a: struct
and 'a :> System.ValueType> = struct
val public Data: 'a[]
member self.Item with inline get i = Scalar<'a, 'm>(self.Data.[i])
and inline set i (v: Scalar<'a,'m>) = self.Data.[i] <- v.Data
member self.Packed
with inline get i = Vector<'a>(self.Data, i) |> PackedScalars<_,'m>
and inline set i (v: PackedScalars<_,'m>) = v.Data.CopyTo(self.Data, i)
new (d:'a[]) = {Data = d}
new (count: int) = {Data = Array.zeroCreate count}
end
…这意味着我们基本上不是通过使用诸如
float<'m>
,但仅通过具有辅助类型/单元参数的包装类型。
不过,我还是希望有人能想出更好的主意