guard
guard copied to clipboard
Unacceptable performance
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();
}
}
}
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 receiving data at the speed of ~15kb/s and processing the data, is it a performance-critical scenario?
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.