Performance: type derived from common

I ran into one performance issue that I cannot understand. I know how to fix it, but I don’t understand why this is happening. It is just for fun!
Let me talk about codes. I simplified the code as much as I could to reproduce the problem.
Suppose we have a common class. It contains an empty list inside and does something with T in the constructor. It has a Run method that calls the IEnumerable<T> method in a list, for example. Any() .

 public class BaseClass<T> { private List<T> _list = new List<T>(); public BaseClass() { Enumerable.Empty<T>(); // or Enumerable.Repeat(new T(), 10); // or even new T(); // or foreach (var item in _list) {} } public void Run() { for (var i = 0; i < 8000000; i++) { if (_list.Any()) // or if (_list.Count() > 0) // or if (_list.FirstOrDefault() != null) // or if (_list.SingleOrDefault() != null) // or other IEnumerable<T> method { return; } } } } 

Then we have a derived class that is empty:

 public class DerivedClass : BaseClass<object> { } 

Let the start-up performance of the ClassBase<T>.Run method from both classes be measured. Access from a derived type is 4 times slower than from a base class. And I can’t understand why this is happening. Compiled in Release mode, the result will be the same as warming up. This only happens for .NET 4.5.

 public class Program { public static void Main() { Measure(new DerivedClass()); Measure(new BaseClass<object>()); } private static void Measure(BaseClass<object> baseClass) { var sw = Stopwatch.StartNew(); baseClass.Run(); sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } } 

The complete list is essentially

+59
performance generics c # clr
Nov 27 '14 at 17:27
source share
3 answers

Update:
There is a response from the CLR team on Microsoft Connect

It is related to finding a dictionary in the generic code. Runtime heuristics and JITs do not work well for this particular test. We will see what can be done with this.

In the meantime, you can get around this by adding two basic methods to BaseClass (you don't even need to call them). This will make the heuristic work as one would expect.

Original:
JIT error.

You can fix this crazy thing:

  public class BaseClass<T> { private List<T> _list = new List<T>(); public BaseClass() { Enumerable.Empty<T>(); // or Enumerable.Repeat(new T(), 10); // or even new T(); // or foreach (var item in _list) {} } public void Run() { for (var i = 0; i < 8000000; i++) { if (_list.Any()) { return; } } } public void Run2() { for (var i = 0; i < 8000000; i++) { if (_list.Any()) { return; } } } public void Run3() { for (var i = 0; i < 8000000; i++) { if (_list.Any()) { return; } } } } 

Note that Run2 () / Run3 () are called not from anywhere. But if you comment on the Run2 or Run3 methods, you will get a performance penalty, as before.

I think something has to do with stack alignment or method table size.

PS you can replace

  Enumerable.Empty<T>(); // with var x = new Func<IEnumerable<T>>(Enumerable.Empty<T>); 

all the same bug.

+29
Nov 28 '14 at 11:17
source share

After some experimentation, I found that Enumerable.Empty<T> always slow when T is a class type; if it is a value type, it is faster, but depends on the size of the structure. I tested the object, string, int, PointF, RectangleF, DateTime, Guid.

After seeing how it is implemented, I tried various alternatives and found some that work fast.

Enumerable.Empty<T> relies on the inner class EmptyEnumerable<TElement> Instance static property .

This property does few things:

  • Checks if a private static volatile field is null.
  • Assigns an empty array to the field once (only if empty).
  • Returns the value of a field.

Then what Enumerable.Empty<T> really does is only return an empty array from T.

Having tried different approaches, I found that slowness is caused by both the volatile property and modifier .

Accepting a static field initialized with T [0] instead of Enumerable.Empty<T> , as

 public static readonly T[] EmptyArray = new T[0]; 

the problem is gone. Please note that readonly modifier is not defining. Having the same static field declared using volatile or accessed via property causes a problem.

Regards, Daniela

0
Nov 28 '14 at 9:30
source share

It seems the CLR optimizer problem. Turn off "Optimize code" on the "Build" tab and try running the test again.

-2
Nov 28 '14 at 9:17
source share



All Articles