Почему предпочтительнее uint32 t, а не uint fast32 t?


кажется,uint32_t гораздо более распространен, чем uint_fast32_t (Я понимаю, что это неподтвержденная информация). Однако это кажется мне противоречащим интуиции.

почти всегда, когда я вижу использование uint32_t, все, что он действительно хочет, это целое число, которое может содержать значения до 4,294,967,295 (обычно гораздо более низкая граница где-то между 65,535 и 4,294,967,295).

это кажется странным, чтобы затем использовать uint32_t, Как 'ровно 32 бита' гарантия не нужно, а то 'самый быстрый доступный > = 32 бита' гарантия uint_fast32_t Кажется, это совершенно правильная идея. Более того, хотя это обычно реализуется,uint32_t на самом деле не гарантируется существование.

зачем же тогда uint32_t предпочтительнее? Это просто лучше известно или есть технические преимущества перед другими?

11 77

11 ответов:

uint32_t гарантированно имеет почти те же свойства на любой платформе, которая его поддерживает.1

uint_fast32_t очень мало гарантий о том, как он ведет себя на разных системах в сравнении.

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

при написании кода, Вы можете даже не иметь открыть до uint_fast32_t система, которая не 32 бит в размере.

uint32_t не будет работать по-другому (кроме см. сноску).

правильность важнее скорости. Таким образом, преждевременная корректность является лучшим планом, чем преждевременная оптимизация.

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

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


1 как отметил @chux в комментарии, если unsigned больше, чем uint32_t, арифметика на uint32_t проходит через обычные целочисленные акции, а если нет, то он остается как uint32_t. Это может привести к ошибкам. Ничто никогда не бывает совершенным.

почему многие люди используют uint32_t, а не uint32_fast_t?

Примечание: Mis-named uint32_fast_t должно быть uint_fast32_t.

uint32_t имеет более жесткую спецификацию, чем uint_fast32_t и так делает для более последовательного функциональность.


uint32_t плюсы:

  • этот тип задается различными алгоритмами. ИМО-лучший повод для использования.
  • точная ширина и диапазон известны.
  • массивы этого типа не несите никаких отходов.
  • без подписи целочисленная математика с ее переполнением более предсказуема.
  • более близкое соответствие по диапазону и математике 32-битных типов других языков.
  • никогда не мягкий.

uint32_t плюсы:

  • не всегда доступны (но это редко в 2018 году).
    Например: платформы без 8/16/32-битных целых чисел (9/18/36-немного, другие).
    Например: использование платформ номера-2-х дополнит. старый 2200

uint_fast32_t плюсы:

  • всегда доступен.
    Это всегда разрешить всем платформам, новым и старым, использовать быстрые/минимальные типы.
  • "самый быстрый" тип, который поддерживает 32-разрядный диапазон.

uint_fast32_t плюсы:

  • диапазон известен только минимально. Например, это может быть 64-битный тип.
  • массивы этого тип может быть расточительным в памяти.
  • все ответы (мои тоже сначала), сообщение и комментарии использовали неправильное имя uint32_fast_t. Похоже, многие просто не нуждаются и используют этот тип. Мы даже не использовали правильное имя!
  • обивка возможно (редко).
  • в некоторых случаях "самый быстрый" тип действительно может быть другим типом. Так что uint_fast32_t - это только приближение 1-го порядка.

в конце концов, что лучше всего зависит от цели кодирования. Если кодирование для очень широкой переносимости или некоторой нишевой функции производительности, используйте uint32_t.


есть еще одна проблема при использовании этих типов, что входит в игру: их ранг по сравнению с int/unsigned

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

таким образом, uintN_t больше шансов, чем uint_fastN_t будет уже unsigned. Это означает, что код, который использует uintN_t математика, скорее всего, подвержена целочисленным акциям, чем uint_fastN_t когда речь заходит о переносимости.

С этой заботой: преимущество удобоносимости uint_fastN_t С помощью выбора математических операций.


заметка о int32_t, а не int_fast32_t: на редкие машины, INT_FAST32_MIN может быть -2,147,483,647 и не -2,147,483,648. Более крупный пункт:(u)intN_t типы плотно определены и водят к портативному коду.

почему многие люди используют uint32_t, а не uint32_fast_t?

глупый ответ:

  • нет стандартного типа uint32_fast_t правильное написание uint_fast32_t.

практический ответ:

  • многие люди на самом деле использовать uint32_t или int32_t для их точной семантики, ровно 32 бита с беззнаковым обертыванием вокруг арифметики (uint32_t) или представление дополнения 2 (int32_t). Элемент xxx_fast32_t типы может быть больше и поэтому нецелесообразно хранить в двоичных файлах, использовать в упакованных массивах и структурах или отправлять по сети. Кроме того, они даже не могут быть быстрее.

прагматичный ответ:

  • многие люди просто не знают (или просто не заботятся) о uint_fast32_t, как показано в комментариях и ответах, и, вероятно, предположить простой unsigned int иметь ту же семантику, хотя многие текущие архитектуры все еще имеют 16-бит ints и какой-то редкий музей образцы имеют другие странные размеры int менее 32.

UX ответ:

  • хотя, возможно, быстрее, чем uint32_t,uint_fast32_t медленнее использовать: требуется больше времени для ввода, особенно учитывая поиск орфографии и семантики в документации C; -)

элегантность имеет значение, (очевидно, на основе мнения):

  • uint32_t выглядит достаточно плохо, что многие программисты предпочитают определять свои собственные u32 или uint32 тип... С этой точки зрения, uint_fast32_t выглядит неуклюжим без ремонта. Неудивительно, что он сидит на скамейке со своими друзьями uint_least32_t и так далее.

одна из причин в том, что unsigned int уже "самый быстрый" без необходимости каких-либо специальных typedefs или необходимости включать что-то. Так что, если вам это нужно быстро, просто используйте фундаментальный int или unsigned int тип.
Хотя стандарт явно не гарантирует, что он самый быстрый, он косвенно делает это, заявляя "простые ints имеют естественный размер, предложенный архитектурой среды выполнения" в 3.9.1. Другими словами,int (или его unsigned counterpart) - это то, с чем процессор наиболее удобен.

теперь, конечно, вы не знаете, какие размеры unsigned int может быть. Вы только знаете, что это по крайней мере как short (и я, кажется, помню это short должно быть не менее 16 бит, хотя я не могу найти это в стандарте сейчас!). Обычно это просто Просто 4 байта, но теоретически это может быть больше или в крайних случаях даже меньше (хотя я лично никогда не сталкивался архитектура в таких случаях, даже на 8-битных компьютерах в 1980-х годах... может быть, некоторые микроконтроллеры, кто знает оказывается, я страдаю слабоумием,int было очень ясно 16 бит тогда).

стандарт C++ не утруждает себя указанием того, что <cstdint> типы или то, что они гарантируют, он просто упоминает "то же самое, что и в C".

uint32_t, согласно стандарту C, гарантии что вы получаете ровно 32 бита. Ничего особенного, нет меньше и без прокладок бит. Иногда это именно то, что вам нужно, и поэтому это очень ценно.

uint_least32_t гарантирует, что независимо от размера, он не может быть меньше 32 бит (но он вполне может быть больше). Иногда, но гораздо реже, чем точное witdh или "не волнует", это то, что вы хотите.

и наконец, uint_fast32_t несколько излишне, на мой взгляд, за исключением документации намеренных целей. Стандартные состояния C "обозначает целочисленный тип, который обычно самый быстрый" (обратите внимание на слово "обычно") и явно указывается, что он должен быть быстрым для всех целей. Другими словами,uint_fast32_t это примерно то же самое, что uint_least32_t, которая составляет обычно самый быстрый тоже, только без гарантии (но без гарантии в любом случае).

так как большую часть времени вы либо не заботитесь о точном размере, либо хотите ровно 32 (или 64, иногда 16) бит, и так как "не волнует" unsigned int тип самый быстрый в любом случае, это объясняет, почему uint_fast32_t не так часто используется.

Я не видел доказательств того, что uint32_t использоваться для его

нескольким причинам.

  1. многие люди не знают, что существуют "быстрый" видах.
  2. это более подробный тип.
  3. труднее рассуждать о поведении ваших программ, когда вы не знаете фактического размера типа.
  4. стандарт на самом деле не закрепляет самый быстрый, и он не может действительно определить, какой тип на самом деле самый быстрый, может быть очень зависимым от контекста.
  5. Я не видел никаких доказательств того, что разработчики платформы вкладывают какие-либо мысли в размер этих типов при определении их платформ. Например, в x86-64 Linux "быстрые" типы являются 64-разрядными, хотя x86-64 имеет аппаратную поддержку для быстрых операций с 32-разрядными значениями.

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

С точки зрения правильности и простоты кодирования, uint32_t имеет много преимуществ над uint_fast32_t в частности, из-за более точно определенного размера и арифметической семантики, как указывали многие пользователи выше.

возможно, было упущено то, что одно предполагаемое преимущество на uint_fast32_t - что это может быть быстрее, просто никогда не материализовался каким-либо значимым образом. Большинство 64-разрядных процессоров, которые доминировали в 64-разрядную эпоху (x86-64 и Aarch64 в основном) превратился из 32-разрядных архитектур и быстро 32-битные операции даже в 64-битном режиме. Так что uint_fast32_t это то же самое, что uint32_t на этих платформах.

даже если некоторые из" Также запущенных " платформ, таких как POWER, MIPS64, SPARC, предлагают только 64-разрядные операции ALU,подавляющее большинство из интересных 32-битных операций можно сделать просто отлично на 64-битных регистрах: Нижний 32-бит будет иметь желаемые результаты (и все основные платформы, по крайней мере, позволяют загружать/хранить 32-бит). Сдвиг влево является основной проблемой, но даже это может быть оптимизировано во многих случаях оптимизациями отслеживания значений/диапазонов в компиляторе.

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

наконец, отмечу, что в то время как компромисс в значительной степени был охарактеризован как "использование памяти и потенциал векторизации" (в пользу uint32_t) против количества команд/скорости (в пользу uint_fast32_t) - даже это мне непонятно. Да, на некоторых платформах вам понадобятся дополнительные инструкции для некоторые 32-битные операции, но вы также сохранить некоторые инструкции, так:

  • использование меньшего типа часто позволяет компилятору умело комбинировать смежные операции, используя одну 64-разрядную операцию для выполнить два 32-битных. Пример такого типа "векторизации бедняка" не является редкостью. Например, создать константу struct two32{ uint32_t a, b; } на rax как two32{1, 2}можно оптимизировать в одном mov rax, 0x20001 в то время как 64-разрядная версия нуждается в двух инструкциях. В принципе это также должно быть возможно для соседних арифметических операций (та же операция, другой операнд), но я не видел этого на практике.
  • Нижняя "использование памяти" также часто приводит к меньшему количеству инструкции, даже если объем памяти или кэш-памяти не является проблемой, потому что любая структура типа или массивы этого типа копируются, вы получаете в два раза больше денег за каждый скопированный регистр.
  • меньшие типы данных часто используют лучшие современные соглашения о вызовах, такие как SysV ABI, которые эффективно упаковывают данные структуры данных в регистры. Например, вы можете вернуть до 16-байтовой структуры в реестрах rdx:rax. Для функции, возвращающей структуру с 4 uint32_t значения (инициализируется из константы), что переводится в

    ret_constant32():
        movabs  rax, 8589934593
        movabs  rdx, 17179869187
        ret
    

    та же структура с 4 64-бит uint_fast32_t нужен регистр двигаться и четыре магазина в память, чтобы сделать то же самое (и вызывающий абонент, вероятно, будет читать значения обратно из памяти после возврата):

    ret_constant64():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 1
        mov     QWORD PTR [rdi+8], 2
        mov     QWORD PTR [rdi+16], 3
        mov     QWORD PTR [rdi+24], 4
        ret
    

    аналогично, при передаче аргументов структуры 32-разрядные значения упаковываются примерно в два раза плотнее в регистры, доступные для параметров, поэтому это делает менее вероятным, что у вас закончатся аргументы регистра и вам придется пролить в стек1.

  • даже если вы решите использовать uint_fast32_t для мест, где" скорость имеет значение", вы часто также будете иметь места, где вам нужен тип фиксированного размера. Например, при передаче значений для внешнего вывода, из внешнего входа, как часть вашего ABI, как часть структуры, которая нуждается в определенном макете, или потому, что вы умно используете uint32_t для больших агрегаций значений для сохранения в памяти след. В тех местах, где ваш uint_fast32_t и типы `uint32_t " должны взаимодействовать, вы можете найти (в дополнение к сложности разработки), ненужные расширения знака или другой код, связанный с несоответствием размера. Компиляторы выполняют нормальную работу по оптимизации этого во многих случаях, но это все еще не редкость видеть это в оптимизированном выходе при смешивании типов разных размеров.

вы можете играть с некоторыми из примеров выше и больше on godbolt.


1 чтобы быть ясным, соглашение структур упаковки плотно в регистры не всегда является явным выигрышем для меньших значений. Это значит, что меньшие значения могут быть "извлечены", прежде чем они могут быть использованы. Например, простая функция, которая возвращает сумму двух элементов структуры вместе, нуждается в mov rax, rdi; shr rax, 32; add edi, eax в то время как для 64-разрядной версии каждый аргумент получает свой собственный регистр и просто нуждается в одном add или lea. Тем не менее, если вы признаете, что дизайн "плотно упакованных структур при прохождении" имеет смысл в целом, то меньшие значения будут использовать больше преимуществ этой функции.

насколько я понимаю,int изначально предполагалось, что это" родной "целочисленный тип с дополнительной гарантией, что он должен быть размером не менее 16 бит - что-то, что считалось" разумным " размером в то время.

когда 32-битные платформы стали более распространенными, мы можем сказать, что" разумный " размер изменился на 32 бита:

  • современная Windows использует 32-бит int на всех платформах.
  • POSIX гарантирует, что int по крайней мере 32 биты.
  • в C#, Java имеет тип int который гарантированно будет ровно 32 бит.

но когда 64-битная платформа стала нормой, никто не расширился int быть 64-разрядным целым числом из-за:

  • переносимость: много кода зависит от int 32 бита в размер.
  • потребление памяти: удвоение памяти для каждого int может быть неразумным в большинстве случаев, так как в большинстве случаев используемые числа намного меньше 2 миллиард.

теперь, почему вы предпочитаете uint32_t до uint_fast32_t? По той же причине языки C# и Java всегда используют целые числа фиксированного размера: программист не пишет код, думая о возможных размерах разных типов, они пишут для одной платформы и тестируют код на этой платформе. Большая часть кода неявно зависит от конкретных размеров типов данных. И вот почему uint32_t является лучшим выбором для большинства случаев - это не позволяет какой-либо двусмысленности относительно его поведение.

, составляет uint_fast32_t действительно самый быстрый тип на платформе с размером равным или больше 32 бит? Не реально. Рассмотрим этот компилятор кода GCC для x86_64 в Windows:
extern uint64_t get(void);

uint64_t sum(uint64_t value)
{
    return value + get();
}

сгенерированная сборка выглядит так:

push   %rbx
sub    x20,%rsp
mov    %rcx,%rbx
callq  d <sum+0xd>
add    %rbx,%rax
add    x20,%rsp
pop    %rbx
retq

теперь, если вы измените get() ' s возвращает значение uint_fast32_t (что составляет 4 байта на Windows x86_64) вы получаете это:

push   %rbx
sub    x20,%rsp
mov    %rcx,%rbx
callq  d <sum+0xd>
mov    %eax,%eax        ; <-- additional instruction
add    %rbx,%rax
add    x20,%rsp
pop    %rbx
retq

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

нет такой проблемы, если вы используете только 32-разрядные значения, но вы, вероятно, будете использовать их с size_t переменные (вероятно, размеры массива?) и это 64 бита на x86_64. На Linux uint_fast32_t 8 байт, так что ситуация другая.

многие программисты используют int когда им нужно вернуть небольшое значение (скажем, в диапазоне [-32,32]). Это будет работать отлично, если int был бы собственный целочисленный размер платформы, но поскольку он не находится на 64-разрядных платформах, другой тип, который соответствует собственному типу платформы, является лучшим выбором (если он часто не используется с другими целыми числами меньшего размера).

в принципе, независимо от того, что стандарт говорит, uint_fast32_t разбивается на некоторые реализации в любом случае. Если вы заботитесь о дополнительной инструкции, сгенерированной в некоторых местах, вы должны определить свой собственный "родной" целочисленный тип. Или вы можете использовать size_t для этой цели, как это обычно соответствует native размер (я не включаю старые и неясные платформы, такие как 8086, только платформы, которые могут работать под управлением Windows, Linux и т. д.).


еще один знак, который показывает int предполагалось, что собственный целочисленный тип - это "правило целочисленного продвижения". Большинство процессоров могут выполнять операции только на собственном компьютере, поэтому 32-разрядный процессор обычно может выполнять только 32-разрядные дополнения, вычитания и т. д. (процессоры Intel здесь являются исключением). Поддерживаются только целочисленные типы других размеров через инструкции по загрузке и хранению. Например, 8-битное значение должно быть загружено с соответствующей инструкцией" load 8-bit signed "или" load 8-bit unsigned " и расширит значение до 32 бит после загрузки. Без целочисленного правила продвижения компиляторы C должны были бы добавить немного больше кода для выражений, которые используют типы меньшие, чем собственный тип. К сожалению, это больше не работает с 64-битными архитектурами, поскольку компиляторы теперь должны выдавать дополнительные инструкции в некоторых случаях (как было показано выше.)

для практических целей uint_fast32_t совершенно бесполезно. Он неправильно определен на самой распространенной платформе (x86_64) и на самом деле не дает никаких преимуществ нигде, если у вас нет очень низкого качества компилятора. Концептуально никогда не имеет смысла использовать "быстрые" типы в структурах данных / массивах - любая экономия, которую вы получаете от типа, более эффективного для работы, будет затмевать стоимость (промахи кэша и т. д.) увеличения размера рабочего набора данных. И отдельные локальные переменные (счетчики циклов, темп и др.)) не игрушечный компилятор обычно может просто работать с большим типом в сгенерированном коде, если это более эффективно, и только усекать до номинального размера, когда это необходимо для корректности (и со знаковыми типами это никогда не нужно).

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

есть также некоторые ABIs и протоколы, указанные для использования ровно 32 бит, например, IPv4-адреса. Вот что uint32_t на самом деле означает: использовать ровно 32 бит, независимо от того, является ли это эффективным на процессоре или нет. Они были объявлены как long или unsigned long, что вызвало много проблем во время 64-битного перехода. Если вам просто нужен беззнаковый тип, который содержит числа по крайней мере до 232-1, это было определение unsigned long С тех пор, как вышел первый стандарт C. На практике, хотя, достаточно старый код предполагал, что a long может содержать любой указатель или смещение файла или метку времени, и достаточно старый код предполагал, что он был ровно 32 бит в ширину, что компиляторы не обязательно могут сделать long аналогично int_fast32_t не разбивая слишком много вещей.

в теории, это было бы более перспективным для программы, чтобы использовать uint_least32_t, и, возможно, даже нагрузки uint_least32_t элементы в uint_fast32_t переменной для расчетов. Реализация, которая не имела uint32_t тип вообще мог даже заявите о себе в формальном соответствии со стандартом! (Он просто не сможет скомпилировать многие существующие программы.) На практике больше нет архитектуры, где int,uint32_t и uint_least32_t Не то же самое, и никаких преимуществ, в настоящее время в исполнении uint_fast32_t. Так зачем же все усложнять?

но посмотрите на причину все 32_t типы должны были существовать, когда у нас уже было long, и вы увидите, что эти предположения были взорваны на наших лицах раньше. Ваш код вполне может в конечном итоге работать когда-нибудь на машине, где 32-разрядные вычисления точной ширины медленнее, чем собственный размер слова, и вам было бы лучше использовать uint_least32_t для хранения uint_fast32_t для расчета религиозно. Или если вы пересечете этот мост, когда доберетесь до него, и просто хотите что-то простое, есть unsigned long.

чтобы дать прямой ответ: я думаю, что реальная причина, почему uint32_t используется свыше uint_fast32_t или uint_least32_t это просто, что его легче печатать, и, из-за того, что он короче, гораздо приятнее читать: Если вы делаете структуры с некоторыми типами, а некоторые из них uint_fast32_t или аналогичный, то часто трудно выровнять их красиво с int или bool или другие типы В C, которые довольно короткие (пример:char и character). Я, конечно, не могу подкрепить это жесткими данными, но другие ответы могут только догадываюсь о причине.

как по техническим причинам, предпочитают uint32_t, Я не думаю, что есть какие - когда вы абсолютно нужно точный 32 бит без знака int, то этот тип является вашим только стандартизированные выбор. Практически во всех остальных случаях технически предпочтительны другие варианты - в частности,uint_fast32_t если вас беспокоит скорость, и uint_least32_t Если вас волнует место для хранения. Используя uint32_t в любом из этих случаев риски невозможности компиляции, поскольку тип не требуется для существования.

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

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