Почему перегрузка ++ занимает значительно больше времени, чем просто увеличение значения? - PullRequest
0 голосов
/ 06 ноября 2018

Почему увеличение Uint (в моем случае) на один 100.000.000 раз занимает ~ 0,175 секунды, в то время как увеличение Uint внутри структуры такое же количество раз занимает ~ 1,21 секунды?

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

private uint _num;        
public static Seq operator ++(Seq a)
{
    a._num++; return a;
}

Я решил отредактировать сам экземпляр (если это идет вразрез с рекомендациями), а не возвращать новый экземпляр, потому что это также занимает довольно много времени.

Эта структура будет увеличиваться очень часто, поэтому я ищу причину такого увеличения времени обработки.

Ответы [ 3 ]

0 голосов
/ 06 ноября 2018

Я думаю, это потому, что ваш Seq является структурой (тип-значения) и работает оператор приращения пути. Как видите, public static Seq operator ++(Seq a) { ... } - это , возвращающий экземпляр вашей структуры Seq. Однако, поскольку структуры передаются по значению, фактически создается новый экземпляр Seq, который возвращается, и вот ваши издержки.

Взгляните на другой пример:

struct SeqStruct
{
    private uint _num;        
    public void Increment() => _num++;
}

// ----------------------------------

var seq = new SeqStruct();
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 100000000; i++)
    seq.Increment();
s3.Stop();

Теперь, если вы измерите время вызова метода Increment(), вы можете увидеть, что оно теперь ближе к «чистому» приращению uint, и если вы переключитесь на Release конфигурацию сборки, у вас будет то же самое время, что и «чистое» увеличение uint (этот метод был «встроенным»).

Другой вариант - использовать class вместо struct:

class SeqClass
{
    private uint _num;
    public static SeqClass operator ++(SeqClass a) { a._num++; return a; }
}

Теперь увеличение будет выполняться быстрее.

0 голосов
/ 06 ноября 2018

Это просто вопрос того, насколько умный джиттер. Для обычной локальной переменной типа int оператор

x++;

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

Но перегрузка ++ в структуре имеет следующую семантику. Предположим, у нас есть наша структура в s, и мы говорим s++. Это означает, что мы эффективно реализуем

s = S.operator++(s);

Что это делает?

  1. Сделать копию из s в локальную переменную, которая является новым формальным параметром
  2. Сохраняет любое состояние регистра, которое будет перезаписано вызываемым абонентом
  3. выполнить инструкцию по вызову
  4. загрузить формальное значение, увеличить его, сохранить
  5. скопировать новое значение в место, зарезервированное для возвращаемого значения
  6. выполнить инструкцию возврата
  7. восстановить состояние предыдущего кадра активации

  8. скопировать возвращенное значение в папку для s.

Итак, ваша быстрая программа выполняет шаг 4. Ваша медленная программа выполняет шаги с 1 по 8 и примерно в восемь раз медленнее. Джиттер может выявить, что это кандидат на встраивание и избавиться от некоторых из этих затрат, но это ни в коем случае не требуется, и есть множество причин, по которым он может отказаться от встраивания. Джиттер не знает, что этот прирост важен для вас.

0 голосов
/ 06 ноября 2018

Во-первых, это тот вопрос, который требует связать рейтинг производительности: https://ericlippert.com/2012/12/17/performance-rant/ Подобные вопросы относятся к преждевременной оптимизации / на самом деле не имеют значения.

Что касается причины: кроме проблем с правильными измерениями, есть как минимум служебный вызов функции. Не имеет значения, насколько далеко вы удалены от обнаженных указателей, внутри все еще есть два прыжка и добавление / извлечение из стека функций.

Или, по крайней мере, это происходит в большинстве случаев. Дело в том, что JiT может выполнить inline вызов этой функции. Это может даже частично перекомпилировать, чтобы сделать это изменение. Действительно трудно предсказать, если и когда это произойдет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...