Сообщение 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, до этого не было таких желающих.
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, до этого не было таких желающих.
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, до этого не было таких желающих.