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

Сообщение Re[21]: Откуда такая неизбывная приверженность к константам? от 29.10.2024 13:18

Изменено 19.11.2024 6:44 netch80

Re[21]: Откуда такая неизбывная приверженность к константам?
Здравствуйте, Евгений Музыченко, Вы писали:

N>>Тему с накопительным поведением вы проигнорировали.

ЕМ>Я задал вопрос: "Зачем нужны непременно накопительные? Как часто в них возникает потребность?". Вы не ответили.

На примере IEEE754 мы видим, как именно они нужны. Не проверять после каждой операции, а проверять после группы операций. Так как?

N>>Авторы считают, что отсутствие зависимости по скрытому значению — да, помогает в реализации.

ЕМ>В каком смысле это значение более "скрыто", нежели значения регистров или ячеек памяти?

В том, что не названо явно в команде, но присутствует в ряде команд. Причём в вариантах x86, PDP-11, VAX, M68k и пачки других — неустранимо. ARM позволяет у большинства команд их не менять, и это уже большой прогресс.
(У тех архитектур ещё и во времена до тотального OoO любили ставить правило, что команда те флажки, которые ей не нужно ставить согласно логике, жёстко объявлены неизменными. А это и значит зависимость от предыдущего значения — то, от чего при OoO стараются уйти.

N>>у x86 то же самое в векторных операциях... потому что не может быть одного флага результата на 4-8-16 разных операций впараллель

ЕМ>Насколько часто при векторных операциях необходимы ветвления по промежуточным результатам? Там, как правило, безусловные, "пакетные" алгоритмы.

А вы посмотрите, как их укладывают в этом случае. Часто приводят пример с операцией типа r = (a[i] > b[i]) ? c[i] : d[i] в цикле; это, конечно, пример искусственный настолько же, насколько вычисление чисел Фибоначчи, но именно этим и показателен. В реале часто встречаются менее жёсткие версии, но всё же где надо делать развилки. Я получал лично такое в своём коде, когда векторизовывался какой-нибудь проход по тексту с поиском конкретных символов.

ЕМ>>>Реализация этих проверок в топологии/микрокоде процессора всегда дешевле, чем в каждой из выполняемых на нем программ.

N>>Ну и где они в x86?
ЕМ>Во флагах состояния, вестимо. Пусть и не все, хотелось бы больше.

Ну вот и проверяются у всех, кроме деления, явной командой типа jc или jo. А у деления — неявно и неустранимо. Неаккуратненько-с.

ЕМ>>>Потому, что перенос очень часто является следствием [i]нормального
выполнения, и как-то специально его обрабатывать не требуется,
N>>Нет здесь такой несимметричности.
ЕМ>Если сейчас навскидку скачать любой бинарник и дизассемблировать его — уверены, что действий со знаковыми величинами, при которых штатно возникает переполнение, найдем примерно столько же, сколько и для беззнаковых?

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

N>>где говорят про экономию ресурсов

ЕМ>Экономия на флагах состояния на фоне сложности любого процессора даже 30-40-летней давности — это фикция, тех ресурсов там совершенно ничтожное количество, исчисляемое единицами логических элементов.

А теперь просто рассмотрите это как ещё 4 регистра (а в x86 даже 6, есть ещё PF и AF), которые надо отдельно учитывать во всём дизайне включая переименование регистров.

N>>где про резкое облегчение дизайна за счёт отсутствия единого регистра флагов и за счёт этого лучшую параллельность.

ЕМ>Тоже непонятно, где здесь может быть "резкое облегчение". Параллельное выполнение даже простых арифметических операций требует наличия нескольких полноценных АЛУ, а флаги состояния из такого АЛУ выходят либо сами по себе, либо путем добавления единиц логических элементов. Некоторое упрощение получить можно, "резкое" — тупо неоткуда.

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

N>>Заметьте, Intel в своём проекте APX намеревается сделать то же самое.

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

О. Значит, таки это при каких-то условиях возможно?

ЕМ>Но и x86, и ARM проектировались во времена, когда написание кода человеком на ассемблере использовалось достаточно широко.


Верно. Вся линия RISC уже открыто декларировалась на то, что код будет в основном генерироваться компилятором и надо ему облегчать работу, а не человеку. "Под нож" в первую очередь попали в этом случае сложные методы адресации — как PDP-11 и вслед ей VAX и M68k любили всякие @(Rn)+ ... а x86 не успел их ввести и поэтому меньше пострадал в начале 90-х.

ЕМ>>>Это было бы следуюшим по разумности вариантом при отказе от исключения.

N>>Как раз более разумно, чем исключение
ЕМ>Если предполагается, что программа всегда будет генерироваться автоматически, и статистика показывает, что затраты на проверку после каждого деления потеряются на общем фоне, то да.

Достаточно не "всегда", а в подавляющем большинстве случаев. Что и имеем.

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

N>>Кажется, единственный вариант, при котором это возможно, это арифметика "по модулю". Назвать такое "очень часто" — это слишком сильная натяжка.
ЕМ>Например, там, где нуль/ненуль нужно преобразовать в 0/1 или 0/-1, компиляторы вовсю используют комбинации из neg, sbb, inc и and.

Я читал Hackerʼs Delight, да. Там таких примеров много. Но для x86 всё-таки test + setcc проще. Для ARM аналогично, с поправкой, что test == ands, и варианты csel позволяют сразу формировать значения типа -1 (csinv по условию). Для RISC-V, для сравнения, bool(rs==0) пишется как sltiu rd,rs,1, а bool(rs!=0) — sltu rd,x0,rs. Вообще одна команда. Ну если нужно -1, то да, ещё инвертировать знак (sub rd,x0,rd). То есть в таком режиме действие даже упрощается. И никакого таки скрытого переполнения

EM> А счетчики с переполнением часто используются для отслеживания небольших временнЫх интервалов в алгоритмах реального времени.


И от общего количества кода для процессора таких будет где-то 0.001%. Вряд ли больше. Ну ладно, в очень embedded — 0.1%.

N>>мы знаем, что "нормальных, запланированных" ситуаций с переполнениями почти нет

ЕМ>Хорошо Вам, что "вы знаете". Я вот их использую регулярно, и столь же регулярно вижу в коде, который делает компилятор (те же преобразования int в bool).

И он регулярно использует эту извращённую последовательность из четырёх команд? Зачем?

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


Да, их мало. Я действительно не могу вспомнить характерные примеры.

Но вы сделали ошибку вот в чём: вы отождествляете беззнаковые и модульные типы. А это разные типы, в нормальных местах, а не извращенческих вроде C/C++.
В честных беззнаковых тоже переполнение как штатная ситуация как-то не присутствует.

ЕМ>>>чтобы в ситуации, когда что-то пошло не так, не было риска забыть проверить возвращаемое значение в ситуации, когда не существует заведомо безопасного значения для нештатной ситуации.

N>>Ну и почему на это плюют во всяких x86 для трёх операций из четырёх (это ещё и если не считать сдвиги за арифметику со степенью двойки, тогда окажется, что в 7 из 8)?
ЕМ>Где можно найти примеров, в которых это "наплевательство" требуется обставлять ловушками?

Вообще-то где угодно, где нет гарантии согласно проверенным локально данным, что переполнения нет.

ЕМ>>>Тогда и в плюсовых исключениях тоже нет смысла. Не в их неправильном использовании, а в механизме, как твковом.

N>>Передёргиваете. Я ясно сказал, что возможность проверки есть и там, где её применяют, проблема детектируется. Может, чуть заморочно.
ЕМ>Пытаюсь увидеть разницу, но не вижу. Если в процессоре не возбуждать исключения при делении на нуль, а возвращать специальное значение, необходимо добавлять проверку после каждого деления; случаев, когда программа может безопасно использовать специальное значение, найдется очень мало.

И в этом предложении можно заменить "деление" на "умножение", "сложение", "вычитание" с ровно таким же успехом. Только вместо деления на 0 будет какое-то из переполнений. Почему тогда не делать, что любая подобная операция вызывает исключение?

N>>случаи проблем из-за непойманного переполнения при остальных трёх арифметических операциях — уже десятки.

ЕМ>Этих тоже хватало. Но, если б каждое такое переполнение порождало исключение, было бы еще хуже.

Чем хуже? Везде некорректное значение, которое ломает дальнейшую логику.

N>>Проверка каждого результата при укладке в целевую переменную, на её границы, плюс операций с базовым размером значения (как int и long в C) на переполнения. Не сильно большая цена за реальную безопасность.

ЕМ>Это было бы полезно во всех без исключения языках, но не безусловно, а при возможности независимого управления каждым видом проверок.

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

N>>Ну вот вы почему-то постановили, что константы разрядности в виде 32 и 64 они оправданны, а остальные — нет.

ЕМ>Это не "мы постановили", это непосредственно вытекает из физических, железных характеристик архитектуры.

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

EM> А числа вроде 2 Гб для границы АП, или 480 Мб для бессбойной загрузки Win95, ниоткуда не вытекают, а возникают произвольно.


Половина всего адресного размаха — что тут произвольного?

N>>жалобы при переходе на S/360, что 32-битный float был заметно хуже 36-битного по свойствам.

ЕМ>Вроде неудивительно. Чем не устраивал 64-битный?

Дороже в разы.

N>>Надо было таки оставить разрядности типа 36 и 72?

ЕМ>Они и так оставались доступными через 64- и 128-разрядные. Или Вы о хранении чисел в памяти?

Нет, я о том, что был 36-битный формат float соответственно размеру слова машины, и у него были нормальные характеристики. А когда S/360 форсировала те же 32 бита слова, и пришлось влезать в 32 бита для float, пришлось делать диверсии в формате — начиная с base16 вместо base2, от чего резко ухудшилась точность.

64-битный float был сильно дороже, 128-битного не было.

N>>увеличение выше 2GB реально стоило — мороки программистам и производительности ядру. Сплошная перекачка данных между буферами.

ЕМ>Почему при границе в 2 Гб "сплошной перекачки" нет, а при смещении на те же 3 Гб вдруг потребовалась?

Это зависело не от границы, а от объёма RAM. Чтобы не было перекачек, нужно, чтобы виртуальное адресное пространство полностью покрыло 2*RAM+MMIO, ещё и с потерями на фрагментацию каталогов. При переходе примерно к 1.5GB RAM это перестало выполняться. Хотеть деление виртуального пространства 3:1 или 3.5:0.5 начали уже при наличии 4-8GB RAM, до этого не было таких желающих.
Re[21]: Откуда такая неизбывная приверженность к константам?
Здравствуйте, Евгений Музыченко, Вы писали:

N>>Тему с накопительным поведением вы проигнорировали.

ЕМ>Я задал вопрос: "Зачем нужны непременно накопительные? Как часто в них возникает потребность?". Вы не ответили.

На примере IEEE754 мы видим, как именно они нужны. Не проверять после каждой операции, а проверять после группы операций. Так как?

N>>Авторы считают, что отсутствие зависимости по скрытому значению — да, помогает в реализации.

ЕМ>В каком смысле это значение более "скрыто", нежели значения регистров или ячеек памяти?

В том, что не названо явно в команде, но присутствует в ряде команд. Причём в вариантах x86, PDP-11, VAX, M68k и пачки других — неустранимо. ARM позволяет у большинства команд их не менять, и это уже большой прогресс.
(У тех архитектур ещё и во времена до тотального OoO любили ставить правило, что команда те флажки, которые ей не нужно ставить согласно логике, жёстко объявлены неизменными. А это и значит зависимость от предыдущего значения — то, от чего при OoO стараются уйти.

N>>у x86 то же самое в векторных операциях... потому что не может быть одного флага результата на 4-8-16 разных операций впараллель

ЕМ>Насколько часто при векторных операциях необходимы ветвления по промежуточным результатам? Там, как правило, безусловные, "пакетные" алгоритмы.

А вы посмотрите, как их укладывают в этом случае. Часто приводят пример с операцией типа r[j] = (a[j] > b[j]) ? c[j] : d[j] в цикле; это, конечно, пример искусственный настолько же, насколько вычисление чисел Фибоначчи, но именно этим и показателен. В реале часто встречаются менее жёсткие версии, но всё же где надо делать развилки. Я получал лично такое в своём коде, когда векторизовывался какой-нибудь проход по тексту с поиском конкретных символов.

ЕМ>>>Реализация этих проверок в топологии/микрокоде процессора всегда дешевле, чем в каждой из выполняемых на нем программ.

N>>Ну и где они в x86?
ЕМ>Во флагах состояния, вестимо. Пусть и не все, хотелось бы больше.

Ну вот и проверяются у всех, кроме деления, явной командой типа jc или jo. А у деления — неявно и неустранимо. Неаккуратненько-с.

ЕМ>>>Потому, что перенос очень часто является следствием нормального выполнения, и как-то специально его обрабатывать не требуется,

N>>Нет здесь такой несимметричности.
ЕМ>Если сейчас навскидку скачать любой бинарник и дизассемблировать его — уверены, что действий со знаковыми величинами, при которых штатно возникает переполнение, найдем примерно столько же, сколько и для беззнаковых?

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

N>>где говорят про экономию ресурсов

ЕМ>Экономия на флагах состояния на фоне сложности любого процессора даже 30-40-летней давности — это фикция, тех ресурсов там совершенно ничтожное количество, исчисляемое единицами логических элементов.

А теперь просто рассмотрите это как ещё 4 регистра (а в x86 даже 6, есть ещё PF и AF), которые надо отдельно учитывать во всём дизайне включая переименование регистров.

N>>где про резкое облегчение дизайна за счёт отсутствия единого регистра флагов и за счёт этого лучшую параллельность.

ЕМ>Тоже непонятно, где здесь может быть "резкое облегчение". Параллельное выполнение даже простых арифметических операций требует наличия нескольких полноценных АЛУ, а флаги состояния из такого АЛУ выходят либо сами по себе, либо путем добавления единиц логических элементов. Некоторое упрощение получить можно, "резкое" — тупо неоткуда.

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

N>>Заметьте, Intel в своём проекте APX намеревается сделать то же самое.

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

О. Значит, таки это при каких-то условиях возможно?

ЕМ>Но и x86, и ARM проектировались во времена, когда написание кода человеком на ассемблере использовалось достаточно широко.


Верно. Вся линия RISC уже открыто декларировалась на то, что код будет в основном генерироваться компилятором и надо ему облегчать работу, а не человеку. "Под нож" в первую очередь попали в этом случае сложные методы адресации — как PDP-11 и вслед ей VAX и M68k любили всякие @(Rn)+ ... а x86 не успел их ввести и поэтому меньше пострадал в начале 90-х.

ЕМ>>>Это было бы следуюшим по разумности вариантом при отказе от исключения.

N>>Как раз более разумно, чем исключение
ЕМ>Если предполагается, что программа всегда будет генерироваться автоматически, и статистика показывает, что затраты на проверку после каждого деления потеряются на общем фоне, то да.

Достаточно не "всегда", а в подавляющем большинстве случаев. Что и имеем.

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

N>>Кажется, единственный вариант, при котором это возможно, это арифметика "по модулю". Назвать такое "очень часто" — это слишком сильная натяжка.
ЕМ>Например, там, где нуль/ненуль нужно преобразовать в 0/1 или 0/-1, компиляторы вовсю используют комбинации из neg, sbb, inc и and.

Я читал Hackerʼs Delight, да. Там таких примеров много. Но для x86 всё-таки test + setcc проще. Для ARM аналогично, с поправкой, что test == ands, и варианты csel позволяют сразу формировать значения типа -1 (csinv по условию). Для RISC-V, для сравнения, bool(rs==0) пишется как sltiu rd,rs,1, а bool(rs!=0) — sltu rd,x0,rs. Вообще одна команда. Ну если нужно -1, то да, ещё инвертировать знак (sub rd,x0,rd). То есть в таком режиме действие даже упрощается. И никакого таки скрытого переполнения

EM> А счетчики с переполнением часто используются для отслеживания небольших временнЫх интервалов в алгоритмах реального времени.


И от общего количества кода для процессора таких будет где-то 0.001%. Вряд ли больше. Ну ладно, в очень embedded — 0.1%.

N>>мы знаем, что "нормальных, запланированных" ситуаций с переполнениями почти нет

ЕМ>Хорошо Вам, что "вы знаете". Я вот их использую регулярно, и столь же регулярно вижу в коде, который делает компилятор (те же преобразования int в bool).

И он регулярно использует эту извращённую последовательность из четырёх команд? Зачем?

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


Да, их мало. Я действительно не могу вспомнить характерные примеры.

Но вы сделали ошибку вот в чём: вы отождествляете беззнаковые и модульные типы. А это разные типы, в нормальных местах, а не извращенческих вроде C/C++.
В честных беззнаковых тоже переполнение как штатная ситуация как-то не присутствует.

ЕМ>>>чтобы в ситуации, когда что-то пошло не так, не было риска забыть проверить возвращаемое значение в ситуации, когда не существует заведомо безопасного значения для нештатной ситуации.

N>>Ну и почему на это плюют во всяких x86 для трёх операций из четырёх (это ещё и если не считать сдвиги за арифметику со степенью двойки, тогда окажется, что в 7 из 8)?
ЕМ>Где можно найти примеров, в которых это "наплевательство" требуется обставлять ловушками?

Вообще-то где угодно, где нет гарантии согласно проверенным локально данным, что переполнения нет.

ЕМ>>>Тогда и в плюсовых исключениях тоже нет смысла. Не в их неправильном использовании, а в механизме, как твковом.

N>>Передёргиваете. Я ясно сказал, что возможность проверки есть и там, где её применяют, проблема детектируется. Может, чуть заморочно.
ЕМ>Пытаюсь увидеть разницу, но не вижу. Если в процессоре не возбуждать исключения при делении на нуль, а возвращать специальное значение, необходимо добавлять проверку после каждого деления; случаев, когда программа может безопасно использовать специальное значение, найдется очень мало.

И в этом предложении можно заменить "деление" на "умножение", "сложение", "вычитание" с ровно таким же успехом. Только вместо деления на 0 будет какое-то из переполнений. Почему тогда не делать, что любая подобная операция вызывает исключение?

N>>случаи проблем из-за непойманного переполнения при остальных трёх арифметических операциях — уже десятки.

ЕМ>Этих тоже хватало. Но, если б каждое такое переполнение порождало исключение, было бы еще хуже.

Чем хуже? Везде некорректное значение, которое ломает дальнейшую логику.

N>>Проверка каждого результата при укладке в целевую переменную, на её границы, плюс операций с базовым размером значения (как int и long в C) на переполнения. Не сильно большая цена за реальную безопасность.

ЕМ>Это было бы полезно во всех без исключения языках, но не безусловно, а при возможности независимого управления каждым видом проверок.

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

N>>Ну вот вы почему-то постановили, что константы разрядности в виде 32 и 64 они оправданны, а остальные — нет.

ЕМ>Это не "мы постановили", это непосредственно вытекает из физических, железных характеристик архитектуры.

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

EM> А числа вроде 2 Гб для границы АП, или 480 Мб для бессбойной загрузки Win95, ниоткуда не вытекают, а возникают произвольно.


Половина всего адресного размаха — что тут произвольного?

N>>жалобы при переходе на S/360, что 32-битный float был заметно хуже 36-битного по свойствам.

ЕМ>Вроде неудивительно. Чем не устраивал 64-битный?

Дороже в разы.

N>>Надо было таки оставить разрядности типа 36 и 72?

ЕМ>Они и так оставались доступными через 64- и 128-разрядные. Или Вы о хранении чисел в памяти?

Нет, я о том, что был 36-битный формат float соответственно размеру слова машины, и у него были нормальные характеристики. А когда S/360 форсировала те же 32 бита слова, и пришлось влезать в 32 бита для float, пришлось делать диверсии в формате — начиная с base16 вместо base2, от чего резко ухудшилась точность.

64-битный float был сильно дороже, 128-битного не было.

N>>увеличение выше 2GB реально стоило — мороки программистам и производительности ядру. Сплошная перекачка данных между буферами.

ЕМ>Почему при границе в 2 Гб "сплошной перекачки" нет, а при смещении на те же 3 Гб вдруг потребовалась?

Это зависело не от границы, а от объёма RAM. Чтобы не было перекачек, нужно, чтобы виртуальное адресное пространство полностью покрыло 2*RAM+MMIO, ещё и с потерями на фрагментацию каталогов. При переходе примерно к 1.5GB RAM это перестало выполняться. Хотеть деление виртуального пространства 3:1 или 3.5:0.5 начали уже при наличии 4-8GB RAM, до этого не было таких желающих.