Похвастался коллеге, что мы в ногу с прогрессом используем 64-разрядный транслятор, а он, небось, по старинке – 32-разрядный.
Ну и он попросил дать ему 64-разрядный компилятор. Задачи у него почти чисто вычислительные. Главный процесс – численное интегрирование. Поэтому производительность важна – чем быстрее считается, тем меньше шаг интегрирования можно задать.
Попробовал. Говорит, перетранслировал – коды стали больше (ну, это ожидаемо). А скорость стала ниже!
Я сначала не поверил. Забрал его тексты проверил. И вот, что получилось.
Процессор Intel Core i5-3210M 2.5 GHz. Стоит Windows-7.
Одна и та же задача. Результаты выдает одинаковые. Компилятор (не скажу с какого языка ) 64-разряда сделан из 32-х разрядного, поэтому команды очень похожие.
Размер кода увеличился на 9%, а скорость упала на 20.5%. Расчет длинный, ввода-вывода мало.
Попробовал разобраться.
Ну да, там, где индексы, их надо бы перевести в 8 байт, но не хочется трогать исходные тексты. Поэтому для 64 разрядов появляется лишняя пересылка, которая вероятно не дает свернуть адресацию в SIB-байт.
Вот одно и то же место:
Практически одни и те же команды и это 95% всего выполняемого расчета. Компилятор даже константы загружает без префикса 48 потому, что код не превышает 4 Гбайт.
Итак, имеем две почти одинаковых последовательности команд в двух режимах, проводящих одни и те же вычисления, главным образом, через FPU.
Исходные тексты одинаковы. Код в 64 разряда ожидаемо увеличивается.
А скорость его работы падает на 20%!
Я читал (и считал), что теперь 64-разряда – это «родной» режим, а 32-разряда оставлен для совместимости. А вот по этому тесту выходит наоборот
Здравствуйте, кт, Вы писали:
кт>Попробовал. Говорит, перетранслировал – коды стали больше (ну, это ожидаемо). А скорость стала ниже! кт>Я сначала не поверил. Забрал его тексты проверил. И вот, что получилось. кт>Процессор Intel Core i5-3210M 2.5 GHz. Стоит Windows-7.
кт>Я читал (и считал), что теперь 64-разряда – это «родной» режим, а 32-разряда оставлен для совместимости. А вот по этому тесту выходит наоборот
Это смотря с какого момента "теперь".
Processor Number i5-3210M
Status Launched Launch Date Q2'12
Очень странно выглядит если честно. Во-первых, если это оптимизрованный код какого хрена он используется FPU вместо AVX/SSE. Второе, какого хрена он все время в память лезет аргументы загружать. __restrict/__declspec ( restrict нигде не забыл?
Здравствуйте, кт, Вы писали:
кт>Ну да, там, где индексы, их надо бы перевести в 8 байт, но не хочется трогать исходные тексты. Поэтому для 64 разрядов появляется лишняя пересылка, которая вероятно не дает свернуть адресацию в SIB-байт. кт>Вот одно и то же место: кт>
Хорошо видно, что отличается: ваш пример совсем не использует адресацию от двух регистров (базового плюс индексного). Ну и IMUL на 24 разложен в два LEA (не столь существенно). У вас же в примере какой-то кошмарик.
Ну хорошо, возьмём от того же GCC5 — gfortran:
function f02(a12, i, j)
double precision f02
double precision a12(3,3)
integer i, j
f02 = a12(i, j)
end
Первые два movslq — взятие значения аргумента по ссылке, дальше — код взятия по индексу.
Вывод: с процессором, скорее всего, всё в порядке. А вот компилятор для 64-битного режима у вас, мягко говоря, фиговый.
Назовёте марку и версию компилятора?
кт>64 разряда кт>
кт>Практически одни и те же команды и это 95% всего выполняемого расчета. Компилятор даже константы загружает без префикса 48 потому, что код не превышает 4 Гбайт.
Интересно, кто и зачем использует FPU в 64-битном режиме. MS категорически переключилась на SSE для 64-битки. Unix переключаются по умолчанию, но могут включать FPU для усиленного распараллеливания. Повторяю вопрос про компилятор.
кт>Итак, имеем две почти одинаковых последовательности команд в двух режимах, проводящих одни и те же вычисления, главным образом, через FPU. кт>Исходные тексты одинаковы. Код в 64 разряда ожидаемо увеличивается. кт>А скорость его работы падает на 20%!
Специфика Intel? Есть AMD для сравнения? Тот более лояльно относится к FPU в 64-битке.
кт>Я читал (и считал), что теперь 64-разряда – это «родной» режим, а 32-разряда оставлен для совместимости. А вот по этому тесту выходит наоборот
Вообще-то для раннего Intel это было, по слухам, вполне верно — они настолько быстро и грязно сделали EM64T, что в первых его версиях (для Pentium 4) 64-битный код вообще не имел никакого out-of-order. Но уже начиная с Core, AFAIK, это не так. И ваш i7 должен вполне нормально это отрабатывать.
И кто такой FMS_M? Это проверка границ массива? А если её отключить, что получится со скоростью?
А почему одни и те же регистры (rbx, rdx) по кругу? Или это только в одном таком куске кода? Я понимаю, что переименование регистров спасает отцов русской демократии в 95% случаев, но компилятор явно без SSA, а постоянные дёргания регистров через стек дороги в любом варианте. А с учётом FPU, непонятно, что это за диковинка такая.
Возьмите GNU Fortran, возьмите интеловский, и сравните. Я подозреваю, что картина выровняется, а, может, 64-битка станет и быстрее (за счёт прямого использования большего количества регистров).
так и знал, что все найдут генерируемый код плохим.
А речь о том, что почти ОДИНАКОВЫЕ последовательности команд в 64-разрядном режиме работают на 20% медленнее, чем в они же 32 разрядном режиме.
Хотя по идее должны были бы работать ТАКЖЕ (плохо или хорошо, это уже второй вопрос)
И, кстати, SSE это не совсем то же, что FPU. В FPU реально работа идет с 80 разрядами, поэтому очень хорошее округление и точность. Промежуточные результаты можно запомнить командой FST80. А в SSE2-4 чем?
Все эти 128 и 256 разрядные регистры все (по времени) теряют на загрузках. Тоже, увы, проверено.
Учитывая, что "mov q rbx,offset SPH2" грузит 4 байта, и адресация по RIP тоже с 4-байтными смещениями, реально имеем такое же ограничение на помещение статических данных в 2GB (с учётом знаковости). Выше этой границы допускается только динамически распределяемая память. Тогда
1) компилятор совсем не умеет использовать адресацию по RIP;
2) как минимум, команды FSUBR[P] он не знает.
[UPD: deleted — не досмотрел, что часть операндов берётся из памяти]
Здравствуйте, кт, Вы писали:
кт>так и знал, что все найдут генерируемый код плохим.
Да, и это правильно. Потому что пример с загрузкой константы очевидно хуже написан в 64 битах, чем в 32. Сами же сказали, что в 64 битах компилятор чего-то не сумел. А я показываю, что это проблема не режима, а компилятора, что он очевидно слаб.
кт>А речь о том, что почти ОДИНАКОВЫЕ последовательности команд в 64-разрядном режиме работают на 20% медленнее, чем в они же 32 разрядном режиме.
Вы это можете доказать, только избавившись от посторонних влияний типа FMS_M. Неизвестно их качество.
Или проанализируйте чем-то вроде Intel VTune, который умеет (AFAIR) показывать тормоза вплоть до конкретной машинной команды и исходной строчки. Или — повторюсь — проверьте с AMD.
кт>Хотя по идее должны были бы работать ТАКЖЕ (плохо или хорошо, это уже второй вопрос)
По форме: "так же" пишется в данном случае раздельно.
кт>И, кстати, SSE это не совсем то же, что FPU. В FPU реально работа идет с 80 разрядами, поэтому очень хорошее округление и точность. Промежуточные результаты можно запомнить командой FST80. А в SSE2-4 чем?
В FPU "реально" работа идёт с точностью согласно режиму процессора. Вы точно выставили extended? Или хотя бы проверили, что оно такое? В винде в 32 битах по умолчанию там double, так же, как и во FreeBSD (а вот в Linux — extended).
Аргумент, что в FPU можно считать в 80 битах, принимаю. Иногда это даже полезно. Но обычно эту пользу переоценивают.
кт>Все эти 128 и 256 разрядные регистры все (по времени) теряют на загрузках. Тоже, увы, проверено.
Здравствуйте, netch80, Вы писали:
N>Да, и это правильно. Потому что пример с загрузкой константы очевидно хуже написан в 64 битах, чем в 32. Сами же сказали, что в 64 битах компилятор чего-то не сумел. А я показываю, что это проблема не режима, а компилятора, что он очевидно слаб.
Ухудшение кода есть, но оно никак не может дать 20% замедление, поскольку в основных циклах встречается очень редко.
N>Вы это можете доказать, только избавившись от посторонних влияний типа FMS_M. Неизвестно их качество.
внутри этой системной подпрограммки команды тоже одинаковые для 32 и 64
N>Или проанализируйте чем-то вроде Intel VTune, который умеет (AFAIR) показывать тормоза вплоть до конкретной машинной команды и исходной строчки. Или — повторюсь — проверьте с AMD.
На AMD обязательно проверю
кт>>Хотя по идее должны были бы работать ТАКЖЕ (плохо или хорошо, это уже второй вопрос)
N>По форме: "так же" пишется в данном случае раздельно.
Спасибо, постараюсь проверять написанное более тщательно
N>В FPU "реально" работа идёт с точностью согласно режиму процессора. Вы точно выставили extended? Или хотя бы проверили, что оно такое? В винде в 32 битах по умолчанию там double, так же, как и во FreeBSD (а вот в Linux — extended).
Внутри FPU все идет в 80 разрядах. "Реально" — я имел ввиду "внутри"
N>Аргумент, что в FPU можно считать в 80 битах, принимаю. Иногда это даже полезно. Но обычно эту пользу переоценивают.
А ошибки IEEE-754 обычно недооценивают.
кт>>Все эти 128 и 256 разрядные регистры все (по времени) теряют на загрузках. Тоже, увы, проверено.
N>Какие именно загрузки? Там несколько разных.
Я уже выкладывал этот плач Ярославны https://groups.google.com/forum/#!topic/comp.lang.asm.x86/j7wBKVUOmfI
И все-таки еще раз: одинаковые команды в двух режимах и должны были бы выполняться одинаково быстро (медленно)
Здравствуйте, netch80, Вы писали:
N>Учитывая, что "mov q rbx,offset SPH2" грузит 4 байта, и адресация по RIP тоже с 4-байтными смещениями, реально имеем такое же ограничение на помещение статических данных в 2GB (с учётом знаковости). Выше этой границы допускается только динамически распределяемая память. Тогда
И это действительно так, в таких программах (о, ужас) команды и статические данные не могут превысить 4 Гбайт.
Кстати, реальный размер исследуемой программы 500 Кбайт кодов.
N>1) компилятор совсем не умеет использовать адресацию по RIP;
Здесь она ничего не дает в плане объема/скорости, статические данные не "рассыпаны" между кодов. N>2) как минимум, команды FSUBR[P] он не знает.
А в чем будет выигрыш? Кстати, подпрограмма FMS_M, судя по всему, выполняет сразу и умножение и сложение.
N>И ещё одно (которое уже по счёту) — смысла писать fmul64 нет! fmul одинаков для всех, а точность задаётся режимом процессора. (В отличие от load/store, которое таки имеет размер операнда в памяти.) Это не имеет отношения к скорости, но имеет отношение к интеллекту авторов компилятора.
А вот сейчас стало обидно за авторов, да.
значит, писать безликие flds 0x0(%rip) это интеллект,
а явно и наглядно указывать fld64 [rbx] вместо какого-нибудь громоздкого fld q ptr [rbx] это плохо.
Здравствуйте, кт, Вы писали:
кт>И все-таки еще раз: одинаковые команды в двух режимах и должны были бы выполняться одинаково быстро (медленно)
А этого, по-моему, никогда не было на x86. 8-битные команды (с AH,AL, короткими переходами и прочим) выполнялись быстрее 16-ти битных, а 16-ти битные были быстрее 32-битных. Между прочим, попробуйте ради любопытства запустить аналогичный 16-ти битный код в MS-DOS, не исключено, что код и сейчас будет быстрее 32-битного. Хотя сейчас вспомнил, что вроде как раз начиная с P6 (i686, Pentium Pro и Pentium-II) оптимизатор исполнения в процессоре стал плохо работать с 16-ти битным кодом, так что возможно сейчас он уже сильно устарел и выигрыша не будет. Но он был вплоть до первых пентиумов включительно.
Здравствуйте, кт, Вы писали:
N>>2) как минимум, команды FSUBR[P] он не знает. кт>А в чем будет выигрыш?
Чуть меньше возни.
кт> Кстати, подпрограмма FMS_M, судя по всему, выполняет сразу и умножение и сложение.
А, это fused multiply and subtract? Понятно. Вряд ли оно выполняет fused с ещё более расширенной точностью, так что вынесение в подпрограмму явно не имеет смысла, но сравнение скорости оно, таки да, не портит.
N>>И ещё одно (которое уже по счёту) — смысла писать fmul64 нет! fmul одинаков для всех, а точность задаётся режимом процессора. (В отличие от load/store, которое таки имеет размер операнда в памяти.) Это не имеет отношения к скорости, но имеет отношение к интеллекту авторов компилятора.
кт>А вот сейчас стало обидно за авторов, да.
Потому что криво читаете.
кт>значит, писать безликие flds 0x0(%rip) это интеллект, кт>а явно и наглядно указывать fld64 [rbx] вместо какого-нибудь громоздкого fld q ptr [rbx] это плохо.
fld64 — это то же, что fldl в AT&T (fld32 == flds, fld80 == fldt). Тут разницы нет, кроме стиля. Команды разные.
Я говорю про _арифметику_. fmul32, fmul64, fmul80 пишутся одним и тем же кодом, и точность будет выбираться согласно режиму FPU, а не тем, какую точность записали в ассемблере. Поэтому ставить точность после fmul не просто бессмысленно, а и вредно для читающего.
Здравствуйте, netch80, Вы писали:
N>Я говорю про _арифметику_. fmul32, fmul64, fmul80 пишутся одним и тем же кодом, и точность будет выбираться согласно режиму FPU, а не тем, какую точность записали в ассемблере. Поэтому ставить точность после fmul не просто бессмысленно, а и вредно для читающего.
Может быть указание точности приводит к генерации кода, устанавливающего нужный режим работы FPU (FLDCW)?
Здравствуйте, кт, Вы писали:
N>>Да, и это правильно. Потому что пример с загрузкой константы очевидно хуже написан в 64 битах, чем в 32. Сами же сказали, что в 64 битах компилятор чего-то не сумел. А я показываю, что это проблема не режима, а компилятора, что он очевидно слаб. кт>Ухудшение кода есть, но оно никак не может дать 20% замедление, поскольку в основных циклах встречается очень редко.
Ну тогда — не знаю аналога для Windows, но тут я бы напустил на неё perf stat и померял, где основные тормоза (фронтэнд, бэкэнд, кэш, ветвления...)
N>>В FPU "реально" работа идёт с точностью согласно режиму процессора. Вы точно выставили extended? Или хотя бы проверили, что оно такое? В винде в 32 битах по умолчанию там double, так же, как и во FreeBSD (а вот в Linux — extended). кт>Внутри FPU все идет в 80 разрядах. "Реально" — я имел ввиду "внутри"
И результат каждой операции округляется согласно выбранному режиму.
N>>Аргумент, что в FPU можно считать в 80 битах, принимаю. Иногда это даже полезно. Но обычно эту пользу переоценивают. кт>А ошибки IEEE-754 обычно недооценивают.
Какие именно ошибки? Погрешности принципа плавучки, ошибки реализации, другое?
В любом случае, как только вы выгружаете из FPU в double, а не long double, точность усекается. И ваш код всё время содержит именно такие усечения (fstp64 и тому подобное).
кт>>>Все эти 128 и 256 разрядные регистры все (по времени) теряют на загрузках. Тоже, увы, проверено.
N>>Какие именно загрузки? Там несколько разных. кт>Я уже выкладывал этот плач Ярославны кт>https://groups.google.com/forum/#!topic/comp.lang.asm.x86/j7wBKVUOmfI
Мнэээ... я правильно понял, что вся проблема была в использовании AVX после SSE без vzeroupper? Тогда — удивляюсь, потому что это уровень букваря. Но даже это даёт только начальный тормоз работы с одним конкретным регистром.
кт>И все-таки еще раз: одинаковые команды в двух режимах и должны были бы выполняться одинаково быстро (медленно)
Давайте пока не спешить с выводами. У вас есть возможность прогнать через аппаратные счётчики? i5-3210M это Ivy Bridge, у него этих счётчиков уже есть на все случаи жизни.
Здравствуйте, Michael7, Вы писали:
N>>Я говорю про _арифметику_. fmul32, fmul64, fmul80 пишутся одним и тем же кодом, и точность будет выбираться согласно режиму FPU, а не тем, какую точность записали в ассемблере. Поэтому ставить точность после fmul не просто бессмысленно, а и вредно для читающего. M>Может быть указание точности приводит к генерации кода, устанавливающего нужный режим работы FPU (FLDCW)?
Эээ... это замедлило бы раз в 10 по сравнению с конкурентами. Честно, даже для компилятора с такими тараканами это чересчур.
Кстати, вас вероятно заинтересует статья "Как я сделал самый быстрый ресайз изображений. Часть 2, SIMD" https://habrahabr.ru/post/326900/ Ее автор описывает как он добился двух-трех кратного ускорения путем использования инструкций SSE2-SSE4. Что интересно, использование AVX такого большого прироста не дало, а в ряде случаев привело даже к падению производительности. Но не из-за проблем с AVX, а из-за того, что процессор Intel Core i5-4258U — ноутбучный (у вас вроде тоже) и он сбрасывает частоту при полной загрузке AVX. Причем это не тротлинг, температура так сильно не вырастала. На процессорах Xeon такого эффекта нет и там использование AVX не приводило к падению производительности.
Здравствуйте, Michael7, Вы писали:
M>Кстати, вас вероятно заинтересует статья "Как я сделал самый быстрый ресайз изображений. Часть 2, SIMD" https://habrahabr.ru/post/326900/ Ее автор описывает как он добился двух-трех кратного ускорения путем использования инструкций SSE2-SSE4. Что интересно, использование AVX такого большого прироста не дало, а в ряде случаев привело даже к падению производительности. Но не из-за проблем с AVX, а из-за того, что процессор Intel Core i5-4258U — ноутбучный (у вас вроде тоже) и он сбрасывает частоту при полной загрузке AVX. Причем это не тротлинг, температура так сильно не вырастала. На процессорах Xeon такого эффекта нет и там использование AVX не приводило к падению производительности.
Здравствуйте, netch80, Вы писали:
M>>Может быть указание точности приводит к генерации кода, устанавливающего нужный режим работы FPU (FLDCW)?
N>Эээ... это замедлило бы раз в 10 по сравнению с конкурентами. Честно, даже для компилятора с такими тараканами это чересчур.
Если это делать перед каждой командой, то конечно замедлило бы и как бы не более, чем в 10. Но если один раз перед первым употреблением, а потом отслеживать изменились требования к точности или нет, то лишнего кода не будет. Я это к тому, что в принципе в указании точности некий смысл есть, хотя далеко не факт, что в обсуждаемом компиляторе так сделано.
Здравствуйте, netch80, Вы писали:
N>>>Я говорю про _арифметику_. fmul32, fmul64, fmul80 пишутся одним и тем же кодом, и точность будет выбираться согласно режиму FPU, а не тем, какую точность записали в ассемблере. Поэтому ставить точность после fmul не просто бессмысленно, а и вредно для читающего. M>>Может быть указание точности приводит к генерации кода, устанавливающего нужный режим работы FPU (FLDCW)?
N>Эээ... это замедлило бы раз в 10 по сравнению с конкурентами. Честно, даже для компилятора с такими тараканами это чересчур.
честно, говоря, я вообще не понимаю, о чем речь. Вот команды загрузки
DD03 fld64 [rbx] операнд в памяти 8 байт
D903 fld32 [rbx] операнд в памяти 4 байта
D9C0 fld st операнд в стеке 10 байт
Причем здесь "точность ассемблера" ? Размер операнда определяет бит в коде операции, что и показывает "неинтеллектуальный" дисассемблер
Здравствуйте, кт, Вы писали:
N>>>>Я говорю про _арифметику_. fmul32, fmul64, fmul80 пишутся одним и тем же кодом, и точность будет выбираться согласно режиму FPU, а не тем, какую точность записали в ассемблере. Поэтому ставить точность после fmul не просто бессмысленно, а и вредно для читающего. M>>>Может быть указание точности приводит к генерации кода, устанавливающего нужный режим работы FPU (FLDCW)? N>>Эээ... это замедлило бы раз в 10 по сравнению с конкурентами. Честно, даже для компилятора с такими тараканами это чересчур.
кт>честно, говоря, я вообще не понимаю, о чем речь. Вот команды загрузки кт>
кт>DD03 fld64 [rbx] операнд в памяти 8 байт
кт>D903 fld32 [rbx] операнд в памяти 4 байта
кт>D9C0 fld st операнд в стеке 10 байт
кт>
кт>Причем здесь "точность ассемблера" ? Размер операнда определяет бит в коде операции, что и показывает "неинтеллектуальный" дисассемблер
В третий раз повторяю: fmul, а не fld! Повторите для него и убедитесь.