ufcpp / UfcppSample

http://ufcpp.net/ 向けのサンプル
Apache License 2.0
136 stars 39 forks source link

MetricSpace engish version please #228

Closed dzmitry-lahoda closed 5 years ago

ufcpp commented 5 years ago

How much quality do you need? Isn't machine translation enough? Is my poor English OK? What's your purpose?

dzmitry-lahoda commented 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 |           - |           - |           - |                   - |
ufcpp commented 5 years ago

May be you have ideas how to improve next?

I don't know exactly but the following issue could help you, maybe...

https://github.com/dotnet/coreclr/issues/18542

ufcpp commented 5 years ago

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);
        }
ufcpp commented 5 years ago

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...

dzmitry-lahoda commented 5 years ago

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 |           - |           - |           - |                   - |
dzmitry-lahoda commented 5 years ago

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); 
        }
dzmitry-lahoda commented 5 years ago

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.