WedX - журнал о программировании и компьютерных науках

x86 4 байта с плавающей запятой против 8 байтов удваивается (по сравнению с длинным длинным)?

У нас есть приложение для обработки данных измерений, и в настоящее время все данные хранятся как C++ float, что означает 32 бита/4 байта на нашей платформе x86/Windows. (32-битное приложение для Windows).

Поскольку точность становится проблемой, ведутся дискуссии о переходе на другой тип данных. Обсуждаемые в настоящее время варианты включают переключение на double (8 байт) или реализацию фиксированного десятичного типа поверх __int64 (8 байт).

Причина, по которой решение с фиксированным десятичным числом, использующее __int64 в качестве базового типа, даже обсуждается, заключается в том, что кто-то заявил, что производительность double (все еще) значительно хуже, чем обработка float, и что мы могли бы увидеть значительные преимущества в производительности, используя собственный целочисленный тип для хранения наших чисел. (Обратите внимание, что мы действительно устроились бы с фиксированной десятичной точностью, хотя код, очевидно, стал бы более сложным.)

Очевидно, что в конце концов нам нужно провести бенчмаркинг, но я хотел бы спросить, соответствует ли истине утверждение о том, что двойники хуже, если смотреть на современные процессоры? Я предполагаю, что для больших массивов удвоения могут испортить кэш-попадания больше, чем с плавающей запятой, но в остальном я действительно не понимаю, как они могут отличаться по производительности?


  • [удваивает] испортит кэш-попадания больше, чем плавает, но в остальном я действительно не понимаю, как они могут отличаться в производительности? ммм... разве вычисление более точных результатов не является, по крайней мере, потенциально более трудоемкой задачей? Однако я никогда не проверял этот материал... Я был бы более подозрительным в отношении более сложных функций, таких как журналы или полномочия. 12.11.2010
  • Почему бы не написать простой стресс-тест, чтобы сравнить арифметические операции с массивом чисел с плавающей запятой и массивом удвоений и проверить результат из первых рук? 12.11.2010

Ответы:


1

Это зависит от того, что вы делаете. Сложения, вычитания и умножения на double выполняются так же быстро, как и на float на современных процессорах с архитектурой x86 и POWER. Деление, квадратный корень и трансцендентные функции (exp, log, sin, cos и т. д.) обычно заметно медленнее с двойными аргументами, поскольку время их выполнения зависит от желаемой точности.

Если вы используете фиксированную точку, умножения и деления должны быть реализованы с помощью инструкций умножения/деления длинных целых чисел, которые обычно медленнее, чем арифметика на doubles (поскольку процессоры не оптимизированы для этого). Тем более, если вы работаете в 32-битном режиме, где длинное 64-битное умножение со 128-битным результатом должно быть синтезировано из нескольких 32-битных длинных умножений!

Использование кэша здесь является отвлекающим маневром. 64-битные целые и двойные числа имеют одинаковый размер - если вам нужно больше 32 бит, вы получите этот штраф, несмотря ни на что.

12.11.2010
  • Спасибо, что указали, что нам в любом случае понадобится 64-битная версия — по крайней мере, аргумент кеша теперь вне окна! 12.11.2010

  • 2

    Поищи это. И Intel, и Intel публикуют задержки инструкций для своих процессоров в свободно доступных PDF-документах на своих веб-сайтах.

    Однако по большей части производительность не будет существенно отличаться по нескольким причинам:

    • при использовании x87 FPU вместо SSE все операции с плавающей запятой вычисляются с точностью до 80 бит внутри, а затем округляются, что означает, что фактическое вычисление одинаково затратно для всех типов с плавающей запятой. Тогда единственная стоимость действительно связана с памятью (с точки зрения использования кеша ЦП и пропускной способности памяти, и это проблема только в float против double, но не имеет значения, если вы сравниваете с int64)
    • с SSE или без него почти все операции с плавающей запятой являются конвейерными. При использовании SSE инструкции double могут (я не смотрел это) иметь более высокую задержку, чем их эквиваленты float, но пропускная способность одинакова, поэтому должна быть возможность достичь аналогичной производительности с doubles.

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

    Итак, мой совет:

    1. Напишите несколько быстрых тестов. Не должно быть так сложно написать программу, которая выполняет ряд операций с плавающей запятой, а затем измерить, насколько double версия медленнее, чем float.
    2. Посмотрите это в руководствах и убедитесь сами, есть ли существенная разница в производительности между вычислениями float и double.
    12.11.2010

    3

    Мне трудно понять обоснование «в два раза медленнее, чем с плавающей запятой, мы будем использовать 64-битное целое». Угадывание производительности всегда было черным искусством, требующим большого опыта, на сегодняшнем оборудовании это еще хуже, учитывая количество факторов, которые необходимо учитывать. Даже измерить сложно. Я знаю несколько случаев, когда микротесты соответствовали одному решению, но измерение контекста показывало, что другое было лучше.

    Во-первых, обратите внимание, что два фактора, которые были даны для объяснения заявленной более медленной производительности double, чем float, здесь неуместны: необходимая пропускная способность будет такой же для double, как и для 64-битного int, а векторизация SSE2 даст преимущество для double...

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

    Поэтому я сомневаюсь, что вращение вашей собственной арифметики с фиксированной точкой будет выгоднее использования double (но меры могут показать мне, что я ошибаюсь).

    12.11.2010

    4

    Реализация 64 фиксированных точек не очень интересна. Особенно для более сложных функций, таких как Sqrt или логарифм. Целые числа, вероятно, будут немного быстрее для простых операций, таких как сложения. И вам нужно будет иметь дело с целочисленными переполнениями. И вам нужно быть осторожным при округлении, иначе могут легко накапливаться ошибки.

    Мы реализуем фиксированные точки в проекте C#, потому что нам нужен детерминизм, который с плавающей запятой в .net не гарантируется. И это относительно болезненно. Некоторая формула содержала переполнение x^3 bang int. Если у вас нет действительно веских причин не делать этого, используйте float или double вместо фиксированной точки.

    Инструкции SIMD из SSE2 еще больше усложняют сравнение, поскольку они позволяют работать с несколькими числами с плавающей запятой (4 числа с плавающей запятой или 2 числа типа double) одновременно. Я бы использовал double и попытался воспользоваться этими инструкциями. Таким образом, double, вероятно, будет значительно медленнее, чем float, но сравнивать с ints сложно, и я бы предпочел float/double фиксированной точке в большинстве сценариев.

    12.11.2010

    5

    Всегда лучше измерять, а не гадать. Да, на многих архитектурах вычисления на doubles обрабатывают вдвое больше данных, чем вычисления на floats (а long doubles еще медленнее). Однако, как указывалось в других ответах и ​​комментариях к этому ответу, архитектура x86 не следует тем же правилам, что, скажем, процессоры ARM, процессоры SPARC и т. д. На x86 floats, doubles и long doubles преобразуются в long doubles для вычисления. Я должен был это знать, потому что преобразование делает результаты x86 более точными, чем SPARC, и Sun приложила много усилий, чтобы получить менее точные результаты для Java, вызвал споры (обратите внимание, что эта страница создана в 1998 году, с тех пор все изменилось).

    Кроме того, вычисления с doubles встроены в ЦП, где вычисления с фиксированным десятичным типом данных были бы написаны программно и потенциально медленнее.

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

    12.11.2010
  • По крайней мере, в x86 основные арифметические операции с плавающей запятой (сложение/вычитание/умножение) всегда выполняются с точностью >80 бит (long double), а затем округляются до текущей указанной точности. Большинство основных инструкций занимают одинаковое время для float, double и long double (обратите внимание, что большинство компиляторов Windows имеют long double == double - они никогда не будут использовать 80-битные числа с плавающей запятой). 12.11.2010
  • @Fabian Giesen, на самом деле, если вы включите SSE2, вычисления с плавающей запятой больше не будут выполняться на оборудовании x87. 12.11.2010
  • Они больше не выполняются с инструкциями x87, но это то же самое аппаратное обеспечение, которое выполняет код SSE2 и x87, а сумматоры/умножители по-прежнему поддерживают полноразмерные операции без дополнительные затраты, хотя результаты не записываются обратно в регистры. 12.11.2010
  • Это не совсем то же самое аппаратное обеспечение, которое используется; SSE2 использует регистры, а x87 использует стек FP. Конечно, сумматоры могут быть общими. Не делает код x87 столь же быстрым: требуется много перетасовки стека, чтобы получить операнды для каждой операции на месте. 12.11.2010
  • @Fabian: насколько я знаю, нет. При использовании SSE вычисления выполняются с фактическим размером данных. Плавает на 32-битном и удваивается на 64. 80-битная внутренняя точность предназначена только для x87 FPU (если у вас нет доказательств обратного?) 12.11.2010
  • @MSalters Стек x87 имеет фиксированный размер (8 записей), который фактически представляет собой просто регистры. Перетасовка стека решается с помощью переименования регистров и была фактически бесплатной, начиная с оригинального Pentium. @jalf Да, SSE не имеет промежуточных результатов с более высокой точностью, но по-прежнему использует то же самое FP ALU. Если это ALU может выполнять 80-битные добавления с той же задержкой в ​​3 цикла, которую вы получаете для 32-битных добавлений (как это происходит сейчас), маловероятно, что 64-битные добавления, которые снова используют то же самое аппаратное обеспечение, будут медленнее. :) 12.11.2010
  • @Fabian Giesen: инструкции могут быть свободны для выполнения в том смысле, что они исключаются на этапе выполнения инструкции. Они по-прежнему должны находиться в кэше L1 и декодироваться. (Они могут быть устранены в кеше трассировки, я не могу сказать - зависит от переименования регистра, которое я предполагаю.) 16.11.2010
  • @MSalters Да, x87 нужно больше инструкций, но они меньше (без дополнительных байтов префикса). Размер кода обычно очень похож между x87/SSE. Ни один процессор семейства x86 со времен P4 не использовал кеш трассировки, но P4 FXCH выполнялся без каких-либо задержек, поэтому я думаю, что он был устранен. SSE быстрее для типичного кода, но это не имеет ничего общего со стеком или дополнительными выполняемыми инструкциями, а все, что связано с наличием 1. Прямого преобразования int-›float, float-›int из/в целочисленные GPR 2. A float-› преобразование int, которое всегда усекает (важно для кода C!) 3. FP сравнивает непосредственно установленные EFLAGS. 16.11.2010

  • 6

    С различными наборами инструкций SIMD вы можете выполнять 4 операции с плавающей запятой одинарной точности по той же цене, что и одна, по сути, вы упаковываете 4 операции с плавающей запятой в один 128-битный регистр. При переключении на двойники вы можете упаковать в эти регистры только 2 двойника и, следовательно, вы можете выполнять только две операции одновременно.

    12.11.2010
  • Пока ваш код не является автоматически векторизованным (я никогда не видел, чтобы это работало с любыми циклами, которые не были полностью тривиальными) или использует встроенные функции, он использует либо скалярные операции с плавающей запятой SSE, либо x87. Оба они упаковывают в регистр только одно значение, поэтому нижняя ширина SIMD не имеет значения. 12.11.2010

  • 7

    Как говорили многие люди, 64-битный int, вероятно, не стоит того, если есть вариант double. По крайней мере, когда SSE доступен. Это может отличаться на микроконтроллерах различных типов, но я думаю, что это не ваше приложение. Если вам нужна дополнительная точность в длинных суммах с плавающей запятой, вы должны помнить, что эта операция иногда проблематична с числами с плавающей запятой и двойными числами и была бы более точной для целых чисел.

    15.11.2010
    Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]