Pull to refresh

Comments 46

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

В том-то и дело, что для железа выгоднее, когда код вообще не использует 8- и 16-битную арифметику

Главный вопрос - все эти варианты, что занимают меньше места, действительно выполняются с той же скоростью что и их "жирный" аналог?

Всё зависит от процессора. И на большинстве современных процессоров эти короткие версии исполняются со скоростью от медленно до… катастрофически медленно.

Ни о какой эффективности речь не идёт в принципе.

Можно было бы предположить, что речь идёт о какой-нибудь странной встраиваемой системе на базе какого-нибудь 386EX с дикими ограничениями по памяти… но тогда причём тут x86-64?

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

Всех этих команд уже много десятилетий нет в железе. Они эмулируются в микрокоде и делается это строго для возможности запускать дикий легаси. Отсюда вся неэффективность.

В Turbo Pascal вроде был ещё один байтовый тип. Так и назывался - BYTE. Или я путаю?

Был. А еще вроде enum занимал один байт.

Enum и сейчас, в C++, может один байт занимать. А в Rust в один байт может быть запакован и enum и Option<enum>.

Да вообще много чего бывает, но если эффективность программы мерить количеством байт кода, не обращая внимание на то, сколько времени оные байты исполняются“… можно много интересного наизобретать.

На современных процессорах x86 использование 8- и 16-битных регистров бьёт по производительности. В частности из-за того, что называется source merge. Собственно, все регистры общего назначения 64-битные. 32-битные инструкции обнуляют старшую часть регистра, а 8- и 16-битные её сохраняют --- очевидно это достигается исполнением дополнительных микроинструкций.

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

Вот, да. Конечно приятнее, когда код более плотный, но не такой ценой.

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

Кто знает, может с точки зрения архитектуры процессора и закладки туда шпионских примочек, SPL и прочие вещи очень даже логичны... ?

Нет, всё банальнее и проще. Они логичны с точки зрения устройства декодера.

Статья оставляет какое-то дико странное впечатление.

С одной стороны — автор утверждает, что у него есть какой-то там компилятор.

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

Ибо, ну… О ужас, INTO отобрали! А ведь было так удобно: приписал INTO и инструкция, занимавшая четверть такта начала занимать в двадцать раз больше времени! Зашибись!

И это если мы говорим о каком-нибудь Skylake или Haswell'е, где они 5-6 тактов занимает, а не о AMD'шном Bulldozer'е, где она 24 такта занимает (а получаемое замедление от использования, соотвественно, программу почти в сто раз замедляет!). А началось это, прости господи, с классического Pentium, где она уже требовала 4 такта (больше, чем на 80486), притом что простая арифметика уже могла исполняться за две инструкции за такт. Правда AMD, как раз, долгое время достаточно эффективно её реализовывала, но так как это никому не было нужно, то… см. что они в “тяжёлой технике” с ней сделали.

Но на Intel платформе INTO нельзя было использовать, к моменту её отмены, уже лет 10 как и дико странно, что вы о ней вообще вспомнили.

Рассуждения про однобайтовые регистры — тоже вызывает недоумение. Ещё раз: компилятор у вас есть или его нет? Ибо почти все виденные мною компиляторы считают, что на классической IA32 платформе однобайтовых регистров не 8, но 4: AL/CL/DL/BL. И всё. AH/CH/DH/BH как бы и не существуют вовсе. Только если руками ассемблерную вставку оформляете — тогда они могут быть использованы.

Причём это было настолько твёрдо установленным фактом уже четверть века назад, что Pentium Pro под это был заточен (на чём и погорел: оказалось, что компиляторы компиляторами, а вот ручками написанный на ассемблере код Windows 95 устроен иначе).

Соотвественно с точки зрения компилятора на x86-64 всё просто и логично: от 4 регистров перешли к 15. Увеличив их количество в три с лишним раза. Ну а что AH/CH/DH/DH ещё существуют и их иногда можно поиспользовать — так это, опять-таки, для переноса ручками написанного кода.

Разговоры про хранение двух переменных в одном физическом регистре тоже “доставляют”… ещё раз: оно у вас, извините, есть? Бенчмарки можно какие-нибудь посмотреть, сравнить с LLVM или GCC? Или это, опать, сферические рассуждения в вакууме?

Так, эта, компилятор — он таки есть или его таки нет?

Компилятор, вероятно, есть. Но Optimization Reference Manual от Intel и AMD читать, похоже, умеют не все (-:

Вот, да, достаточно взглянуть на какой-нибудь godbolt чтобы посмотреть как современные компиляторы оптимизируют код. Иногда программа которая содержит в 5 раз больше машинных команд работает в 2-3 раза быстрее такой вот компактной.

Да и в целом не ясно зачем может понадобиться работать с 8-ми вычислениями и ловить переполнения, когда есть широкие регистры?

Да, похоже что-то (правда не очень понятно что) в природе имеется. Но я прошёл по первой ссылке и обнаружил там претензии на Аристотеля. Который целый трактат написал, где выяснял, с помощью теоретических построений, сколько у мухи ног.

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

Читаешь вот это вот:

Например, взять те же DAA и DAS. Они однобайтные, не имеют параметров и (редкий случай!) не имеют исключительных ситуаций. Т.е. обрабатываются в процессоре самым простым образом. Зачем же было отключать несложную и эффективную аппаратную поддержку целого класса объектов и задач? Неужели только ради убирания нескольких примитивных инструкций?

И возникает ощущение, что столнулся с каким-то пришельцем из прошлого века, этаким Фи́лип Джей Фрайем. Несложная и эффективная аппаратная поддержка? А ничего, что уже в Pentium 4 больше 20 лет назад DAA и DAS начали требовать по сотне тактов процессора? Prescott, правда, ускорился, всего-навсего 29 тактов требуется…

А эффективность кода оценивается, барабанная дробь… в байтах.

Мы эта, вообще в каком веке живём?

Если вас эффективность не волнует (а если вы всё ешё используете DAA/DAS и INTO, то она вас точно не волнует), то почему, вдруг, начали волновать размеры?

Что вы пытаетесь создать, для кого и причём тут x86-64?

Откуда Вы взяли такие такты? Со времен Пентиума DAA - 3 такта, DAS - 3 такта, INTO - срабатывание 56 тактов, пропуск - 4 такта. Откуда взялись сотни тактов? Современные процессоры могут добавить задержку от 0.33 до 1.5 тактов. И, да, меня волнует эффективность моих программ, поэтому и использую аппаратную поддержку точных вычислений. И волнует надежность, поэтому и нужен аппаратный контроль переполнения.

Откуда Вы взяли такие такты?

А откуда их вообще все люди, занимающиеся компиляторами, берут? Ну, кроме тех, кто непосредственно работает в AMD или Intel? Из всем известных таблиц, очевидно.

Вообще дико видеть человека, рассуждающего о написании компилятора и не знаюшего об Агнере.

Со времен Пентиума DAA - 3 такта, DAS - 3 такта

Угу. Вот начиная с Пентиума на них и начали “забивать”. 80486 — 2 такта, Pentium — 3 такта (и параллельно ничего другого исполняться не может, то есть уже тогда, уже на том самом Pentium'е INTO в 3-6 раз хуже, чем JO, который только V pipe занимает на один такт, давая запустить на U pipe какое-нибудь сложение), дальше — ещё хуже.

Чего вы хотите от “инструкций, которые более не используются”? Это из той же оперы, что и LOOP какой-нибудь, который на том же Pentium уже тоже 5-6 тактов занимают (и который, кстати, в x80-86 режиме остался и даже, на AMD, не сильно тормозит, но если ваш код используется ещё и на Intel'овских процах, то, разумеется, LOOP вы использовать не будете)

Откуда взялись сотни тактов?

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

Впрочем не спрорю: на большинстве процессоров вы, скорее, получите десятикратное замедление, чем стократное, 50-100 тактов это только на отдельных “особо выдающихся” архитектурах.

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

Дык вы ж, вроде как, компилятор пишите? Причём тут вообще надёжность? У вас JO уже ненадёжным стал?

Вы в курсе того, что делают другие на этом поприще? Про Intel MPX знаете? А про то, что его использование оказалось бессмысленным (и его выпилили и из GCC и из ядра Linux)?

Как я уже написал в другом месте: такое ощущение что читаешь записки Фрая, свалившегося в криогенную камеру и провалявшегося в ней лет 20-30.

И не знающего ни того, как работают современные компиляторы, ни того, как работают современные процессоры.

Но при этом поддерживающего x86-64.

Странно это всё выглядит.

Странно это всё выглядит.

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

И это даже в чем-то хорошо - иногда встяхивает общее "потому что это так" болотце.

И это даже в чем-то хорошо - иногда встяхивает общее "потому что это так" болотце.

Когда вы в поседний раз такое видели?

Во всех областях развитие, действительно, проходит “сургучно-верёвочный” период.

Когда талантливые одиночки, без какого-образования могут изобрести нечто революционное.

Как громоотвод, который был изобретён человеком, которого мы знаем, как политика.

В разработке компиляторов такое время тоже было. Но кончилось. Где-то четверть века назад.

Может ли сегодня самоучка создать компилятор? Может, конечно. Но он будет примерно где-то на том же уровне, что и четверть века назад.

Об эффективности можно будет говорить крайне условно (всё будет гораздо, гораздо хуже, чем четверть века назад, ибо железо в те годы было гораздо проще, а главное — не менялось так сильно год от года).

P.S. Вообще я глянул на сайт и мне стало грустно и обидно за Российскую космонавтику. У неё ведь нет будущего с такими самоучками! Потому что они ведь сделали всё возможное и невозможное для того, чтобы все их разработки сгинули вместе с ними, чтобы их невозможно было использовать никому и никак после того, как они уйдут на пенсию. Ну ладно, не совсем невозможно, если археологов привлечь, то может что-то ещё можно и спасти. Но ведь этого никто не делает, так что шансов мало. И ладно бы если бы они, таким путём, заработали себе на домик на Гаваях. Так нет же — этого ведь тоже нет! Вот кем надо быть, чтобы сознательно гробить дело своей жизни? И даже не понимать, что этим всё кончится?

UFO just landed and posted this here

Может это транслятор в 1С? Тогда это многое бы объяснило D:

Ну, судя по сайту автора, это компилятор PL/I. Только там нет исходников, а у меня нет винды, поэтому я не могу запустить бинарную сборку.

А даже если запустите? Тут же главный вопрос не в том даже “существует оно или нет”, а скорее “а зачем оно такое кому-то нужно”.

То есть, с одной стороны, можно предположить, что это жуткое легаси, которое боятся перевести на что-то более вменяемое. Ибо как работает та каша, что у нас имеется мы не знаем, а использовать-то её как-то нужно. Ладно. Бывает.

Но если эффективность никого не волнует (волновала бы — давно бы адаптировали что-то более эффективное, чем слегка допиленный компилятор четвертьвековой давности), то почему, вдруг, стал волновать режим x86-64 ?

Ну, мне иногда интересно потыкать палочкой в разные компиляторы и посмотреть, какой код они генерируют. Не то, чтобы в данном случае был какой-то практический смысл. В конце концов, если бы PL/I был кому-то нужен, компиляторы для него писали бы разработчики процессоров (-:

На все языки, которые "кому-то нужны" раработчиков процессоров не хватит.

Но даже без этого есть куда более эффеквтивные методы, чем попытки наложить патчи поверх патчей в течении многих лет.

x86 вообще представляет собой некое нагромождение слоев и возможностей, добавляемых с каждым новым релизом очередного процессора. При этом некоторые расширения могут отсутствовать, и их наличие еще нужно проверять какими-то специальным кодом типа CPUID.

Сама система регистров — очевидный пример такого нагромождения. Есть базовые регистры x86, регистры x64, регистры FPU, регистры MMX, регистры SSE и т.д. При этом некоторые регистры совмещены, некоторые — отдельные. Регистры FPU зачем-то организованы в виде стека, что выбивается из общей системы прямой индексации.

В системе команд нет встроенных команд MIN, MAX и CLAMP (это все делается конечно через проверки и условные переходы, но операции определения минимума и максимума достаточно частые, почему бы их не сделать командами?)

Нет команды RBIT (разворота битов в слове). Хотя места для того чтобы ее воткнуть имеются — например в унарной группе 0xF6 есть свободная позиция (TEST, <свободно>, NOT, NEG, MUL, IMUL, DIV, IDIV). Кто-то говорит что такая команда не нужна, т.к. редко используется. Но в ARM она есть, и кроме того это по смыслу фундаментальная операция.

Арифметические операции с насыщением хоть и существуют, но не для базовых регистров, а в MMX и SSE. Хотя наверное можно было бы сделать это префиксами для основных команд и регистров.

В FPU почему-то операции сравнения числа на равенство и неравенство с NaN всегда дают false, хотя какой в этом смысл? В результате код сравнения для float усложняется. При этом минимум и максимум с NaN дает второй аргумент (не NaN).

Система SIMD сделана на отдельных командах, хотя тоже напрашивается мысль — почему бы не сделать ее префиксами для стандартного набора команд? Есть же префиксы повторения операций (REPE, REPNE). Но и они работают не унифицированно, а только с ограниченным набором команд. Но конечно все это потребовало бы глубокого редизайна системы команд и регистров.

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

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

Напоминает и будет напоминать, пока какая-нибудь внешняя по отношению к IT-индустрии сила ситуацию не изменит.

Например если Россия или Китай, на уровне законодательства, заставят народ отказаться от Windows и пересесть на RISC-V или там E2k — ситуация может и измениться.

Если же рынок будет предоставлен сам себе, то никаких шансов нет.

Посмотрите на Apple, который уже 4 раза меняет процессорную архитектуру. Каждый раз это начинается с того, что выпуск железок на старой архитектуре принудительно прекращается: 68060 ни в одной модели не использовался, старшие модели PowerPC (перед переходом на x86) тоже были проигнорированы, как и Ryzen.

Ну и кто ещё контролирует какую-либо платформу так плотно, что может себе подобное позволить и не обанкротится, когда ниша будет занята конкурентами?

Регистры FPU зачем-то организованы в виде стека, что выбивается из общей системы прямой индексации.

Почти все странности в таких случаях объясняются “историческими причинами”. Изначально для FPU было выделено всего-навсего 8 опкодов и формат инструкции большего не предполагал: 3 бита в основном опкоде, ещё 3 в ModRM - и… крутись как хочешь.

Программы в те годы писали на ассемблере и стековая организация позволила совместить компактность кода и эффективность.

Что касается MIN и MAX, то они есть, но лучше бы их не было. Ибо MIN(1.0, NaN) или MAX(1.0, NaN) — это NaN, а вот MIN(NaN, 1.0) и MAX(NaN, 1.0) — это 1.0.

Ну так эффективнее было реализовывать, а что такой хоккей нафиг никому не нужен — так это мелочи.

Кто-то говорит что такая команда не нужна, т.к. редко используется. Но в ARM она есть, и кроме того это по смыслу фундаментальная операция.

Правильно говорят. Вот именно ARM и показывает почему она не нужна. Команду RBIT я встречал в живом виде только в двух случаях:

  • Для эмуляции RBIT (в интерпретаторах) — просто “шоб було”

  • В ARM - чтобы эмулировать CTZ (которого там нету).

Сама по себе она просто никому особо не нужна (хотя будет очень инетресно узнать что-нибудь о существовании алгоритма, где она применима), а CTZ нужен куда как чаще, потому логично что в x86 имеется LZCNT и TZCNT, но не RBIT.

В FPU почему-то операции сравнения числа на равенство и неравенство с NaN всегда дают false, хотя какой в этом смысл?

Смысл в возможности использовать языки созданные до повяления понятия NaN. Заметьте, что в уже упомянутом вами ARM NaN ведёт себя так же.

При этом минимум и максимум с NaN дает второй аргумент (не NaN).

То есть вы-таки в курсе о том, что они существуют?

Система SIMD сделана на отдельных командах, хотя тоже напрашивается мысль — почему бы не сделать ее префиксами для стандартного набора команд?

А в каком процессоре сделано так, как вы предлагаете, извините?

Но в какой-то момент нужно уже остановиться и полностью пересмотреть систему команд, провести полный рефакторинг всего — как минимум для повышения ясности самой архитектуры.

Отличное предложение и уже реализованное. Один раз, второй, третий, четвёртый, пятый… это то, что до пользователей дошло только! А сколько миллиардов было выкинуто на разработки, которые просто были выкинуты в корзину? Не сосчитать.

Кто знает, возможно это привело бы и к высвобождению транзисторов на кристалле, и к устранению каких-то уязвимостей, и к упрощению компиляторов

Главное, к чему бы это привело — к гневозможности запускать существующие бинарники и последующим убыткам.

Автор статьи — неординарная личность, зачем-то пилит поделку, которая ещё в 80е была создана под 64 бита.

Этим действительно люди почти не занимаются — но вот запускать имеющиеся у них бинарники они таки хотят. И если у них не получается — сильно огорчаются. А для разработчика процессора это кончается убытками.

UFO just landed and posted this here

Зачем вообще нужны эти R8-R15? Это что, RISC-процессор?

Когда доступно много регистров, компилятору становится проще жить. Не надо без конца спиливать переменные в память. Плюс, с добавлением дополнительных регистров пришло новое соглашение о передаче параметров в функции: первые 6 параметров передаются на регистрах.

первые четыре - через RCX, RDX, R8 и R9

UFO just landed and posted this here

Байтовые операции не нужны.

Сообщите свои соображения разработчикам AMD64. Очевидно, они их не учли и в результате имеется двадцать лишних байтовых регистров.

Они и не нужны. На самом деле компиляторы практически не используют их.

А вот сейчас стало обидно за компиляторы, которым надо реализовывать логические переменные true-false

По стандарту C++, например, во всех операциях переменные целочисленных типов, меньшьх int, должны быть преобразованы к int. Так сложилось, что в x86-64 тип int 32-битный. Поэтому вся целочисленная арифметика как минимум 32-битная.

GCC и LLVM так же генерируют 32-битные операции для bool-переменных: https://godbolt.org/z/1vqnjWezs

Правильно. Зачем нужны легковые автомобили, когда можно прекрасно ездить на самосвале. У него и кабина просторнее и обзор лучше.

UFO just landed and posted this here

Причём тут самосвал? Работа с регистрами размером с байт банально медленнее. Потому что у них идиотская семантика: прочитать регистр, изменить один или два байта, записать значение обратно.

Формально время на исполнение то же, но при этом могут возникать паразитная зависимость между данными, которая не позволяет исполнять инструкции спекулятивно.

У 32-битных регистров этой проблемы нет, так как в старшую половину регистра, при их использовании, записывается нуль и потому паразитных связей между не связанными между собой регистрами не возникает.

Уже Pentium Pro прекрасно параллелил такие штуки:

Не умел. Не надо сказок.

EAX в строке 1 и EAX в строке 4 мапятся на разные регистры регистрового файла, и блоки 1-3 и 4-6 исполняются в порядке (1, 4), (2, 5), (3, 6) - это утрированно, на самом деле операции ещё бьются на микроопы, и уже те переупорядочиваются при исполнении.

Вот только в вашем случае ничего переупорядочить на Pentium Pro нельзя ибо у блока процессора, этим занимающегося, нет информации о том, являются [EDI]и [ESI+8]одним и тем же местом в памяти или нет.

В последних процессорах появились кой-какие попытки иногда в этом разобраться и запустить чтение до записи.. Записи же в память поменять нельзя вообще никогда. MOV [EDI+8], EAXне может отработать до MOV [EDI], EAXни при каких условиях.

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

Исследования показали, что наилучшее количнество регистров — 32, если их больше, то становятся слишком длинными команды, если меньше — то приходится часто обращаться в память.

Но это если мы ISA делаем с нуля. Так как AMD пришлось прикручивать x86-64 к уже существующей ISA, то оказалось выгоднее 16 регистров сделать.

Более того, архитектура x86 явно запрещает переупорядочивать операции чтения. То есть нельзя поменять местами два чтения или две записи. Можно только чтение и запись.

Sign up to leave a comment.

Articles