Производительность: тип, производный от универсального
я столкнулся с одной проблемой производительности, которую я не совсем понимаю. Я знаю, как это исправить, но я не понимаю, почему это происходит. Это просто для удовольствия!
Давайте поговорим о коде. Я упростил код настолько, насколько мог, чтобы воспроизвести проблему.
Предположим, у нас есть общий класс. Он имеет пустой список внутри и делает что-то с T
в конструктор. Он имеет Run
метод, который называет IEnumerable<T>
способ в списке, например,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;
}
}
}
}
затем мы есть производный класс, который пуст:
public class DerivedClass : BaseClass<object>
{
}
давайте измерим производительность запуска ClassBase<T>.Run
метод из обоих классов. Доступ из производного типа в 4 раза медленнее, чем из базового класса. И я не могу понять, почему это происходит. Скомпилированный в режиме выпуска, результат тот же с разминкой. Это происходит только на .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);
}
}
полный список по сути
3 ответа:
обновление:
Есть ответ от команды CLR на Microsoft Connectэто связано с поиском словаря в общем коде generics. Эвристика во время выполнения и JIT не работают хорошо для этого конкретного теста. Мы посмотрим, что можно с этим сделать.
в то же время, вы можете обойти его, добавив два фиктивных метода в базовый класс (даже не нужно вызывать). Это приведет к тому, что эвристика работайте так, как и следовало ожидать.
Оригинал:
Это Джит фэйл.можно исправить с помощью этой сумасшедшей вещи:
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; } } } }
обратите внимание, что Run2 () / Run3 () являются не любой из них. Но если вы закомментируете методы Run2 или Run3 - вы получите штраф за производительность, как и раньше.
есть что-то, связанное с выравниванием стека или размером таблицы методов, я думаю.
P. S. Вы можете заменить
Enumerable.Empty<T>(); // with var x = new Func<IEnumerable<T>>(Enumerable.Empty<T>);
все та же ошибка.
после некоторых экспериментов, я обнаружил, что
Enumerable.Empty<T>
всегда медленно, когда T класс тип; если это тип значения, он быстрее, но зависит от размера структуры. Я тестировал object, string, int, PointF, RectangleF, DateTime, Guid.глядя на то, как это реализовано, я пробовал разные альтернативы, и нашел некоторые, которые работают быстро.
Enumerable.Empty<T>
полагается на внутренний классEmptyEnumerable<TElement>
' sInstance
статический свойства.это свойство делает мелочах:
- проверяет, если частный статический!--8-->летучие поле имеет значение null.
- присваивает пустой массив полю один раз (только если он пуст).
- возвращает значение поля.
то, что
Enumerable.Empty<T>
на самом деле делает-это только возвращает пустой массив т.пробуем разные подходы, я обнаружил, что медлительность вызвана и the свойства и летучие модификатор.
принятие статического поля, инициализированного T[0] вместо
Enumerable.Empty<T>
какpublic static readonly T[] EmptyArray = new T[0];
проблема исчезла. Обратите внимание, что модификатор readonly не является определителем. Имея то же самое статическое поле, объявленное с летучие или через свойства вызывает проблему.
С уважением, Даниеле.