Информация об изменениях

Сообщение Re[36]: Carbon от 24.04.2024 1:54

Изменено 24.04.2024 6:32 Sinclair

Re[36]: Carbon
Здравствуйте, vdimas, Вы писали:
V>Для тебя это ликбез.
Ложные утверждения ликбезом быть не могут.

S>>Нет, компилятору совершенно всё равно, есть ли в коде шаблоны или нет. Он прекрасно оптимизирует и вот такую функцию:

S>>
S>>bool is_max(long a) { return (a+1) < a; }
S>>


V>Define "оптимизирует"? ))

Оставляет от всего кода xor eax, eax.

V>Оптимизаций много разных.

О, началось виляние.
V>"Вся оптимизация" — это какая?
Примерно вся. Распространение констант, инлайн вызовов, разворачивание и разгрузка циклов, алгебраические тождества, приведение подобных — я вспотею перечислять.

S>>Поэтому правильный ответ на вопрос "зачем" — для максимизации производительности.


V>Это делает простая бета-редукция и тотальный инлайн.

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

V>То ты рассуждаешь о решении уравнений, то совсем уж направление рукой показываешь.
V>Ты хоть понял, почему твоё "уравнение" компилятор и не думал решать?
Я? Нет, не понял. Поясните своими словами.

V>Шаблонного/инлайного, а не любого.

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

V>Это результат стандартной бета-редкции, но она возможна только до неких установленных пределов своего распространения

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

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

Отож.

V>Мы тут рассуждали чуть о другом — почему компилятор ведёт себя определённым образом при обнаружении UB?

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

V>И почему же в дотнете не превратилась?

Я же вам объяснил — потому что в дотнете нет UB.

V>Т.е., когда подаёшь костанту — оно даёт сразу ответ, бо JIT и AOT инлайнят маленькие методы, т.е. производят небольшую бета-редукцию.

V>Но почему в теле ф-ии остались честные вычисления от параметров? ))
Потому, что в дотнете эта функция возвращает true только для одного значения — int.MaxValue. А для всех остальных она, в соответствии со стандартом, возвращает false.
И точно так же себя ведут плюсы, когда тип аргумента не позволяет UB.

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

Вы продолжаете феерически тупить. Я такого давно не видел.
V>Плюсы без агрессивных оптимизаций тоже дают верный код, как и дотнет.
Плюсы дают верный код и с агрессивными оптимизациями. Просто он верный с т.з. стандарта.
V>Вопрос здесь не почему исполняет в случае знаковых, а почему именно так?
Повторю вопрос: почему С++ с агрессивными оптимизациями не превратил is_max<unsigned long> в xor eax, eax?
V>Я ведь ниже дал хороший пример, который должен был объяснить логику разруливания UB.
Нет, не дали.

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

А для long, значит, запрограммировали?
Всё ещё хуже, чем я думал.

V>Если прибавлять двойку — уже два сравнения, больше — больше.

Это вы вообще о чём? Какие два сравнения?

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

Понятно. Я сдаюсь — вы не способны понять правильный ответ, даже если в него ткнуть носом.

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

Это — досужие рассуждения. Было принято осознанное решение, в котором ошибкой является вызов метода на null-объекте, а не дереференсинг this.


V>Эдак тебя заносит. ))

V>Причина ранней диагностики ошибки по твоей ссылке понятна, когда еще не были развиты наработки по escape analisys.
V>А если this не уходит дальше, как в примере?
V>Почему дотнетные компилятор/JIt/AOT неспособны сделать escape analysis для простейших случаев?
JIT и AOT не имеют права это делать. Потому что семантика инструкции callvirt прописана в стандарте, и отклонение от неё — это бага.
А C# принял такое решение. Что непонятного-то?

V>Джава с такими вещами хорошо справляется уже лет 15, если не больше.

V>В рантайме в джаве, если код пойдёт по ошибочной ветке, будет просто выброшено исключение, а основная ветка выполнится без создания ненужного экземпляра объекта.
И в дотнете "основная ветка" выполнится без создания ненужного экземпляра объекта. А если код пойдёт по ошибочной ветке, будет просто выброшено исключение. В чём вы тут увидели разницу?

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

Покажите пример, где дотнет проверяет на null дважды.

V>Новомодные nullable-нотации не помогают сэкономить на "системных" проверках аж никак, они помогают сэкономить только на юзверских проверках.

Всё верно, так и должно быть.
V>Т.е. nullable-подход в дотнете не является строгим. Ты можешь вернуть null или подать null в кач-ве аргумента, даже если те описаны как non-nullable.
Именно. Потому что нет гарантий, что код по обе стороны вызова скомпилирован с одной и той же конфигурацией нуллабельности.

V>И одновременно с этим обрезает оптимизации, т.е. не позволяет выкидывать ненужный код. ))

Какой именно?

V>Во всей этой истории я зацепился из-за твоего любимого взятого тона в адрес плюсов, мол, херня какая-то происходит.

V>Нет, не херня. В каждом конкретном случае или никакой оптимизации при UB не происходит вовсе (как в MSVC), либо происходит такая оптимизация, которая считает, что ошибочная ситуация произойти не должна.
Ну так это и есть херня, т.к. поведение кода радикально меняется при оптимизации и без неё. В итоге можно успешно отлаживаться, а в релизе получить булшит.
Вот вы только что с гордостью привели пример, как здорово работает код с UB в MSVC. Ну так это и провоцирует писать код в стиле приведённого мной примера — "а чо, я же знаю, что на самом деле будет происходить!".

V>Clang традиционно более щепетилен — поэтому он всегда создаёт ненужный объект в моём сниппете.

V>Но Clang — это не про производительность, это некий референс плюсов как таковых.
Теперь уже и про производительность. Потому что в LLVM вваливают очень много ресурсов, в том числе — и в оптимизации.

V>GCC — это существо из рил ворлд, поэтому исследует объекты на предмет побочных эффектов и убегания зависимостей, и если их нет — может "уничтожать" объекты, т.е. не конструировать их вовсе, а брать только описанную функциональность. Единственным "побочным эффектом" тут будет лишнее выделение памяти аккурат перед AV, что побочным эффектом не очень-то и считается.

Тут нет выделения памяти перед AV.

V>Лично меня тут удивило чуть другое — когда-то MSVC в деле оптимизаций драл GCC в хвост и гриву, а сейчас малость сдал позиции...

V>Но посмотрим, посмотрим...

V>В GCC аналогично.

Отож.

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

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

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

V>Ты допускаешь ill-formed код в своих рассуждениях, просто ждешь от компилятора некоей предсказуемой реакции на такой код в рантайм.

V>Т.е. утекание ошибки в продакшен — "а чо такого?" ))
Ну так вы же ровно этого же хотите от дотнета — чтобы он вам разрешил вызывать методы на null объектах. С т.з. C# это — ill-formed код.
Просто этот ill-formed код всегда ведёт себя предсказуемо. А в плюсах он может себя вести одним способом, а может — другим.

V>Это спор слепого со зрячим.

Да я уже понял.
V>Мы были слепы еще примерно в 90-х. Ближе к концу пошли анализаторы и общее понимание важности well-formed кода.
Ваша слепота частично развеялась за последние несколько дней, но ещё не до конца.
Вот буквально только что, пять дней тому, вы писали:

В данном случае ты даёшь оценку своим фантазиям, бо архитектуры/компиляторы явно определяют своё поведение при переполнении.
Т.е. UB тут для стандарта в общем, но не для конкретной связки платформы и компилятора.

То есть не понимали разницы между implementation-specific behavior и undefined behavior. А тут, гляди-ка, недели не прошло — начало брезжить понимание важности well-formed кода.

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

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

V>Помнишь себя в 2004-2005-х годах как ты вещал с трибун: "Представьте, что сейчас вы напишете код, а потом он будет исполняться намного быстре на будущих версиях платформы!!!"

V>Дудки!
Ну так самое забавное, что именно так и произошло. Вы прямо в этой ветке приводите примеры кода, который стал исполняться намного быстрее на будущих версиях платформы.

V>Сначала убери из кода потенциальные ошибки. ))

В дотнете потенциальных ошибок изначально меньше.
V>Например, в дотнете (и особенно в Джаве) есть ошибки, когда в метод подают не копию объекта, а сам объект, т.е. провоцируют убегание побочных эффектов, а компилятор помочь не может, т.к. он понятия не имеет о семантике.
Это вы сейчас про defensive copying или про что? Мне прямо интересно, есть ли пределы вашей некомпетентности.
Re[36]: Carbon
Здравствуйте, vdimas, Вы писали:
V>Для тебя это ликбез.
Ложные утверждения ликбезом быть не могут.

S>>Нет, компилятору совершенно всё равно, есть ли в коде шаблоны или нет. Он прекрасно оптимизирует и вот такую функцию:

S>>
S>>bool is_max(long a) { return (a+1) < a; }
S>>


V>Define "оптимизирует"? ))

Оставляет от всего кода xor eax, eax.

V>Оптимизаций много разных.

О, началось виляние.
V>"Вся оптимизация" — это какая?
Примерно вся. Распространение констант, инлайн вызовов, разворачивание и разгрузка циклов, алгебраические тождества, приведение подобных — я вспотею перечислять.

S>>Поэтому правильный ответ на вопрос "зачем" — для максимизации производительности.


V>Это делает простая бета-редукция и тотальный инлайн.

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

V>То ты рассуждаешь о решении уравнений, то совсем уж направление рукой показываешь.
V>Ты хоть понял, почему твоё "уравнение" компилятор и не думал решать?
Я? Нет, не понял. Поясните своими словами.

V>Шаблонного/инлайного, а не любого.

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

V>Это результат стандартной бета-редкции, но она возможна только до неких установленных пределов своего распространения

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

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

Отож.

V>Мы тут рассуждали чуть о другом — почему компилятор ведёт себя определённым образом при обнаружении UB?

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

V>И почему же в дотнете не превратилась?

Я же вам объяснил — потому что в дотнете нет UB.

V>Т.е., когда подаёшь костанту — оно даёт сразу ответ, бо JIT и AOT инлайнят маленькие методы, т.е. производят небольшую бета-редукцию.

V>Но почему в теле ф-ии остались честные вычисления от параметров? ))
Потому, что в дотнете эта функция возвращает true только для одного значения — int.MaxValue. А для всех остальных она, в соответствии со стандартом, возвращает false.
И точно так же себя ведут плюсы, когда тип аргумента не позволяет UB.

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

Вы продолжаете феерически тупить. Я такого давно не видел.
V>Плюсы без агрессивных оптимизаций тоже дают верный код, как и дотнет.
Плюсы дают верный код и с агрессивными оптимизациями. Просто он верный с т.з. стандарта.
V>Вопрос здесь не почему исполняет в случае знаковых, а почему именно так?
Повторю вопрос: почему С++ с агрессивными оптимизациями не превратил is_max<unsigned long> в xor eax, eax?
V>Я ведь ниже дал хороший пример, который должен был объяснить логику разруливания UB.
Нет, не дали.

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

А для long, значит, запрограммировали?
Всё ещё хуже, чем я думал.

V>Если прибавлять двойку — уже два сравнения, больше — больше.

Это вы вообще о чём? Какие два сравнения?

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

Понятно. Я сдаюсь — вы не способны понять правильный ответ, даже если в него ткнуть носом.

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

Это — досужие рассуждения. Было принято осознанное решение, в котором ошибкой является вызов метода на null-объекте, а не дереференсинг this.


V>Эдак тебя заносит. ))

V>Причина ранней диагностики ошибки по твоей ссылке понятна, когда еще не были развиты наработки по escape analisys.
V>А если this не уходит дальше, как в примере?
V>Почему дотнетные компилятор/JIt/AOT неспособны сделать escape analysis для простейших случаев?
JIT и AOT не имеют права это делать. Потому что семантика инструкции callvirt прописана в стандарте, и отклонение от неё — это бага.
А C# принял такое решение. Что непонятного-то?

V>Джава с такими вещами хорошо справляется уже лет 15, если не больше.

V>В рантайме в джаве, если код пойдёт по ошибочной ветке, будет просто выброшено исключение, а основная ветка выполнится без создания ненужного экземпляра объекта.
Джава была вынуждена заниматься ескейп-анализом потому, что в ней нету value типов. Из-за этого простейшие вещи, вроде комплексной арифметики, приводят к очень быстрому накоплению мусора.
Канонические примеры работы ескейп-анализа — это что-то вроде:
class Complex
{
  private double _re; public double getRe() { return _re; }
  private double _im; public double getIm() { return _im; }
  
  public Complex(double re, double im) 
  {
    _re = re;
    _im = im;
  }

  public Complex add(Complex other)
  {
    return new Complex(getRe() + other.getRe, getIm() + other.getIm()
  }
}
...

public static Complex sum(Complex[] data)
{
  Complex s = new Complex(0, 0);
  for(int i=0; i<data.length; i++)
    s = s.add(data[i]);
  return s;
}

Честное исполнение такого кода на массиве из миллиона элементов порождает миллион временных объектов, провоцируя вызов GC. Как несомненная победа преподносится то, что в методе sum JIT "понимает", что наружу s уезжает только в момент return; так что можно положить s на стек, и сконструировать объект в куче только для конечного результата.

Ну так в дотнете этого не делается в основном потому, что Complex в нём — value-тип, и s будет жить на стеке безо всякого escape analysis.
Кроме того, джава в массиве Data держит указатели на Complex, что приводит двух-трёхкратному росту потребления памяти и примерно двукратному замедлению кода итерирования по массиву (это если оптимистично предположить идеальный для джавы сценарий, когда сами экземпляры Complex лежат в памяти в том же порядке, как и ссылки на них в массиве — в силу удачной инициализации или недавней сборки мусора).
А дотнет держит экземпляры Complex прямо в массиве, без лишней косвенности и object headers.
Так что в этой гонке он выигрывает ещё до того, как джава выходит на забег.
Чтобы осмысленно рассуждать о недостатках escape analysis в дотнете, нужно показывать более убедительные примеры, чем "у меня тут джава смогла подавить один объект, а дотнет — не смог".
На производительность перемещение единичных объектов на стек не особо влияет. Влияют массовые операции, когда подавляется конструирование O(N) объектов.

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

Покажите пример, где дотнет проверяет на null дважды.

V>Новомодные nullable-нотации не помогают сэкономить на "системных" проверках аж никак, они помогают сэкономить только на юзверских проверках.

Всё верно, так и должно быть.
V>Т.е. nullable-подход в дотнете не является строгим. Ты можешь вернуть null или подать null в кач-ве аргумента, даже если те описаны как non-nullable.
Именно. Потому что нет гарантий, что код по обе стороны вызова скомпилирован с одной и той же конфигурацией нуллабельности.

V>И одновременно с этим обрезает оптимизации, т.е. не позволяет выкидывать ненужный код. ))

Какой именно?

V>Во всей этой истории я зацепился из-за твоего любимого взятого тона в адрес плюсов, мол, херня какая-то происходит.

V>Нет, не херня. В каждом конкретном случае или никакой оптимизации при UB не происходит вовсе (как в MSVC), либо происходит такая оптимизация, которая считает, что ошибочная ситуация произойти не должна.
Ну так это и есть херня, т.к. поведение кода радикально меняется при оптимизации и без неё. В итоге можно успешно отлаживаться, а в релизе получить булшит.
Вот вы только что с гордостью привели пример, как здорово работает код с UB в MSVC. Ну так это и провоцирует писать код в стиле приведённого мной примера — "а чо, я же знаю, что на самом деле будет происходить!".

V>Clang традиционно более щепетилен — поэтому он всегда создаёт ненужный объект в моём сниппете.

V>Но Clang — это не про производительность, это некий референс плюсов как таковых.
Теперь уже и про производительность. Потому что в LLVM вваливают очень много ресурсов, в том числе — и в оптимизации.

V>GCC — это существо из рил ворлд, поэтому исследует объекты на предмет побочных эффектов и убегания зависимостей, и если их нет — может "уничтожать" объекты, т.е. не конструировать их вовсе, а брать только описанную функциональность. Единственным "побочным эффектом" тут будет лишнее выделение памяти аккурат перед AV, что побочным эффектом не очень-то и считается.

Тут нет выделения памяти перед AV.

V>Лично меня тут удивило чуть другое — когда-то MSVC в деле оптимизаций драл GCC в хвост и гриву, а сейчас малость сдал позиции...

V>Но посмотрим, посмотрим...

V>В GCC аналогично.

Отож.

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

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

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

V>Ты допускаешь ill-formed код в своих рассуждениях, просто ждешь от компилятора некоей предсказуемой реакции на такой код в рантайм.

V>Т.е. утекание ошибки в продакшен — "а чо такого?" ))
Ну так вы же ровно этого же хотите от дотнета — чтобы он вам разрешил вызывать методы на null объектах. С т.з. C# это — ill-formed код.
Просто этот ill-formed код всегда ведёт себя предсказуемо. А в плюсах он может себя вести одним способом, а может — другим.

V>Это спор слепого со зрячим.

Да я уже понял.
V>Мы были слепы еще примерно в 90-х. Ближе к концу пошли анализаторы и общее понимание важности well-formed кода.
Ваша слепота частично развеялась за последние несколько дней, но ещё не до конца.
Вот буквально только что, пять дней тому, вы писали:

В данном случае ты даёшь оценку своим фантазиям, бо архитектуры/компиляторы явно определяют своё поведение при переполнении.
Т.е. UB тут для стандарта в общем, но не для конкретной связки платформы и компилятора.

То есть не понимали разницы между implementation-specific behavior и undefined behavior. А тут, гляди-ка, недели не прошло — начало брезжить понимание важности well-formed кода.

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

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

V>Помнишь себя в 2004-2005-х годах как ты вещал с трибун: "Представьте, что сейчас вы напишете код, а потом он будет исполняться намного быстре на будущих версиях платформы!!!"

V>Дудки!
Ну так самое забавное, что именно так и произошло. Вы прямо в этой ветке приводите примеры кода, который стал исполняться намного быстрее на будущих версиях платформы.

V>Сначала убери из кода потенциальные ошибки. ))

В дотнете потенциальных ошибок изначально меньше.
V>Например, в дотнете (и особенно в Джаве) есть ошибки, когда в метод подают не копию объекта, а сам объект, т.е. провоцируют убегание побочных эффектов, а компилятор помочь не может, т.к. он понятия не имеет о семантике.
Это вы сейчас про defensive copying или про что? Мне прямо интересно, есть ли пределы вашей некомпетентности.