Почему быстрые целочисленные типы быстрее, чем другие целочисленные типы?

107

В ИСО / МЭК 9899: 2018 (С18) указано в 7.20.1.3:

7.20.1.3 Самые быстрые целочисленные типы минимальной ширины

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

2 Имя typedef int_fastN_tобозначает самый быстрый целочисленный тип со знаком с шириной не менее N. Имя typedef uint_fastN_tобозначает самый быстрый целочисленный тип без знака с шириной не менее N.

3 Требуются следующие типы:

int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t, uint_fast8_t, uint_fast16_t, uint_fast32_t,uint_fast64_t

Все остальные типы этой формы являются необязательными.


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


Но не указано, почему эти «быстрые» целочисленные типы быстрее.

  • Почему эти быстрые целочисленные типы быстрее, чем другие целочисленные типы?

Я пометил вопрос с C ++, потому что быстрые целочисленные типы также доступны на C ++ 17 в заголовочном файле cstdint. К сожалению, в ISO / IEC 14882: 2017 (C ++ 17) нет такого раздела об их объяснении; Я реализовал этот раздел иначе в теле вопроса.


Информация: в C они объявлены в заголовочном файле stdint.h.

RobertS поддерживает Монику Челлио
источник
24
Ключевым моментом здесь является то, что эти целочисленные типы не являются отдельными, магически более быстрыми типами. Они просто являются псевдонимами любого обычного существующего типа, который является самым быстрым на этой машине для этой операции.
mtraceur
3
Компилятор выдает операционные коды ЦП для загрузки, хранения, маскирования и изменения областей памяти и регистров определенных размеров; это все, что видит процессор. Операционная система не имеет к этому никакого отношения. Это все, что делает компилятор, точно так, как если бы вы сами указали данный typedef. (Я полагаю, что компилятору разрешено обрабатывать его по-другому - возможно, более эффективно, если это возможно - чем пользователь typedef, если нет видимых различий в поведении.)
Питер - Восстановите Монику
1
@ RobertS-ReinstateMonica Чтобы быть точным, эти "псевдонимы" являются просто typedefутверждениями. Как правило , это делается на уровне стандартной библиотеки. Разумеется, C стандарт не накладывает реальное ограничение на то , что они typedef- значит , например , типичная реализация сделать из на 32-битную системе, но гипотетический компилятор может , например , реализовать присущий тип и обещает сделать некоторые фантазии Оптимизация для выбора самого быстрого типа машины в каждом конкретном случае для переменных этого типа, а затем библиотека может просто к этому. int_fast32_ttypedefint__int_fasttypedef
mtraceur
1
@ RobertS-ReinstateMonica Да, правда. Вы получаете программы максимальной производительности с флагами компиляции для конкретной архитектуры, что делает двоичный файл менее переносимым.
Питер - Восстановить Монику
1
@ Робертс ReinstateMonica Это будет наиболее эффективным на платформе он был составлен для , не обязательно на .
HABO

Ответы:

152

Представьте себе процессор, который выполняет только 64-битные арифметические операции. Теперь представьте, как бы вы реализовали 8-битное дополнение без знака на таком процессоре. Чтобы получить правильный результат, потребуется несколько операций. На таком процессоре 64-битные операции выполняются быстрее, чем операции с другими целочисленными значениями. В этой ситуации все они Xint_fastY_tмогут быть псевдонимами 64-битного типа.

Если ЦП поддерживает быстрые операции для узких целочисленных типов и, следовательно, более широкий тип не быстрее, чем более узкий, тогда Xint_fastY_tне будет псевдоним более широкого типа, чем это необходимо для представления всех битов Y.

Из любопытства я проверил размеры для конкретной реализации (GNU, Linux) на некоторых архитектурах. Они не одинаковы для всех реализаций на одной архитектуре:

┌────╥───────────────────────────────────────────────────────────┐
 Y     sizeof(Xint_fastY_t) * CHAR_BIT                         
    ╟────────┬─────┬───────┬─────┬────────┬──────┬────────┬─────┤
     x86-64  x86  ARM64  ARM  MIPS64  MIPS  MSP430  AVR 
╞════╬════════╪═════╪═══════╪═════╪════════╪══════╪════════╪═════╡
 8   8       8    8      32   8       8     16      8   
 16  64      32   64     32   64      32    16      16  
 32  64      32   64     32   64      32    32      32  
 64  64      64   64     64   64      64    64      64  
└────╨────────┴─────┴───────┴─────┴────────┴──────┴────────┴─────┘

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


Скриншот таблицы, для пользователей Android:

Скриншот приведенной выше таблицы

(Android не имеет черчения символов в моно шрифте - ссылка )

eerorika
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Самуэль Лев
@RobertSsupportsMonicaCellio Нет. «Не одинаково для всех архитектур» также верно, но сразу видно из показанных данных, поэтому я не считаю необходимым указывать очевидное. Я только показал значения из одной реализации, и действительно у других будет другой выбор. Проверьте, например, x86-64 на Windows. Вы найдете разные размеры по сравнению с тем, что было показано здесь.
eerorika
@RobertSsupportsMonicaCellio На мой взгляд, эти комментарии имеют отношение к ответу и уместны здесь. Я бы позволил модератору переместить их, если они чувствуют необходимость сделать это.
eerorika
11

Они не, по крайней мере, не надежно.

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

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

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

Определения типов на многих распространенных платформах, похоже, еще не продуманы. В частности, современные 64-битные платформы, как правило, имеют хорошую поддержку 32-битных целых чисел, однако «быстрые» типы часто излишне 64-битные на этих платформах.

Кроме того, типы в C становятся частью ABI платформы. Таким образом, даже если поставщик платформы обнаружит, что он сделал глупый выбор, трудно изменить этот тупой выбор позже.

Игнорируйте «быстрые» типы. Если вы действительно обеспокоены целочисленной производительностью, сравните ваш код со всеми доступными размерами.

plugwash
источник
7

Быстрые типы не быстрее, чем все другие целочисленные типы - они фактически идентичны некоторому «нормальному» целочисленному типу (они просто псевдонимы для этого типа) - какой бы тип не оказался самым быстрым для хранения значения по крайней мере, так много битов.

Это просто зависит от платформы, для какого целого типа каждый быстрый тип является псевдонимом.

Крис Додд
источник