Pull to refresh

Comments 46

UFO landed and left these words here

Не знаю, как у вас там в С#, но для микроконтроллеров в классическом СИ в любой приличной 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;
}

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

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

UFO landed and left these words here

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

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

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

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

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

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

UFO landed and left these words here

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

UFO landed and left these words here

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

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

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

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

UFO landed and left these words here
UFO landed and left these words here
Сейчас мы НЕ доказываем что код меняется на 40% производительности

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


Кодировал алгоритмы на C# и заметил что изменение позиции объявления переменной влияло на время исполнения на 20%, условно
UFO landed and left these words here
UFO landed and left these words here

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

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

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

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

UFO landed and left these words here

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

UFO landed and left these words here

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

UFO landed and left these words here
2) Мы не в суде, я сообщил информацию к сведению, вы можете её проигнорировать

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

UFO landed and left these words here

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

UFO landed and left these words here

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

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

UFO landed and left these words here

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

UFO landed and left these words here

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

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

UFO landed and left these words here

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

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

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

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

Sign up to leave a comment.

Articles