Re[34]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 23.04.24 03:50
Оценка: :)
Здравствуйте, vdimas, Вы писали:
V>Тут стоит задасться вопросом — а зачем компилятор вообще пытается решить такие "уравнения"?
V>Это из-за шаблонов, из-за сильного раздутия бинарников по мере того, как шаблонный код становился всё более популярным когда-то.
То есть вы решили, что недостаточно бреда в ветке написали.
Нет, компилятору совершенно всё равно, есть ли в коде шаблоны или нет. Он прекрасно оптимизирует и вот такую функцию:
bool is_max(long a) { return (a+1) < a; }

Вообще, вся эта оптимизация применяется уже после инстанцирования всех шаблонов. В LLVM IR никаких шаблонов нету.
Поэтому правильный ответ на вопрос "зачем" — для максимизации производительности. Если компилятор не будет делать таких оптимизаций, то по бенчмаркам его код будет проигрывать конкурентам.
V>Такой подход позволяет обрезать из шаблонного кода ненужные ветки где только возможно дотянуться анализом кода.
Такой подход позволяет обрезать ненужные ветки из любого кода. Что, в свою очередь, разблокирует целую цепочку дополнительных оптимизаций.
Во-первых, уходят бранчинг-инструкции. А ведь предсказание переходов экономит только такты условного перехода. Сами сравнения в конвеере остаются.
Во-вторых, если код выполняется в коротком цикле, то пара лишних инструкций способна ещё ухудшить производительность из-за переполнения буфера декодера опкодов.
В-третьих, после устранения лишних бранчей меняется дерево доминирования, что позволяет оптимизировать код, идущий ниже по графу исполнения.

V>Но оно же работает для любого инлайна.

Оно работает даже без инлайна. Просто без инлайна функция превращается в return false.

V>В примитивных случаях работает — уходит бранчинг, половина кода метода выбрасывается за ненадобностью.

Эскимосы впервые увидели снег.

V>Для сравнения, дотнет ничего не ищет, тупо исполняет:

V>
V>Program:IsMax(int):bool:
V>       lea      eax, [rdi+01H]
V>       cmp      eax, edi
V>       setl     al
V>       movzx    rax, al
V>       ret  
V>

И это — прямое следствие того, что в дотнете знаковое переполнение не является UB.
Можете для эксперимента посмотреть, как будет выглядеть С++ код для unsigned long.
Внезапно, он тоже "ничего не ищет, а тупо исполняет". Как вы думаете, почему?

V>Забавно, что сложение выполняется в 64 бит, да еще через трюк адресной арифметики, но запоминаются младшие 32 бит результата.

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

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

Да, это является необходимым условием. Но одного его недостаточно. Посмотрите, какой код генерируется для is_max<unsigned long>. Там нет никакой "потенциальной ошибки", и тем не менее компилятор не может избавиться от сложений и вычитаний.

V>Плюс, некоторые UB довольно-таки, забавны.


V>Например, вызов метода объекта, когда не обращаются данным объекта, т.е. ни к виртуальному методу, ни к полю.

V>Например, в C# вызов sc.Method() полностью заинлайнился, обращения к this нет, но в коде перед вызовом метода всё-равно стоит проверка, что адрес sc валидный.
Это потому, что C# порождает callvirt для всех методов, независимо от того, является ли метод виртуальным или нет. А по стандарту, CLR обязан проверять this на null перед вызовом метода, даже если в данном контексте биндинг выполняется статически. Это было осознанным решением.

V>В плюсах это UB, но, например, в MSVC прекрасно работает

V>И кто тут прав?
V>C#, который делает лишнюю проверку в рантайм перед вызовом практически каждого метода (nullable-аннотация не помогает), хотя зачастую нет обращения к this?
V>Или MSVC, который исполняет ненужный бранчинг?
V>Или Clang, который создаёт ненужный объект?
V>Или GCC, который не создаёт ненужный объект, но даже косвенно про проблему узнать не получится, бо нет утечки памяти? ))
В дотнете UB нет, там вызов метода на null-объекте является defined behavior, что облегчает портирование программ.
А из плюсовых компиляторов правы все, на то оно и UB.

Посмотрите, что происходит в LLVM-based компиляторах, если чутка поменять код:
https://godbolt.org/z/Y1W431348
Неожиданно, внутри метода Method() нет никакого бранчинга. И даже строка "!!!" в выхлоп не попадает — компилятор решил, что this никогда-никогда не может быть null, и выкинул ветку.
Кланг хотя бы воспитанно предупреждает об этом:

warning: 'this' pointer cannot be null in well-defined C++ code; comparison may be assumed to always evaluate to false [-Wtautological-undefined-compare]

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

template<typename T, T default_value>
struct Holder {
private: T _value;

public:
    Holder(T value) { _value = value; }

    T Value() {
        return (this != NULL)
            ? _value
            : default_value;
    }
};
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.