Re[38]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 04.05.24 06:05
Оценка: 2 (1)
Здравствуйте, vdimas, Вы писали:

V>А что поделать, если плаваешь в стадиях трансформации кода, путаешься в терминологии?

А что делать, если не плаваю и не путаюсь?
То, что вы выбираете применение терминологии, которую в индустрии не используют — ваша личная проблема. Как и заблуждения об устройстве компиляторов.

V>Это не оптимизирует, это выполняет подстановку в общем случае, т.е. производит бета-редукцию с заменой известных значений (распространение констант).

V>Я же говорю — для тебя это ликбез.
V>Оптимизация — это процесс преобразования/упрощения вычислений, в том числе в символьном виде.
1. То, что делает компилятор в рассмотренном примере (xor eax, eax), вполне укладывается даже в ваше определение. Почему вы при этом утверждаете, что он "не оптимизирует" — . Противоречите сами себе.
2. В вашем определении почему-то опущена цель преобразований. "упрощение" — не самый удачный термин, т.к. очень многие оптимизации "усложняют" код.
Вот если взять что-то общепринятое, например википедию, то там сразу будет написано

Оптимизация — модификация системы для улучшения её эффективности


V>Но, в любом случае, у него протекает дофига вычислений в рантайм, которые могли быть сделаны в compile-time, просто в рантайме эти вычисления происходят лишь однажды, что несколько нивелирует беспомощность его компилятора.

Ну, всё же какие-то оптимизации он выполняет. А не просто полагается на мемоизацию. Но давайте не будем отвлекаться.

V>Это продолжение ликбеза.


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

V>Распространения констант без подстановок не бывает, ваш КО.

Продолжаете стендап?
Распространение констант прекрасно работает безо всяких "подстановок". Вам привести пример кода, где инлайна нету, а constant propagation есть?

V>Это еще не оптимизация, это штатное ср-во языка С++, где явно указывается, какие методы будут инлайными.


Компилятору уже давно наплевать на ваши "указания", сделанные в рамках языка. У него — свои критерии того, что инлайнить, а что нет.

V>Демонстрируешь непонимание.

Нет, это вы не понимаете, что вам пишут.

V>Например, global optimization — это уже неявное преобразование кода, которое может инлайнить даже то, что не заказывали инлайнить.

В рассмотренном примере никто никаких инлайнов не "заказывал".
Граница между глобальной и локальной оптимизациями — только в применяемых технологиях. А в современных компиляторах зачастую и технология та же самая — вы же представляете себе механику LTCG в том же самом MSVC?

V>А так же может подменять тождественные в терминах низлежащих регистров вычисления, т.к. такая оптимизация происходит уже после стирания типов, т.е. несколько тождественных с т.з. логики происходящего в регистрах ф-ий могут заменяться одной. И если подстановки — это простая бета-редукция, то на этом этапе уже эффективны альфа- и эта-преобразования.

Склейка одинакового кода происходит ещё до фазы распределения регистров.

V>Просто надо понимать, что и как ускоряется.

Отож.
V>Например, до эпохи спекулятивных вычислений и длинной предвыборки, экономия на удалении call была существенной. Но сегодня call и jump стоят примерно ноль тактов процессора, т.к. эти операции выполняются еще на стадии предвыборки кода,
Зачем вы пишете чушь? Напишите бенчмарк и посмотрите, сколько тактов стоит call.

V>Всё это именно так и называется в С++ — "кодогенерацией", потому что верно описывает происходящее.

Никем это так в C++ не называется. Кодогенерация — это последняя фаза работы компилятора, когда строится код целевой платформы.
V>Но кодогенерация не есть оптимизация.
Смотря что использовать для генерации кода. Например, тот же rust использует для неё LLVM, что означает возможность применения ещё каких-то оптимизаций.


V>Из-за ангажированности дотнетом у тебя в голове обитает хорошо заметная каша из независимых изначально вещей, которые в твоём представлении стали неотделимыми друг от друга, смотрю. ))

Ваша проблема — в том, что вы спорите с голосами в голове. У меня нет никакой ангажированности дотнетом.
V>Например, JIT не просто инлайнит методы, он часто одновременно с этим пытается их оптимизировать.
V>Странно, что ты сам не соизволил отделить мух от котлет, ведь при запрете оптимизации и указании агрессивного инлайна он всё равно имеет право инлайнить, но не оптимизировать.
Ну так и более-менее любой C++ компилятор можно понагибать, разрешая одни оптимизации и запрещая другие.
В чём вопрос-то? В том, что вы не понимаете, что существует множество разных оптимизаций, и инлайнинг — это одна из них? Ну, да, похоже. Особенно с учётом злоупотребления терминами редукции — похоже, что вы пытаетесь все методики свести к трём выученным вами словам. Ну, так-то можно и к одному термину свести: "неявное преобразование кода". Это никак не помогает разобраться ни в том, чем одни оптимизации отличаются от других, ни в том, как именно компилятор принимает решения о применении тех или иных трансформаций.

V>Потому что C# не может позволить себе агрессивные оптимизации на уровне байт-кода.

V>Агрессивные оптимизации стирают типы, а в байт-коде типы должны оставаться.
Нда. Всё ещё хуже, чем я думал.
1. Какие именно типы стёрты вот в такой оптимизации?
public static bool is_max(long a) => a == long.MaxValue;

2. Почему моё "уравнение" также не думает решать компилятор С++? Ведь в нём агрессивные оптимизации разрешены?
Каша, каша в голове.

V>Понимаешь, на сегодня практически все неагрессивные оптимизации уже выполняются.

V>Помнится, я долго сетовал насчёт хреновой или отсутствующей оптимизации вычислений с плавающей точкой (и уже давно перегорел с кое-какой своей идеей), но за плавучку уже взялись и буквально за 2-3 версии дотнета упрутся уже в полоток векторных оптимизаций. По крайней мере, догонят плюсы на O1 и местами на O2...

V>Но есть же еще O3! ))

Хохотался. Пока вы думаете об оптимизациях в терминах "O1"/"O2"/"O3", успеха вам не видать. Вообще, формулировка "догонят плюсы на O1" уже показывает опасный уровень некомпетентности. Потому, что O1 — это опция компилятора, а не языка. И в каждом компиляторе — свой набор того, что включается на каждом из этих уровней. Иногда код с -O3 может работать медленнее, чем с -O2.
Поэтому сама идея "чем больше O, тем сильнее оптимизации" — порочна, забудьте её.


V>Ну вот ты стучал себя кулаком в грудь "да никогда такого не будет!!!" (С)

V>Как ты вообще мог такое задвинуть, что дотнет через единицы версий опять похоронят с концами, как это было после выхода второго дотнета в том самом 2005-м?
Опять голоса в голове....
Вы, если хотите меня в чём-то опровергнуть, приведите цитату. Где я утверждал про "похоронят с концами"?

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

Да в чём именно я заблуждаюсь?
V>Я вангую, что через 2-3 версии дотнет как платформа упрётся уже в предел неагрессивных оптимизаций, что потребует пересмотра всего того, что будет мешать агрессивным.
У вас неверное понимание термина "агрессивность оптимизации". Вы его трактуете так, как будто она связана с изменением семантики. Типа, "дружелюбные" оптимизации обходятся с кодом аккуратно, а "агрессивные" изменяют его поведение, и чем агрессивнее, тем сильнее.
Нет. Агрессивность оптимизации — это просто количество усилий, потраченных оптимизатором.
У JIT агрессия зарезана искусственным образом, потому что ему нужно уложиться в ограниченное время. Для (частичного) обхода этого ограничения придумана tiered compilation. (На всякий случай, с учётом вашей ангажированности дотнетом, напишу: придумана не в дотнете, а в java, и в дотнет пока что толком не завезена).
Но даже с учётом tiered compilation никакой джит не может себе позволить тратить столько же ресурсов, сколько AOT-компилятор. Например, те же плюсы или раст.
И никакая, самая агрессивная оптимизация, не имеет права ломать семантику кода.

Поэтому все ваши рассуждения про то, что в будущих версиях дотнета целая арифметика станет вести себя как в плюсах — полная чушь. Точно так же, как и никакие версии С++ в будущем не станут превращать в xor eax, eax код функции is_max<unsigned long>. Ровно и именно поэтому — семантика жёстко задана.
И в оптимизациях, которые можно делать без жертвования семантикой, в дотнете ещё конь не валялся. Всякие loop peeling, автовекторизация, спекулятивные оптимизации — дофига ещё работы. Не на две-три версии, а, скорее, на пять-шесть. Не при нашей жизни этот колодец вычерпают.

V>Из того, что на поверхности — JSON и XML-сериализацию приделали в виде кодогенерации, чтобы не лезть в рефлексию...

Xml-сериализация существует в виде кодогенерации примерно с самого начала дотнета.

V>Да, в дотнете меньше UB, чем в С++, например, прямо в стандарте указана очередность вычисления аргументов вызовов методов (как позиционных, так и именованных), указана очередность вычисления в условных выражениях и в выражениях присваивания...

О чём и речь.

V>Но главный-то UB остаётся, причём его остаётся намного больше чем в плюсах — это разыменование ссылочных типов.

Чего? Где вы увидели UB в разыменовании ссылочных типов?

V>И тогда мне будет интересно посмотреть на код после AOT, где я обращался потенциально по нулевому указателю, вызывая метод, который не обращается к this или к виртуальным методам.

V>Будет ли тогда AOT генерировать ненужную проверку на null для ссылки, по которой не обращаются?
V>Делаем ставки?
Можем и сделать. Когда расчёт?
V>Если же ты мне показывал подставновку констант в вычисления в рамках метода-ф-ии — то это продолжения детсада а-ля Синклер.
Я дал конкретную ссылку. Где тело функции is_max<long> по-прежнему есть, call есть, но всё тело — это
xor eax, eax
ret


V>В общем, опять твоё "слышал звон".

Скорее, наоборот. Попробуйте найти упоминание бета- или alpha-редукции в исходниках компилятора С++ или llvm.

V>Вы десятки постов выдвигали неверные предположения с коллегой, пока не ткнул тебя носом, написав код-аналог на C#, показав происходящее.

)
V>При этом напомнил, что в С++ промежуточные вычисления можно допустимо производить в более широких типах прямо по стандарту.
Ну приведите же уже ссылку на пункт стандарта — вместе посмеёмся.
V>Уже все лулзы были собраны. ))
Но вы же продолжаете, правда уже не так смешно, как вначале.

V>Какое в опу UB, если этот код не был соптимизирован на уровне байт-кода?

Ну не был и не был — какая нам разница, на каком этапе проводить оптимизацию?
Кстати, кланг все оптимизации, которые мы наблюдали в этом топике, делает после уровня байт-кода.

S>>Потому, что в дотнете эта функция возвращает true только для одного значения — int.MaxValue. А для всех остальных она, в соответствии со стандартом, возвращает false.

V>Это не отвечает на вопрос — почему компилятор C# не решил эту формулу?
V>Зачем он каждый божий раз выполняет вычисления, вместо сравнения с единственным числом? ))
Хороший вопрос. Тогда давайте так — а зачем те же вычисления выполняет C++? Вместо сравнения с единственным числом?

V>Про эту проблему я писал еще в нулевые.

Проблема того, что вы тупите?

V>Тебе уже 100 раз объявняли — там unsigned, т.е. переполнение не являются UB.

Это место — верно.
V>А со знаковыми — допускается промежуточно вычислять в болеее широких типах для избегания переполнения.
А вот это — уже нет.

V>Это поведение прямо по стандарту плюсов — можно расширить тип на промежуточном значении в формуле.

V>Просто для тебя это всё сложно, смотрю... ))
Ну, раз для вас это просто, давайте разберём.
Вот у нас формула: (x+1)<x
Посмотрим, какие типы будут у промежуточных результатов. Например, для int:
(
 (
  x  // int
   + 
  1  // int
 )   // ?

 < 
 x   // ?
)    // bool

Давайте, расскажите, какие типы нужно вписать вместо ?.
Можете сослаться на пункты стандарта, которые это описывают.

V>Ты путаешь значение всего выражения со значением отдельных его компонент в процессе вычисления.

Я-то как раз нет.
V>Тебя же не смущает в точности аналогичный эффект в плавучке, когда 80-битные промежуточные значения давали совсем не тот результат, который получился бы на 64-битных промежуточных?
Это — совершенно другой эффект. Совершенно отдельно и очень внятно описанный в стандарте языка C++.
И применяется он ровно потому, что вычисления с плавающей точкой изначально "приблизительные", и гоняться за побитовой эквивалентностью результатов нужно гораздо реже, чем за скоростью математики.

V>А ты думал — откуда взялось упомянутое правило?


Вы его для начала процитируйте. А там посмотрим, что откуда взялось. Про double можете пока не задуряться — когда с целыми числами разберётесь, перейдём к плавающей запятой.

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

S>> А для long, значит, запрограммировали?

V>Чего? ))

V>При чём тут, вообще, long?
Повторюсь: для is_max<long> генерируется вот такой код:
xor eax, eax,
ret

Как видите — тут компилятор "решил уравнение". А для is_max<unsigned long> остаются вычисления в рантайме.
Рассуждения про "право вычислять в расширенных типах" вам ничем не помогут — ведь если бы можно было так делать, то компилятор бы их точно так же применил к беззнаковому типу.
Более того — в исходной версии кода is_max всё так и есть. is_max(unsigned short x) { return (x+1) < x;} всегда будет возвращать false.
Почему же is_max(unsigned long x) { return (x+1) < x;} так себя не ведёт?

V>Я ж говорю — ты не понимаешь, даже о чём ты рассуждаешь...

Да я-то как раз понимаю, а вы продолжаете писать какую-то чушь.

V>Тебе хочется, чтобы компилятор распознал тот уникальный вариант, когда N==1, и дя него сделал особый выверт — сравнение на равенство с единственным числом!

Нет, не хочу. Это вы этого хотите — пятью абзацами выше вы требовали этого от дотнета, помните? Там это было поводом критиковать C# и JIT. А тут, оказывается, всё в порядке, "компилятор сам такой выверт не сделает".
То, чего я хочу — это чтобы вы поняли, как работает оптимизатор С++. Что вот в этом коде нет никаких "вычислений в расширенных типах", и что инлайн никак не связан с обсуждаемой оптимизацией:
#include <climits>
#include <iostream>

bool is_max(int x)
{
  return (x+1) < x;
}

typedef bool (* Check) (int);

int main()
{
   volatile Check f = is_max;
   std::cout << (f(INT_MAX) ? "ok": "fail") << std::endl;
}

(https://godbolt.org/z/qEGsnEPhY)
V>И хотя в дотнеет описано, что "просто отбрасываются биты" — это лишь описание происходящего при потере данных, хотя оно всё-равно UB, но уже на прикладном уровне.
Нет там никакого UB. Это — implementation-specific. То есть биты будут отбрасываться одинаковым образом, что в дебаге, что в релизе.

V>Банально не дошли еще руки у разработчиков дотнета.

Мы говорим об одном и том же. Вы — о том, что затраты на EA велики, а я — что выигрыш маленький.

V>Плюс, сам этот анализ нетривиален в модели регистров и ячеек стека, ведь из-за value-типов "размер" ячеек стека в дотнете может быть произвольный, в отличие от джавы, где размер ячейки условно всегда long (и сужается, где это является возможным для более простых числовых типов).

А вот это — чушь. Размер ячеек стека тут совершенно ни при чём. JIT всё равно видит его в терминах "список типов", а уж одинаковый ли там размер — дело десятое.
Для EA нужен только анализ CFG, больше там ничего нет.

V>Теряешь лицо каждый божий раз.

Напомню, что потеря лица — это не ошибиться в дискуссии, а настаивать на своей ошибке. Ну, вот как вы сейчас.

V>Это если бы в Джаве еще были комплексные числа в базовой библиотеке...

Да какая разница, в базовой или не в базовой?
На Java невозможно написать тип Complex идиоматическим образом так, чтобы он не тормозил в массовых операциях.
И escape analysis тут будет припаркой мёртвому.
Не нравится пример с Complex — ну, возьмите какой-нибудь Vector4 для 3d-графики.

V>- Джава гораздо раньше дотнета научилась пользоваться векторными регистрами современных процов.

И до сих пор делает это весьма вяло.
V>И до сих пор толком не умеет, в этом направлении движения пока мест зачаточные.
Непонятно, что такое "толком". Автовекторизации — нету, и пока не предвидится. Опять же, автовекторизация — это одна из самых сложных техник в оптимизациях, её крайне трудно прикрутить, а ещё труднее — прикрутить её так, чтобы она предсказуемо работала в интересующих нас случаях. Поэтому пока джависты заклинают автовекторизатор (который может внезапно перестать автовекторизировать код при "небольших" изменениях в codebase), дотнетчики пишут код с интринсиками, который гарантированно разогнан при исполнении на целевой платформе.

V>- в реальных расчётах не нужны массивы из миллиона комплексных чисел — эти вычисления почти всегда промежуточные.


V>- даже если нужны такие длинные данные, то в джаве их хранят очень просто — в чётных ячейках одна компонента числа, в неёчетных другая.
Отож, это понятно. Техника — вполне известная, называется "дрочить вприсядку". Думаете, вы первый, кто мне рассказал про хранение произвольных данных в byte[] и о том, как здорово JIT в джаве натренирован распознавать все эти data[x] << 24 + data[x+1] << 16 + data[x+2] << 8 + data[x+3]?
Но всё же, куда удобнее записывать операции над value-типами в терминах операций над value-типами, а не в терминах манипуляций с элементами массивов.

V>При вычислении результат помещается в эти же ячейки (допустим, взяли любой третьесторонний библиотечный тип Complex, который обязательно будет иметь ср-ва записи и чтения из массива согласно правилам хорошего тона, принятым для этого языка)

Правдачтоли? Ну, покажите мне ссылку на любой третьесторонний библиотечный тип Complex, у которого есть средства записи и чтения из массива. Вы опять делаете свою любимую ошибку: воображаете мир, в котором вам было бы комфортно, и подменяете воображением реальность.


V>, так вот, временные переменные Complex на стеке никуда не убегут, поэтому дотнет в таких задачах сливает Джаве от слова всегда.

Да вы смеётесь. Я же говорю — джава в таких задачах сливается ещё до выхода на рубеж. Хотите побенчмаркать? Опыт с интегрированием массивов на дотнете вас ничему не научил?

V>Что тоже ложь.


V>Тут как-то в начале 10-х годов соревновались с реализацией круговой очереди, я сделал её как value-type и бенчмарки показали прирост быстродействия примерно в 2.4 раза!
Надо смотреть на конкретный пример. Очень сомневаюсь, что дело было именно в "одном объекте".
V>Помнится, AVK тогда резко среагировал, примерно на твой сегодняшиний манер... ))
V>Потому что тыкаешь вас фейсом в ваши порядком надоевшие предрассудки, а толку ноль.

V>Избавление от лишней косвенности даёт много не только в синтетических тестах.
V>Да, в дотнете есть value-типы, но они практически не используются для построения иерархий объектов, не приносят пользу.
А из них особо и не построишь. Напомню — нет наследования, нет косвенности. Вся "иерархия" встраивается по месту.
V>Зато escape-анализ из-за них отсутствует.
V>В итоге, вреда больше чем пользы.
Если выигрыш в реальных задачах от escape analysis станет заметным — его тоже прикрутят. Просто дело-то именно в том, что реальный код, который мог бы сильно выиграть от EA, уже написан на value-типах.
V>Или можно поступать на наш манер — сделать огромную библиотеку value-типов, взамен многих десятков из базовой инфраструктуры, и получать совсем другую эффективность.
Можно, и именно так всё и работает. Но вообще, конечно, подозрительно. Что же это за типы из базовой инфраструктуры, которые вы переписали на value?
И почему их не переписали на value-типы прямо в Microsoft?

V>Классика жанра — как написаны унутре базовые библиотеки дотнета:

V>https://godbolt.org/z/o7xjqYzfj
1. Первую проверку вижу. G_M3253_IG02: test rdi, rdi; je SHORT G_M3253_IG04. А где вторая?
2. Вы уверены, что унутре базовых библиотек дотнета столь же щедро рассыпаны [MethodImpl(MethodImplOptions.NoInlining)]?
3. А если их убрать, то сколько будет проверок?

V>В публичном методе обычно проверяются параметры, затем вызывается приватный или internal некий Impl-метод, где аргументы уже не проверяются в коде, но проверяются фреймворком.

Как мы видим, никакой "проверки аргументов фреймворком" не возникает, если к этому не прилагать искусственных усилий: https://godbolt.org/z/svh8Gf69E

V>Итого две проверки одного аргумента вдоль всей цепочки.


V>И это происходит даже на таких критических вещах, как манипулирование массивами, Span, Memory и т.д., что в итоге приходится писать свою дублирующю функциональность, в т.ч. unmanaged, ради избегания лишнего бранчинга.
Очень, очень сомневаюсь. Массивы, спаны и т.д. прекрасно инлайнятся, а Memory заменить value-типом невозможно в силу дизайна.
V>Заметь, вызов NotNull заинлайнился, иначе было бы 3 проверки.
V>Но у таких методов специально выставляют AgressiveInlining, ес-но.
Даже без выставления всё прекрасно работает. Дотнет обязан проверить на null — дотнет проверяет на null.
При этом, если написать аналог класса Holder, который я приводил в прошлом посте, то он будет работать одинаково на любой платформе (а не так, что в MSVC он летает, а в GCC вызывает segfault).
И если даже программист перепишет код с C++ 1-в-1, то обнаружит ошибку сразу же при запуске (неважно, в дебаге или релизе), почитает интернет, и починит код так, чтобы он работал именно таким способом, каким работает код Holder на MSVC. И опять — код будет верно работать на любой платформе. При этом перформанс будет ровно таким же, как в случае "кода MSVC".
То есть дело опять не в том, что С++ типа как-то "более агрессивно" оптимизирует. А в том, что в стандарте разыменование null объявлено UB вместо IS. Было бы IS — код Holder отлично бы работал и в GCC.

V>Вообще-то, нет.

V>Эта техника растёт из техники зависимых типов в некоторых языках, но в этих языках гарантии строгие.
Понятно, откуда растёт техника. Но я имел в виду не "теоретическую достижимость", а реальное ТЗ. Которое пришлось писать с учётом реального мира.
Идеи сделать NotNull строгим были, но их пришлось отложить по причинам невозможности немедленного внедрения.
В будущем — да, возможно NotNull сделают частью системы типов, и JIT станет его учитывать при генерации кода. А пока — всё работает ровно так, как спроектировано, создавая минимум сюрпризов для прикладных разработчиков. И есть перспектива навсегда остаться с нынешней реализацией, потому что от legacy избавляться сложнее, чем хочется.

V>В языках с зависимыми типами в рантайме возникает исключение при невозможности привести null-значение к not-null.


V>Впрочем, мы это уже обсуждали, эта техника касается не только распространения гарантий тще-null, а вообще вопроса допустимых значений.

V>Так-то в дотнете через Unsafe.As можно присвоить управляемой ссылке любой мусор
V>При этом даже не нужно компилять свой код в режиме unsafe, достаточно подключить соотв. внешнюю либу.
Отож. Но это — специальная возможность, про которую нужно знать; нужно иметь причины её использовать, и в документации прямо написано

his API is used to cast an object to the given type, suppressing the runtime's normal type safety checks. It is the caller's responsibility to ensure that the cast is legal.

Сравним это с C++, где в поисках UB не надо специально копать. Оно лежит прямо в знаковой арифметике, разыменовании указателей, и обращения к переменным. Крайне тяжело написать программу на C++, не используя ничего из этого.


V>Но это всё лирика, тут всё понятно — проклятое легаси, которое будет жить неизвестно сколько.

Ну, вот видите.
V>Для сравнения, я показывал тут как-то инлайный хелпер для плоюсов, для реализации строгой идиомы NotNull.
V>Как обычно, в плюсах не добавляет ничего в рантайм, не требуется изменять код, т.е. внешний АПИ умного указателя совпадает с АПИ обычного, но гарантии при этом получаются строгие.
V>И в язык вмешиваться не пришлось, просто используется NotNull<SomeType>, плюс удобство typedef-ов.
Тут главное — чтобы умный компилятор не убрал весь этот ваш хелпер, посчитав, что "указатель и так не может быть нулевым".

S>>Ну так это и есть херня, т.к. поведение кода радикально меняется при оптимизации и без неё. В итоге можно успешно отлаживаться, а в релизе получить булшит.

V>Во-первых, отлаживать можно и релизный код — поставь флаг генерацию pdb и отлаживай себе.
Ну, попробуйте пошагово отладить is_max, чтобы понять, как же там получается false для INT_MAX.

V>В-третьих, правильный ответ я сказал сразу же — переполнение знаковых означает потерю результата.

Что такое "потеря результата"? Там нет никакой "потери результата". Результат есть, ведь платформа в реальности выполняет переполнение совершенно конкретным способом. Или вы мне не верите? Сохраните результат в переменную, выведите на экран.
V>С тех пор так и было принято, что для упражнений над битами рекомендуется использовать беззнаковые, а знаковые — это компромисс для тех вычислений, результат которых должен уложиться в выбранную ширину переменной. Таковой консенсус сложился в отрасли с миллионами разработчиков уже очень давно, стандарт плюсов лишь фиксирует этот консенсус на бумаге.


V>И да, в этой связи две стадии оптимизации: от исходника в LLVM-машинку и от неё в конкретную архитектуру железа.

V>Деньги вваливают в основном во вторую стадию, т.к. это "для всех", а в первой стадии генерации с исходника С++ CLang улучшается очень небыстро...
Вот и я о том же. Вваливать в первую стадию — не так эффективно.

V>>>Т.е. в реальных проектах, где никто в здравом уме никогда не сравнивает this с nullptr, а передаёт его куда-то дальше, где уже и возникает ошибка, Clang никак не помогает.

S>>С чего вы взяли, что "где-то уже" будет происходить ошибка? Покажите мне ошибку в коде Holder.

V>Слушай, если сложности с пониманием, ты мог бы и сам исправить код так, чтобы возникла описанная ситуация, когда объекту был подан this=null, но объект не проверил, что this!=null (бо так никто в здравом уме не делает).

А зачем? Я пишу объект именно для того, чтобы он проверял this на null.
V>Потом можно посмотреть на код и увидеть, что имелось ввиду. Ты там похвалил Clang, а я описал ситуацию, где он не поможет уже через один доп. вызов.
Не, вы всё же покажите ошибку в коде Holder.

S>>В реальных проектах такой код упадёт в рантайме, несмотря на интуитивно понятную логику и наличие проверки на null.

V>В реальных проектах этот код упадёт в рантайме и безо-всяких оптимизаций, бо содержит ошибку.
Покажите, как он падает без оптимизаций.
Покажите, как он падает с оптимизациями под MSVC++.

V>Оптимизация может повлиять лишь на то, в какой точке возникла ошибка, т.е. как далеко код убежал от места, где ошибся программист.



V>Если в плюсах я могу обложить любой тип, который будет использоваться по-значению, своими описанными операторами присвоения или копирования, то в C# можно зазеваться и многими способами всё испортить. ))

Если под "зазеваться" вы имеете в виду Unsafe.As(), то это не так, чтобы частый способ всё себе испортить.
А если нет, то как бы присвоение корректного значения в другое место всегда даст корректное значение Дотнет не даёт просто так ошибиться при "присвоении или копировании".

V>В общем, разработка эффективного кода на C# лично у меня отнимает в разы больше квантов внимания на единицу функциональности, чем аналогичная разработка на плюсах, бо в плюсах всё просто — ошибиться невозможно. Обложился всеми перегрузками операторов, мышь не проскочит, вся семантика детерминирована.

Ну, да, вся, кроме UB.

V>А в C# тут приходится напоминать коллегам, что конструктор без параметров для value-типов — это не то же самое, что дефолтная инициализация,

V>В плюсах такой дичи нет, ес-но.
В плюсах заместо этого — просто возможность вообще ничего не инициализировать. На фоне этого то, что для value типов гарантируется default initialization — уже великий прогресс.


V>В ссылочной семантике null часто является допустимым значением, это позволяет обходиться без игрищ с идиомой NullObject (одноимённый паттерн), тем более, что такая идиома способна раскрыться во всей красе только при наличии строгих not-null аннотаций, иначе является профанацией, т.к. проверка на null всё-равно будет генерироваться платформой, а значит, эту проверку с тем же успехом можно делать и на прикладном уровне. ))

Ну вот вам Holder как пример попытки обойтись без игрищ с идиомой NullObject.

S>>То есть не понимали разницы между implementation-specific behavior и undefined behavior.

V>Походу, ты продолжаешь не понимать написанного.
Не, я-то как раз понимаю
V>Или настолько слабо признать свою неправоту?
В чём именно?
V>Еще раз, медленно — типом обсуждаемого выражения является bool, а какой тип у промежуточного значения в процессе вычисления — не ваше дело.
Да блин это просто неверно. Ваше утверждение является враньём. Типы всех промежуточных выражений чётко прописаны в стандарте.
Как вы вообще столько лет пишете на языке, основ которого не понимаете?
V>Это неопределённое поведение, т.е. разное, в зависимости от чего угодно.
Совершенно верно.

V>Всё так, с тех пор как в ведущей тройке компиляторов уже лет 15 обитает встроенный тип __in128.

V>(у кого-то чуть раньше появился, у кого-то позже)
V>Просто ты был не в курсе, верно?
Конечно в курсе Но я правильно понимаю, что ваше утверждение — компилятор ведёт вычисления для long в типе _int128?
Покажите пункт стандарта, который это описывает.

V>И если implementation-defined разное для разных компиляторов, то UB может быть разным для одного и того же компилятора.

V>Об этом тоже тебе было сказано.
Это как раз я об этом говорил вам. А вы стояли на позиции, что, дескать, это для языка целое переполнение — UB, а в конкретном компиляторе под конкретную платформу всё очень даже defined.

V>И да, компиляторы по-прежнему специфицируют, как они действуют при переполнениях.

V>Неужели не веришь?
Неа, не верю. Примеры кода из этого топика опровергают ваше утверждение.
Если бы ваша гипотеза была верна, компилятор не мог бы одновременно делать две разных вещи при переполнении.

V>И это надо учитывать при написании кода.

По идее, это не должно быть "надо учитывать" при написании кода. Разработчику и так есть о чём подумать, кроме эффектов от алгебраически-нейтральных трансформаций кода.
Типа — была длинная формула, давала неверный результат, в целях упрощения кода распилили на цепочку операций с промежуточными переменными, и тут опс! результат стал другим.

V>Например, ты мог написать код, который давал некое ожидаемое поведение для 64-битной плавучки, но на 80-битной или 128-битной получить другие значения.

Мог. И другие значения — это ожидаемое поведение. Потому, что, сюрприз-сюрприз,

Formally, the C++ standard makes no guarantee on the accuracy of floating-point operations.


V>А оно касается не только целых, а любых типов, которыми ты оперируешь локально.

Неа. Не любых, а только тех, которые описаны в стандарте.

V>Еще немного столкнёшься с подробностями — глядишь, начнёшь уважать коллег по цеху.

Я их и так уважаю.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.