Обновить

Комментарии 46

НЛО прилетело и опубликовало эту надпись здесь

Не знаю, как у вас там в С#, но для микроконтроллеров в классическом СИ в любой приличной IDE (IAR, KEIL, и т.д.) компиляторы имеют несколько режимов оптимизации уже давным-давно. И как раз такие вещи они прекрасно обрабатывают. Даже могут несколько строк кода выбрасывать из прошивки, если эти строки не имеют смысла. Во время отладки с непривычки разработчик может быть удивлён тем, что отладчик пропускает некоторые строки и даже блоки кода типа простых циклов со счётчиком, который нигде больше не используется (например, для задержки на 1 млн тактов).

Уверен, что для С# оптимизация настраивается от и до.

Уверен, что для С# оптимизация настраивается от и до.

Нет. Это по многим причинам не так. По большому счёту основная разница идёт между debug и release режимами - между ними разница значительна и, конечно, тестирование производительности в debug не стоит выполнять.
Компиляция C#->IL почти не настраивается и почти не оптимизирует (ну разве что на уровне вывода типов немного). Это, похоже, принципиальный подход.
JIT (IL в машинный код) тоже особо не настраивается - это скорее нужно для отладки самого движка .Net, чем для прикладных программистов. Причём JIT ограничен в оптимизациях - во-первых жёсткие требования к времени компиляции, во-вторых некоторые вещи нельзя оптимизировать "как в C" потому что нельзя по контракту.
В итоге .NET может выявить какие-то константные вычисления, некоторые peephole оптимизации, ограниченно может заинлайнить, может выкинуть неисполнимые ветки кода. Но, например, не делает оптимизацию tail recursion - скорее всего для сохранения возможности размотки стека при исключениях, возможно по этой же причине хвостовой call никогда не преобразует в jump. Расчёт констант и выражений, оптимизация циклов и глубина инлайнинга тоже очень далека от -O3 С/С++. Но не скажу, что всё совсем плохо (пользуясь случаем, скажу спасибо @Nagg за его вклад в дело оптимизации движка).

Важно отметить, что это не "C# плохой", это скорее "C# другой".

Но, например, не делает оптимизацию tail recursion - скорее всего для сохранения возможности размотки стека при исключениях, возможно по этой же причине хвостовой call никогда не преобразует в jump.

Вообще говоря, инструкция tail. в IL есть. Другое дело, что компилятор C# очень тяжело заставить её применять. А вот F# и Nemerle активно используют хвостовую рекурсию, если это возможно (например, нет обработки исключений с вызовом рекурсивной функции внутри).

Ну что же, век живи - век учись. Спасибо за наводку. Я не знал о её существовании. В любом случае, C/C++ компилятору проверить допустимость хвостовой рекурсии обычно проще (и поэтому она происходит).

Иногда под дотнетом очень интересные оптимизации встречаются. Например, тот же F# часто вообще рекурсию в цикл преобразует.

Пример с факториалом

Было:

let fact n =
    let rec fact_r n acc =
        match n with
        | 1 -> acc
        | n -> fact_r (n - 1) (acc * n)
    if n > 1 then fact_r n 1 else 1

Стало:

_.fact_r@2(Int32, Int32)
    L0000: push ebp
    L0001: mov  ebp, esp
    L0003: lea  eax, [ecx-1]
    L0006: test eax, eax
    L0008: ja   short L000e
    L000a: mov  eax, edx
    L000c: pop  ebp
    L000d: ret
    L000e: imul edx, ecx
    L0011: mov  ecx, eax
    L0013: jmp  short L0003
_.fact(Int32)
    L0000: cmp  ecx, 1
    L0003: jle  short L0010
    L0005: mov  edx, 1
    L000a: call _.fact_r@2(Int32, Int32)
    L000f: ret
    L0010: mov  eax, 1
    L0015: ret

Эквивалентный код на C#:

internal static int fact_r@2(int n, int acc)
{
    while (true)
    {
        if (n == 1) return acc;
        acc *= n;
        n = n - 1;
    }
}
public static int fact(int n)
{
    if (n > 1) return fact_r@2(n, 1);
    return 1;
}

Это и есть пример оптимизации хвостовой рекурсии:

Перезаписать аргументы и перейти к началу функции.

НЛО прилетело и опубликовало эту надпись здесь

Только не инструкция, а префикс инструкции.

Вот только в своё время изменение обработки этого префикса в JIT-компиляторе вызвало некоторое удивление у некоторых людей.

Не скажу за детали, но суть была в том, что некоторый человек ориентировался на то, что tailcall игнорируется и некоторый ресурсоёмкий сервис в облаке падает со stack overflow, а после обновления рантайма он внезапно стал использовать хвостовую рекурсию и не падал, а пользователю за работу сервиса был выставлен немалый счёт (это в телеге в pro.net как-то обсуждалось)

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

Хотелось бы увидеть какой-то тест, не один год в C#, никогда не замечал подбного.

Так как JIT о C# ничего не знает, это надо на iL смотреть.

НЛО прилетело и опубликовало эту надпись здесь

Я думаю было бы хорошо иметь полный пример

... и желательно на https://sharplab.io/

НЛО прилетело и опубликовало эту надпись здесь

Не обязательно же всю программу.
Я сделал пример, но в нём разница на 1 mov (и то, зависит просто от кода ниже)

Надеюсь, что разница именно в release режиме?

Наличие этой разницы уже многое говорит

Ни о чём не говорит. Это даже на синтетическом примере не даст и 3% разницы.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Сейчас мы НЕ доказываем что код меняется на 40% производительности

В начале топика вы четко сказали про 20%:


Кодировал алгоритмы на C# и заметил что изменение позиции объявления переменной влияло на время исполнения на 20%, условно
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

У автора там "опечатка" или хз как ее назвать. Если ее убрать - скомпилированный код будет идентичный. SharpLab

Да, упустил и не проверил внимательно - как раз к тому, что от небольшой семантически значимой разницы может измениться код. Это отличие было в и коде комментария. Там еще и j определена была.

Я пишу без ООП, короткие программы, я не разработчик, проверяю какие-то алгоритмы и т.п. и для меня такое поведение — нонсенс,

Возможно, вы просто не умеете замерять производительность.

НЛО прилетело и опубликовало эту надпись здесь

Да, измерять производительность не так просто. Это не про "искривление пространства-времени". Судя по разнице и кейсу, скорее всего у вас разница проявляется на debug режиме. Могу ошибаться, но вы же так и не предоставили конкретные кейсы и замеры. И в этом случае у вас вашим бегунам надо одному после каждых 10 метров отмечаться в бланке о пробегании 10 метров, а второму - раз в 50 метров. В такой процедуре уже неважно, чем вы измеряли - фотофинишем или ручным секундомером, потому что скорость бега на 400 метров вы не измеряете.
База с которой в принципе можно начинать замерять производительность - поставить максимально в одинаковые "стандартные" условия измеряемые кейсы (в .NET стал стандартным инструмент BenchmarkDotNet, в JVM - JMH). Следующая часть - зафиксировать и описать окружение. Следующая - сформулировать гипотезу (в данном случае - что однострочное объявление лучше многострочного). Потом - проверить гипотезу. Потом, если она подтверждается замерами - максимально изолировать причину и уменьшить влияние других факторов. По результатам уже можно было бы поднять issue в проекте dotnet runtime.
Да, бывают какие-то отклонения от этой процедуры, например, если бага в инлайнинге jit, то она как раз НЕ проявляется в debug, а проявляется в release (я такой кейс находил). Но это всё равно примерно тот же сценарий (только код был одинаковый, а окружения отличались ровно на опцию debug/release).

НЛО прилетело и опубликовало эту надпись здесь

Так полный код предоставьте, на котором такое поведение наблюдается, иначе это все равно голословно.

НЛО прилетело и опубликовало эту надпись здесь
2) Мы не в суде, я сообщил информацию к сведению, вы можете её проигнорировать

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

НЛО прилетело и опубликовало эту надпись здесь

Ну для новчиков может быть не очевидно — прочитают первое сообщение и примут его за чистую монету.

НЛО прилетело и опубликовало эту надпись здесь

Старших классов тоже.

1 лучше почисти метрики от контекста, часто nda содержит размытое определение выдачи конфиденциальной информации

НЛО прилетело и опубликовало эту надпись здесь

У вас не идентичный код, что выше привели вы. У вас не только изменение в объявлении, но и само присваивание для eof совершенно подругому описано. Ваша проблема может состоять в том, что для вычисления eof переменная S уже ушла из кеша процессора например

НЛО прилетело и опубликовало эту надпись здесь

Неужели все это стоит учитывать?

Голословное утверждение, ничем не подкрепленное.

НЛО прилетело и опубликовало эту надпись здесь

Однако оба эти подхода привели бы к критическому изменению, поскольку этот метод является частью публичного API.

Разве сейчас контракт функции не поменялся (пусть и не так заметно)? До этого возвращался reference на копию, а сейчас на оригинал. Гипотетически можно представить клиента, который менял полученное правило и при этом никак не влиял на других клиентов. Теперь это изменится

А нельзя просто написать r[i].Match(...)? Зачем это переменная вообще нужна?

Конечно, если как выше написано в комментарии, чтобы отдать копию, то тогда автор статьи рукожоп (не переводчик, переводчик молодец).

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации