guard icon indicating copy to clipboard operation
guard copied to clipboard

Unacceptable performance

Open crclz opened this issue 4 years ago • 2 comments

First, thank you guys for this cool library. The lambda and the fluent api is cool.

As other developer discussed in #13 and #39 , compiling the expression causes overhead in performance.

And today I found the overhead unacceptable when developing a IO-bounded program which process some data packets:

Data packets are produced by another program at the rate of approximately 15KB/s. The consumer process receives the packets via a tcp connection and decode the packet.

My function ReadFloat is widely used in decoding the data packet. And I found that Guard.Argument(() => pos).Min(0); has a great impact on performance.

static float ReadFloat(byte[] buffer, int pos)
{
    Guard.Argument(() => pos).Min(0);

    return BitConverter.ToSingle(buffer, pos);
}

I think the overhead is unacceptable because my program is an IO-bounded program that consumes the data packets at quite a low rate (~15KB/s), but the usage of the CPU is very high (~40% taskmgr)

So obviously this library is not high performance, and might cause problems even if you are developing a IO-bounded program. I think you should warn the developers by marking this method with Obsolete.

To Reproduce

the benchmark:

            Results:

            Without Guard:              00:00:00.0020735
            With Guard:                 00:00:13.5584228
            Guard, Not Using Lambda:    00:00:00.0011907
            Bottleneck:                 00:00:13.3689263
using Dawn;
using Microsoft.VisualBasic;
using System;
using System.Linq.Expressions;

namespace GuardPerformance
{
    class Program
    {
        static byte[] _buffer = new byte[1024];
        static readonly int Iterations = 100000;

        static void Main(string[] args)
        {
            /*

            Results:

            Without Guard:              00:00:00.0020735
            With Guard:                 00:00:13.5584228
            Guard, Not Using Lambda:    00:00:00.0011907
            Bottleneck:                 00:00:13.3689263

            */
            DateTime t;

            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                ReadFloat(_buffer, i % 1000);
            }
            Console.WriteLine($"Without Guard: {DateTime.Now - t}");


            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                ReadFloatWithGuard(_buffer, i % 1000);
            }
            Console.WriteLine($"With Guard: {DateTime.Now - t}");


            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                ReadFloatWithGuard_NotUsingLambda(_buffer, i % 1000);
            }
            Console.WriteLine($"Guard, Not Using Lambda: {DateTime.Now - t}");


            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                Bottleneck(() => i);
            }
            Console.WriteLine($"Bottleneck: {DateTime.Now - t}");
        }

        static float ReadFloat(byte[] buffer, int pos)
        {
            if (pos < 0)
                throw new ArgumentOutOfRangeException();

            return BitConverter.ToSingle(buffer, pos);
        }

        static float ReadFloatWithGuard(byte[] buffer, int pos)
        {
            Guard.Argument(() => pos).Min(0);

            return BitConverter.ToSingle(buffer, pos);
        }

        static float ReadFloatWithGuard_NotUsingLambda(byte[] buffer, int pos)
        {
            Guard.Argument(pos, nameof(pos)).Min(0);

            return BitConverter.ToSingle(buffer, pos);
        }

        public static void Bottleneck<T>(Expression<Func<T>> e, bool secure = false)
        {
            // This causes the bad performance
            e.Compile();
        }
    }


}

crclz avatar Oct 31 '20 10:10 crclz

Why are you using the expression-based overload in such a performance-critical scenario?

Instead of writing:

Guard.Argument(() => pos).Min(0);

Can't you do:

Guard.Argument(pos, nameof(pos)).Min(0);

?

julealgon avatar Dec 24 '20 15:12 julealgon

@julealgon receiving data at the speed of ~15kb/s and processing the data, is it a performance-critical scenario?

crclz avatar Dec 24 '20 16:12 crclz

The expression compiling argument was very slow, and never recommended for performance-critical methods. It was due to be removed with v2 now that we have [CallerArgumentExpression], but unfortunately, I'm retiring Guard - I apologise for any inconvenience, and thank you for all the support. The readme has details.

safakgur avatar Jul 18 '23 04:07 safakgur