Подтвердить что ты не робот

Является ли Где на массиве (типа структуры) оптимизировано, чтобы избежать ненужного копирования значений структуры?

По соображениям производительности памяти у меня есть массив структур, так как количество элементов велико, и элементы регулярно подбрасываются и, следовательно, измельчают кучу GC. Это не вопрос о том, следует ли использовать большие структуры; Я уже определил, что перехват GC вызывает проблемы с производительностью. Мой вопрос в том, когда мне нужно обработать этот массив структур, не следует ли мне использовать LINQ? Поскольку структура невелика, нецелесообразно передавать ее по значению, и я понятия не имею, если генератор кода LINQ достаточно умен, чтобы это сделать или нет. Структура выглядит следующим образом:

public struct ManufacturerValue
{
    public int ManufacturerID;
    public string Name;
    public string CustomSlug;
    public string Title;
    public string Description;
    public string Image;
    public string SearchFilters;
    public int TopZoneProduction;
    public int TopZoneTesting;
    public int ActiveProducts;
}

Итак, скажем, у нас есть массив этих значений, и я хочу извлечь словарь пользовательских слизней для идентификаторов производителей. Прежде чем я изменил это на структуру, это был класс, поэтому исходный код был написан простым запросом LINQ:

ManufacturerValue[] = GetManufacturerValues();
var dict = values.Where(p => !string.IsNullOrEmpty(p.CustomSlug))
                 .ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);

Моя забота - я хочу понять, как LINQ собирается генерировать фактический код для создания этого словаря. Мое подозрение состоит в том, что внутри код LINQ будет иметь нечто вроде этой наивной реализации:

var dict = new Dictionary<string, int>();
for (var i = 0; i < values.Length; i++) {
    var value = values[i];
    if (!string.IsNullOrEmpty(value.CustomSlug)) {
        dict.Add(value.CustomSlug, value.ManufacturerID);
    }
}

что было бы плохо, потому что третья строка создаст локальную копию структуры, которая будет медленной, потому что структура большая и вместо этого будет разбивать шину памяти. Нам также не нужно ничего, кроме идентификатора и пользовательского пули, чтобы он копировал много бесполезной информации на каждой итерации. Скорее, если бы я сам его правильно закодировал, я бы написал его так:

var dict = new Dictionary<string, int>();
for (var i = 0; i < values.Length; i++) {
    if (!string.IsNullOrEmpty(values[i].CustomSlug)) {
        dict.Add(values[i].CustomSlug, values[i].ManufacturerID);
    }
}

Знает ли кто-нибудь, если генератор кода достаточно умен, чтобы использовать простое индексирование массива, как второй пример, когда код генератора запускается над массивами структур или он реализует более наивную, но медленную первую реализацию?

Каков наилучший способ декомпилировать этот тип кода, чтобы узнать, что на самом деле сделает этот генератор кода?

UPDATE

Эти изменения теперь производятся. Как оказалось, в процессе перезаписи кода и использования профилировщика Dot Memory для определения того, сколько памяти было использовано, и где я обнаружил две утечки памяти в коде компилятора Phalanger PHP. Это была одна из причин того, что объем памяти, которую использовали наши процессы, продолжал расти, и одна из утечек памяти была действительно неприятной и фактически вызвана кодом Microsoft Async (вероятно, стоит вопрос или вопрос о переполнении блога, чтобы помочь другим избежать она).

Во всяком случае, как только я обнаружил утечку памяти и исправил ее, я нажал этот код без каких-либо оптимизаций памяти для преобразования из классов в структуры, и, как ни странно, это на самом деле заставило GC трэш даже больше. Я видел периоды времени, когда GC будет использовать до 27% процессора в соответствии с счетчиками производительности. Скорее всего, эти большие блоки ранее не получали GC'ed из-за утечек памяти, поэтому они просто зависали. Как только код был исправлен, GC начал вести себя еще хуже, чем раньше.

Наконец, мы закончили код для преобразования этих классов в структуры с использованием обратной связи в этом вопросе, и теперь наше общее использование памяти на пике составляет около 50% того, что было, оно быстро падает, когда загрузка на сервере идет и, что еще важнее, мы видим, что только 0,05% процессора используется для GC, если даже это. Поэтому, если кто-то задается вопросом, могут ли эти изменения повлиять на реальный мир, они действительно могут, особенно если у вас есть объекты, которые обычно висят на некоторое время, так что застряли в куче второго поколения, а затем нужно получить бросок и сбор мусора.

4b9b3361

Ответ 1

Каков наилучший способ декомпилировать этот тип кода, чтобы узнать, что на самом деле сделает этот генератор кода?

Нет необходимости декомпилировать код. Все реализации метода LINQ to Objects можно увидеть в Справочном источнике.

Относительно вашего конкретного вопроса. Вы можете ожидать много операций копирования struct при использовании методов LINQ (и вообще IEnumerable<T> и Func<T, ..>).

Например, текущий элемент IEnumerator<T> доступен через свойство Current, определенное следующим образом

T Current { get; }

поэтому доступ, по крайней мере, включает в себя одну копию. Но реализации перечисления обычно хранят текущий элемент в поле во время метода MoveNext, поэтому я бы сказал, что вы можете смело считать 2 операции копирования.

И, конечно, каждый Func<T, ...> вызовет другую копию, потому что входной аргумент T.

Поэтому в общем случае вам следует избегать LINQ в таких сценариях.

Или вы можете использовать старую школьную технику моделирования ссылки через массив и индекс. Поэтому вместо этого:

var dict = values
    .Where(p => !string.IsNullOrEmpty(p.CustomSlug))
    .ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);

вы можете избежать копирования struct, используя это:

var dict = Enumerable.Range(0, values.Length)
    .Where(i => !string.IsNullOrEmpty(values[i].CustomSlug))
    .ToDictionary(i => values[i].CustomSlug, i => values[i].ManufacturerID);

ОБНОВЛЕНИЕ:. Поскольку кажется, что есть интерес к теме, я предоставлю вам вариант последней методики, которая может облегчить вашу жизнь, избегая чрезмерной копии struct.

Скажем, ваш ManufacturerValue был классом, и вы использовали множество запросов LINQ, подобных тем, которые приведены в примере. Затем вы переключились на struct.

Вы также можете создать обертку struct и вспомогательный метод расширения, подобный этому

public struct ManufacturerValue
{
    public int ManufacturerID;
    public string Name;
    public string CustomSlug;
    public string Title;
    public string Description;
    public string Image;
    public string SearchFilters;
    public int TopZoneProduction;
    public int TopZoneTesting;
    public int ActiveProducts;
}

public struct ManufacturerValueRef
{
    public readonly ManufacturerValue[] Source;
    public readonly int Index;
    public ManufacturerValueRef(ManufacturerValue[] source, int index) { Source = source; Index = index; }
    public int ManufacturerID => Source[Index].ManufacturerID;
    public string Name => Source[Index].Name;
    public string CustomSlug => Source[Index].CustomSlug;
    public string Title => Source[Index].Title;
    public string Description => Source[Index].Description;
    public string Image => Source[Index].Image;
    public string SearchFilters => Source[Index].SearchFilters;
    public int TopZoneProduction => Source[Index].TopZoneProduction;
    public int TopZoneTesting => Source[Index].TopZoneTesting;
    public int ActiveProducts => Source[Index].ActiveProducts;
}

public static partial class Utils
{
    public static IEnumerable<ManufacturerValueRef> AsRef(this ManufacturerValue[] values)
    {
        for (int i = 0; i < values.Length; i++)
            yield return new ManufacturerValueRef(values, i);
    }
}

Это дополнительное (одноразовое) усилие, но со следующими преимуществами:

(1) Это a struct, но с фиксированным размером, поэтому накладные расходы на копирование будут незначительными по сравнению с обычной ссылкой (один дополнительный int).
(2) Вы можете увеличить фактический размер данных struct без беспокойства.
(3) Все, что вам нужно сделать с вашими запросами LINQ, - это добавить .AsRef()

Пример:

var dict = values.AsRef()
    .Where(p => !string.IsNullOrEmpty(p.CustomSlug))
    .ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);

Ответ 2

Структуры [pass by value] [1] - так что я уверен, что только использование делегатов для вашего ToDictionary приведет к двум копиям, независимо от того, что еще происходит.

Другими словами, рассмотрим

.ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);

Как эквивалентно:

var key = GetKey(values[i]);
var value = GetValue(values[i]);

.ToDictionary(key, value);

который, очевидно, создает две копии структуры для перехода к GetKey и GetValue.

Ответ 3

Если вам нужно немного расслабиться от сборщика мусора, вы можете использовать gcServer в файле app.config:

<configuration>
    <runtime>
        <gcServer enabled="true" />
    </runtime>
</configuration>

Чтобы узнать, какой IL генерируется на основе вашего кода LINQ, LinqPad - отличный инструмент.

К сожалению, я не знаю, как использовать LINQ для перечисления структур. Обычно я использую structs для сохранения небольшого количества типов значений.

Может быть, расслабление GC поможет вам обойти вашу проблему с производительностью и дать другим шанс? У меня также есть приложение, которое делает огромное количество создания и удаления объектов, где спектакли были охвачены GC Frenzy. Использование GCServer = "true" разрешило его, взамен увеличения доли свободной частной памяти.

Ответ 4

Стрелки:

p => !string.IsNullOrEmpty(p.CustomSlug)
p => p.CustomSlug
p => p.ManufacturerID

скомпилированы в фактический метод, где p - параметр значения метода. Затем эти методы передаются Linq в виде экземпляров делегата Func. Поскольку они являются параметрами значения, ваша структура передается по значению.

Возможно, вы можете использовать:

ManufacturerValue[] values = GetManufacturerValues();
var dict = Enumerate.Range(0, values.Length)
  .Where(i => !string.IsNullOrEmpty(values[i].CustomSlug))
  .ToDictionary(i => values[i].CustomSlug, i => values[i].ManufacturerID);

Это просто фиксирует ссылку на массив в каждой из стрелок лямбда (замыкания).

Редактировать: я не видел, чтобы Иван Стоев ответил уже это предложение. Направьте свой ответ вместо этого.