When entering a method, we often perform a null check. Someone makes a check in a separate method, so that the code looks cleaner, and it turns out that it is:
public void ThrowIfNull(object obj) { if(obj == null) { throw new ArgumentNullException(); } }
And what is interesting with such a check, I see the use of the object attribute in large quantities, because you can use a generic. Let's try to replace our method with generic and compare performance.
Before testing, you need to take into account another drawback of the object argument. Significant types (value types) can never be equal to null (Nullable type does not count). Calling a method like ThrowIfNull (5) is meaningless, however, since the type of the argument is object, the compiler will allow the method to be called. As for me, this reduces the quality of the code, which in some situations is much more important than performance. In order to get rid of this behavior, and to improve the method signature, the generic method will have to be divided into two, with an indication of constraints (constraints). The trouble is that you cannot specify a nullable constraint, however, you can specify a nullable argument with a struct constraint.
We start performance testing, and use the BenchmarkDotNet library. We hang attributes, run, and look at the results.
public class ObjectArgVsGenericArg { public string str = "some string"; public Nullable<int> num = 5; [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullGenericArg<T>(T arg) where T : class { if (arg == null) { throw new ArgumentNullException(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullGenericArg<T>(Nullable<T> arg) where T : struct { if(arg == null) { throw new ArgumentNullException(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullObjectArg(object arg) { if(arg == null) { throw new ArgumentNullException(); } } [Benchmark] public void CallMethodWithObjArgString() { ThrowIfNullObjectArg(str); } [Benchmark] public void CallMethodWithObjArgNullableInt() { ThrowIfNullObjectArg(num); } [Benchmark] public void CallMethodWithGenericArgString() { ThrowIfNullGenericArg(str); } [Benchmark] public void CallMethodWithGenericArgNullableInt() { ThrowIfNullGenericArg(num); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<ObjectArgVsGenericArg>(); } }
Method | Mean | Error | Stddev |
---|---|---|---|
CallMethodWithObjArgString | 1.784 ns | 0.0166 ns | 0.0138 ns |
CallMethodWithObjArgNullableInt | 124.335 ns | 0.2480 ns | 0.2320 ns |
CallMethodWithGenericArgString | 1.746 ns | 0.0290 ns | 0.0271 ns |
CallMethodWithGenericArgNullableInt | 2.158 ns | 0.0089 ns | 0.0083 ns |
Our generic on nullable type worked 2000 times faster! And all because of the notorious packaging (boxing) . When we call CallMethodWithObjArgNullableInt, our nullable-int is "packaged" and placed on the heap. Packaging is a very expensive operation, and the method sags in performance. Thus using generic we can avoid packaging.
So, the generic argument is better than object because:
Upd. Thanks to the zelyony habraiser for comment. Methods inline for more accurate measurements added attribute MethodImpl (MethodImplOptions.NoInlining) .
Source: https://habr.com/ru/post/332640/
All Articles