Is it possible to write zero cost wrappers around doubles, for dimensional annotation purposes? #74475
-
My attempts at wrapping doubles in dimensionally typed wrappers are O(5x) slower than raw doubles. I have previously asked whether this could be improved at https://stackoverflow.com/questions/78696148/wrap-doubles-without-overhead-in-csharp (without genetics, which I use for the actual dimensional units, but even then it's 4x slower) Is there anything I could do to speed things up? If not, would it be a reasonable feature request to ask for improvements here? My previous attempt (copied from the stackoverflow post where it was recommended I post here), with BenchmarkDotNet code that exhibits a 4x slowdown: using System;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using NUnit.Framework;
namespace MyBenchmark;
public readonly struct MyDouble
{
public readonly double Value;
public MyDouble(double value)
{
Value = value;
}
public static MyDouble operator +(MyDouble self, MyDouble value)
{
return new MyDouble(self.Value + value.Value);
}
public static MyDouble operator *(MyDouble self, double value)
{
return new MyDouble(self.Value * value);
}
}
public class DoubleBenchmark
{
private const int N = 1_000_000;
private readonly double[] _data;
private readonly double[] _workspace;
private readonly MyDouble[] _typedData;
private readonly MyDouble[] _typedWorkspace;
public DoubleBenchmark()
{
_data= new double[N];
_workspace = new double[N];
_typedWorkspace = new MyDouble[N];
var rand = new Random(0);
for (var i = 0; i < N; i++)
{
_data[i] = rand.NextDouble();
}
_typedData = _data.Select(x => new MyDouble(x)).ToArray();
}
[Benchmark]
public MyDouble TypedDoubles()
{
var d = new MyDouble(0);
for (var i = 0; i < N; i++)
{
d += _typedData[i] * 3;
_typedWorkspace[i] = d * 2 + new MyDouble(1);
}
var e = new MyDouble(0);
for (var i = 0; i < N; i++)
{
e += _typedWorkspace[i];
}
return e;
}
[Benchmark]
public MyDouble ManuallyInlinedTypedDoubles()
{
var d = new MyDouble(0);
for (var i = 0; i < N; i++)
{
d = new MyDouble(d.Value + _typedData[i].Value * 3);
_typedWorkspace[i] = new MyDouble(d.Value * 2 + 1);
}
var e = new MyDouble(0);
for (var i = 0; i < N; i++)
{
e = new MyDouble(e.Value + _typedWorkspace[i].Value);
}
return e;
}
[Benchmark]
public double Doubles()
{
var d = 0d;
for (var i = 0; i < N; i++)
{
d += _data[i] * 3;
_workspace[i] = d * 2 + 1;
}
var e = 0d;
for (var i = 0; i < N; i++)
{
e += _workspace[i];
}
return e;
}
[Test, Explicit]
public static void RunBenchmarks()
{
BenchmarkRunner.Run<DoubleBenchmark>();
}
[Test, Explicit]
public static void EqualResults()
{
var a = new DoubleBenchmark();
Console.WriteLine((a.TypedDoubles().Value, a.Doubles(), a.ManuallyInlinedTypedDoubles().Value));
if (Math.Abs(a.TypedDoubles().Value - a.Doubles()) > 0 ||
Math.Abs(a.TypedDoubles().Value - a.ManuallyInlinedTypedDoubles().Value) > 1e-16)
{
throw new Exception("Different results");
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
This question lacks necessary information. What approach are you currently taking? Where is it slow? What part of that slowness is due to Roslyn? |
Beta Was this translation helpful? Give feedback.
Zero-cost wrappers in general are a complex topic and there is no definitive answer because it is often dependent on the ABI (application binary interface) that exists for the underlying C runtime.
In general,
struct S { T x; }
andT
are not equivalent at the ABI level and while they often do get passed around the same, there are many cases that they may not and that can lead to deficiencies.It used to be that structs with single-float fields could not be "promoted" (an optimization) due to such ABI differences, but I resolved that in the .NET 8 timeframe: dotnet/runtime#84627. -- This impacted all single-field floating-point wrapper structs, regardless of whether inlining happened or not.