Closed dzmitry-lahoda closed 5 years ago
I replicated result on my own, so thanks. I want to share with my English speaking colleagues to prove we can go that way (also I still need to run stuff in Unity).
May be you have ideas how to improve next?
using System;
using System.Numerics.Primitives;
using System.Runtime.CompilerServices;
using BenchmarkDotNet;
using BenchmarkDotNet.Attributes;
namespace benchmarks
{
#if NETCOREAPP2_2
[CoreJob]
#elif NET472
[ClrJob]
#endif
[MemoryDiagnoser]
public class Wrapper
{
readonly struct MyFloat
{
public readonly float a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MyFloat(float a) => this.a = a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MyFloat(int a) => this.a = a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MyFloat operator +(MyFloat b1, MyFloat b2) => new MyFloat(b1.a + b2.a);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MyFloat operator *(MyFloat b1, MyFloat b2) => new MyFloat(b1.a * b2.a);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator MyFloat(float f) => new MyFloat(f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator MyFloat(int f) => new MyFloat(f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator double(MyFloat f) => f.a;
}
[Benchmark]
public void FloatIntoDouble()
{
double acc = 0;
for (var i = 0; i < N; i++)
acc += distance(i, i / 2 + 1);
}
[Benchmark]
public void Float()
{
float acc = 0;
for (var i = 0; i < N; i++)
acc += distance(i, i / 2 + 1);
}
float distance(float a, float b) => a * a + b * b;
[Benchmark]
public void FloatWrapperIntoDouble()
{
double acc = 0;
for (var i = 0; i < N; i++)
acc += mydistance(i, i / 2 + 1);
}
[Benchmark]
public void FloatWrapper()
{
MyFloat acc = 0;
for (var i = 0; i < N; i++)
acc += mydistance(i, i / 2 + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
MyFloat mydistance(MyFloat a, MyFloat b) => a * a + b * b;
[Params(1000)]
public int N;
}
}
Method | N | Mean | Error | StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
----------------------- |----- |----------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
FloatIntoDouble | 1000 | 1.797 us | 0.0360 us | 0.0759 us | - | - | - | - |
Float | 1000 | 1.479 us | 0.0202 us | 0.0169 us | - | - | - | - |
FloatWrapperIntoDouble | 1000 | 9.490 us | 0.1676 us | 0.1567 us | - | - | - | - |
FloatWrapper | 1000 | 10.782 us | 0.2148 us | 0.3647 us | - | - | - | - |
May be you have ideas how to improve next?
I don't know exactly but the following issue could help you, maybe...
The workaround is:
[Benchmark]
public void FloatWrapperUnsafe()
{
MyFloat acc = 0;
for (var i = 0; i < N; i++)
acc += myunsafedistance(i, i / 2 + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
MyFloat myunsafedistance(MyFloat a, MyFloat b)
{
ref var a1 = ref System.Runtime.CompilerServices.Unsafe.As<MyFloat, float>(ref a);
ref var b1 = ref System.Runtime.CompilerServices.Unsafe.As<MyFloat, float>(ref b);
var c = a1 * a1 + b1 * b1;
return System.Runtime.CompilerServices.Unsafe.As<float, MyFloat>(ref c);
}
Likewise, the operators should be:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MyFloat operator +(in MyFloat a, in MyFloat b)
{
ref var a1 = ref Unsafe.As<MyFloat, float>(ref Unsafe.AsRef(in a));
ref var b1 = ref Unsafe.As<MyFloat, float>(ref Unsafe.AsRef(in b));
var c = a1 + b1;
return Unsafe.As<float, MyFloat>(ref c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MyFloat operator *(in MyFloat a, in MyFloat b)
{
ref var a1 = ref Unsafe.As<MyFloat, float>(ref Unsafe.AsRef(in a));
ref var b1 = ref Unsafe.As<MyFloat, float>(ref Unsafe.AsRef(in b));
var c = a1 * b1;
return Unsafe.As<float, MyFloat>(ref c);
}
but this code is still slower than float
version...
Thanks for idea! I will try to improve it. Also there is idea to use enum, will try it. (my current enum usage may appear wrong as it casts float to int with lost of digits after point, not sure). And than will look into different of IL
of all approaches (and than try to use Fody
or ilproj
to hack).
Method | N | Mean | Error | StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
----------------------- |----- |----------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
FloatWrapperUnsafe | 1000 | 8.617 us | 0.1704 us | 0.3364 us | - | - | - | - |
FloatIntoDouble | 1000 | 2.802 us | 0.0541 us | 0.0506 us | - | - | - | - |
FloatNew | 1000 | 1.210 us | 0.0249 us | 0.0256 us | - | - | - | - |
FloatEnumNew | 1000 | 4.837 us | 0.0287 us | 0.0268 us | - | - | - | - |
FloatWrapperNew | 1000 | 5.798 us | 0.0416 us | 0.0389 us | - | - | - | - |
Float | 1000 | 2.415 us | 0.0468 us | 0.0437 us | - | - | - | - |
FloatEnum | 1000 | 6.888 us | 0.1352 us | 0.1557 us | - | - | - | - |
FloatWrapperIntoDouble | 1000 | 13.172 us | 0.0590 us | 0.0552 us | - | - | - | - |
FloatWrapper | 1000 | 14.749 us | 0.0652 us | 0.0610 us | - | - | - | - |
Var (no ref) faster than enum. Will check other approaches.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
MyFloatUnsafe myunsafedistance(MyFloatUnsafe a, MyFloatUnsafe b)
{
var a1 = Unsafe.As<MyFloatUnsafe, float>(ref a);
var b1 = Unsafe.As<MyFloatUnsafe, float>(ref b);
var c = a1 * a1 + b1 * b1;
return Unsafe.As<float, MyFloatUnsafe>(ref c);
}
Simplest fastest approach with ref
has next assembly. I guess C# could optimize out readonly struct
with Pure
method at some point in future.
But for my cases I seems have enough possibilities to do different kind of designs.
How much quality do you need? Isn't machine translation enough? Is my poor English OK? What's your purpose?