Proposal: NTuple into the STL and/or runtime? #113387
Unanswered
HyperSphereStudio
asked this question in
Ideas
Replies: 2 comments
-
I optimized the ntuple library to make it obvious to the JIT where to inline function calls... Here are the results from a (very) simple and non rigourous benchmark
The results are pretty neck and neck with the current Vector2, Vector3 & Vector4 on release mode. Without release mode, the ntuples are about 8x slower (to be expected) Here are what some of the functions from the test look like: [Benchmark]
public Vec<float, NTuple4<float>> Add_Mult_Length_NTUPLE4f() {
var w = new Vec<float, NTuple4<float>>(new NTuple4<float>(3, 8, 7, 12));
var k = new Vec<float, NTuple4<float>>(new NTuple4<float>(65, 2, 8, -5));
var c = w + k;
for (int i = 0; i < 10; i++) {
c += w + k * i;
w += i * c.Length;
k -= w.Length;
}
return c;
}
[Benchmark]
public Vector4 Add_Mult_Length_VECTOR4f() {
var w = new Vector4(3, 8, 7, 12);
var k = new Vector4(65, 2, 8, -5);
var c = w + k;
for (int i = 0; i < 10; i++) {
c += w + k * i;
w += new Vector4(i * c.Length());
k -= new Vector4(w.Length());
}
return c;
}
[Benchmark]
public float Dot_Product_NTUPLE3f() {
var w = new Vec<float, NTuple3<float>>(new NTuple3<float>(3, 8, 7));
var k = new Vec<float, NTuple3<float>>(new NTuple3<float>(65, 2, 8));
float s = 0;
for (var i = 0; i < 10; i++) {
s += Vec.Dot(w, k);
}
return s;
}
[Benchmark]
public float Dot_Product_VECTOR3f() {
var w = new Vector3(3, 8, 7);
var k = new Vector3(65, 2, 8);
float s = 0;
for (var i = 0; i < 10; i++) {
s += Vector3.Dot(w, k);
}
return s;
} Here is what the hyperdimensional vec class looks like now: public static class Vec {
public static T Dot<T, NT>(Vec<T, NT> a, Vec<T, NT> b) where T : IFloatingPointIeee754<T> where NT : NTuple<T, NT> {
return NT.BinaryOperation<Multiply<T>>(a.Data, b.Data).Sum<T, NT>();
}
/*
* Use Item#s rather than X, Y & Z for less JIT optimization
*/
public static Vec<T, NTuple3<T>> Cross<T>(Vec<T, NTuple3<T>> a, Vec<T, NTuple3<T>> b)
where T : IFloatingPointIeee754<T> =>
new(new NTuple3<T>(
a.Data.Item2 * b.Data.Item3 - a.Data.Item3 * b.Data.Item2,
a.Data.Item3 * b.Data.Item1 - a.Data.Item1 * b.Data.Item3,
a.Data.Item1 * b.Data.Item2 - a.Data.Item2 * b.Data.Item1));
}
public struct Vec<T, NT> where NT : NTuple<T, NT> where T : IFloatingPointIeee754<T> {
public NT Data;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vec(T v)
{
Data = NT.Uninitialized;
NTuple<T, NT>.AsSpan(ref Data).Fill(v);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vec(NT data) => Data = data;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vec(Vec<T, NT> v) => Data = v.Data;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> Unit(int n)
{
var w = NT.Zeroed;
w[n] = T.One;
return new(w);
}
public static Vec<T, NT> UnitX { get; } = NT.Zeroed.Count > 0 ? Unit(0) : default;
public static Vec<T, NT> UnitY { get; } = NT.Zeroed.Count > 0 ? Unit(1) : default;
public static Vec<T, NT> UnitZ { get; } = NT.Zeroed.Count > 0 ? Unit(2) : default;
public static Vec<T, NT> Zero { get; } = new(T.Zero);
public static Span<T> AsSpan(ref Vec<T, NT> v) => NTuple<T, NT>.AsSpan(ref v.Data);
[IgnoreDataMember] public T LengthSquared => NT.Reduce<EuclidLengthSquared<T>>(T.AdditiveIdentity, Data);
[IgnoreDataMember] public T Length => T.Sqrt(LengthSquared);
[IgnoreDataMember] public T ReciprocalSqrt => T.ReciprocalSqrtEstimate(LengthSquared);
[IgnoreDataMember]
public T X {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Data[0];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Data[0] = value;
}
[IgnoreDataMember]
public T Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Data[1];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Data[1] = value;
}
[IgnoreDataMember]
public T Z {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Data[2];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Data[2] = value;
}
[IgnoreDataMember]
public int Dimension
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Data.Count;
}
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Data[index];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => Data[index] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator +(Vec<T, NT> a, Vec<T, NT> b) =>
new(NTuple.BinaryElementwiseAdd<T, NT>(a.Data, b.Data));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator -(Vec<T, NT> v) => v * -T.One;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator -(Vec<T, NT> a, Vec<T, NT> b) => a + -b;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator +(Vec<T, NT> a, T b) => new(NTuple.ScalarAdd(a.Data, b));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator +(T a, Vec<T, NT> b) => new(NTuple.ScalarAdd(b.Data, a));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator -(T a, Vec<T, NT> b) => -b + a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator -(Vec<T, NT> a, T b) => a + -b;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator *(T a, Vec<T, NT> b) => new(NTuple.ScalarMultiply(b.Data, a));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator *(Vec<T, NT> a, T b) => b * a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vec<T, NT> operator /(Vec<T, NT> a, T b) => a * T.ReciprocalEstimate(b);
public override string? ToString() => Data.ToString();
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Note This proposal is inspired by the Julia Programming Language
One usage of the NTuple is for N dimensional abstraction. I have noticed in the C# STL the usage of Vector2, Vector3 etc. While this does work, a lot of abstraction power can come from writing code that can take an arbitrary ntuple size (using generics for performance).
Benefits:
Downsides:
I believe that the addition of the NTuples into the .NET ecosystem can increase the abstractional power of C# and allow for far more advanced numerical algorithms that would traditionally be very annoying to implement and not necessarily COMPLETE. Actual implementation into the STL could simply a library similar to below, but if the runtime implemented NTuples, the approach could be simplified (add in SIMD?).
I was inspired to write the NTuple library to create a parallel to [GeometryBasics]. (https://github.com/JuliaGeometry/GeometryBasics.jl/tree/master) library
Performance Note: This library produces a lot of garbage because of the closures at the moment... work will needed in order to make the speed (which it should be!) equivalent to manual versions.
The NTuple Library
To perform efficient field lookup using the element reference function I made a rule that the static allocated tuples should have a sequential layout with no packing, maybe this should be improved?
Here is the base interface for the NTuple I made. It mostly just implements the list interface into all NTuple variants
Here are the first three ntuple's (I made up to seven)
This breaks the memory layout of the static tuples but may still be useful for super high dimensional data (the classical approach to NTuples):
P.S You may have noticed I have been trying to do practices that make C# a little more julian (generics wise) ;).
Beta Was this translation helpful? Give feedback.
All reactions