Как определить размер машинного слова в C / C++?
есть ли более или менее надежный способ (не обязательно идеальный) для обнаружения размер машинного слова на архитектура для которого я собираю?
By размер машинного слова Я имею в виду размер целого регистра аккумулятора (например, EAX на x86, RAX на x86_64 и т. д., не streaming extensions, сегмент или регистры с плавающей запятой).
стандарт, похоже, не предоставляет тип данных "машинное слово". Так что я не ищет 100% портативный способ, просто то, что работает в большинстве распространенных случаев (Intel x86 Pentium+, ARM, MIPS, PPC - то есть на основе регистров, современные товарные процессоры).
size_t и uintptr_t звучит как хорошие кандидаты (и на практике соответствует размеру регистра везде, где я тестировал), но, конечно, что-то еще и, таким образом, не гарантируется всегда делать так, как уже описано в Is size_t слово размер.
контекст
предположим, я реализую цикл хэширования по блоку непрерывных данных. Нормально, что результирующий хэш зависит от компилятора, имеет значение только скорость.
пример:http://rextester.com/VSANH87912
тестирование на Windows показывает, что хэширование в кусках 64 бит быстрее в 64-битном режиме и в 32 битах в 32-битном режиме:
64-bit mode
int64: 55 ms
int32: 111 ms
32-bit mode
int64: 252 ms
int32: 158 ms
6 ответов
Я думаю, вы хотите
sizeof(size_t) который должен быть размером индекса. то есть. ar[index]
32 bit machine
char 1
int 4
long 4
long long 8
size_t 4
64 bit machine
char 1
int 4
long 8
long long 8
size_t 8
это может быть сложнее, потому что 32-битные компиляторы работают на 64-битных машинах. Их выпуск 32, хотя машина способна на большее.
я добавил компиляторы windows ниже
Visual Studio 2012 compiled win32
char 1
int 4
long 4
long long 8
size_t 4
Visual Studio 2012 compiled x64
char 1
int 4
long 4
long long 8
size_t 8
поскольку языки C и c++ намеренно абстрагируются от таких соображений, как размер машинного слова, маловероятно, что какой-либо метод будет 100% надежным. Однако, существуют различные int_fastXX_t типы, которые могут помочь вам определить размер. Например, эта простая программа на C++:
#include <iostream>
#include <cstdint>
#define SHOW(x) std::cout << # x " = " << x << '\n'
int main()
{
SHOW(sizeof(int_fast8_t));
SHOW(sizeof(int_fast16_t));
SHOW(sizeof(int_fast32_t));
SHOW(sizeof(int_fast64_t));
}
производит этот результат с помощью gcc версии 5.3.1 на моей 64-разрядной машине Linux:
sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 8
sizeof(int_fast32_t) = 8
sizeof(int_fast64_t) = 8
это говорит о том, что можно обнаружить размер регистра следует искать наибольшую разницу между требуемым размером (например, 2 байта для 16-битного значения) и соответствующим int_fastXX_t размер и использование размера int_fastXX_t как размер регистра.
дополнительные результаты
Windows 7, gcc 4.9.3 под Cygwin на 64-разрядной машине: то же, что и выше
Windows 7, Visual Studio 2013 (v 12.0) на 64-разрядной машине:
sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 4
sizeof(int_fast32_t) = 4
sizeof(int_fast64_t) = 8
Linux, gcc 4.6.3 на 32-разрядной ARM, а также Linux, gcc 5.3.1 на 32-разрядной Атом:
sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 4
sizeof(int_fast32_t) = 4
sizeof(int_fast64_t) = 8
даже в машинной архитектуре a слово может быть несколько вещей. AFAIK у вас есть различные количества, связанные с оборудованием:
- символ: вообще говоря, это самый маленький элемент, который можно обменять на память или из памяти-теперь он почти везде 8 бит, но раньше был 6 на некоторых старых архитектурах (CDC в начале 80-х годов)
- integer: регистр целых чисел (e.г. EAX на x86). ИМХО приемлемое приближение
sizeof(int) - address: что можно решить в архитектуре. ИМХО приемлемым приближением является
sizeof(uintptr_t) - не говоря уже о плавающих точках...
давайте сделаем историю:
Machine class | character | integer | address
-----------------------------------------------------------
old CDC | 6 bits | 60 bits | ?
8086 | 8 bits | 16 bits | 2x16 bits(*)
80x86 (x >= 3) | 8 bits | 32 bits | 32 bits
64bits machines | 8 bits | 32 bits | 64 bits
| | |
general case(**) | 8 bits | sizeof(int) | sizeof(uintptr_t)
(*) это был специальный режим адресации, где высокое слово было сдвинуто только на 8 бит, чтобы создать 20 - битный адрес-но указатели far использовали бит 32bits long
(**) uintptr_t не имеет большого смысла в старой архитектуре, потому что компиляторы (когда они существовали) не поддерживали этот тип. Но если на них был портирован приличный компилятор, я предполагаю, что значения будут такими.
но будьте осторожны: типы определяются компилятором, а не архитектура. Это означает, что если вы нашли 8-битный компилятор на 64-й машине, вы, вероятно, получите sizeof(int) = 16 и sizeof(uintptr_t) = 16. Поэтому вышесказанное имеет смысл только при использовании компилятора адаптированный к архитектуре...
Я дам вам правильный ответ на вопрос, который вы должны задать:
Q: Как выбрать самую быструю хэш-процедуру для конкретной машины, если мне не нужно использовать конкретный, и он не должен быть одинаковым, за исключением одной сборки (или, возможно, запуска) приложения?
A: реализуйте параметризованную процедуру хэширования, возможно, используя различные примитивы, включая инструкции SIMD. На данном аппаратном обеспечении какой-то набор из них будет работать и вы нужно будет перечислить этот набор, используя некоторую комбинацию времени компиляции #ifdefS и динамическое обнаружение функций процессора. (Например. вы не можете использовать AVX2 на любом процессоре ARM, определенном во время компиляции, и вы не можете использовать его на более старом x86, определенном cpuinfo инструкция.) Возьмите набор, который работает и время их на тестовых данных на интересующих машинах. Либо сделайте это динамически при запуске системы / приложения или протестируйте столько случаев, сколько сможете, и hardcode, какую процедуру использовать, на какой системе на основе некоторых алгоритм обнюхивания. (Например. ядро Linux делает это, чтобы определить самый быстрый memcpy рутина, etc.)
обстоятельства, при которых вам нужен хэш, чтобы быть последовательным, будут зависеть от приложения. Если вам нужно, чтобы выбор был полностью во время компиляции, вам нужно создать набор макросов препроцессора, определенных компилятором. Часто можно иметь несколько реализаций, которые производят один и тот же хэш, но используют разные аппаратные подходы для разных размеры.
пропуск SIMD, вероятно, не является хорошей идеей, если вы определяете новый хэш и хотите, чтобы он был очень быстрым, хотя в некоторых приложениях может быть возможно насытить скорость памяти без использования SIMD, поэтому это не имеет значения.
если все это звучит как слишком много работы, использовать size_t как размер аккумулятора. Или используйте самый большой размер, для которого std::atomic говорит вам, что тип lock free. См.: std::atomic_is_lock_free, std::atomic::is_lock_free, или std::atomic::is_always_lock_free.
по "размеру машинного слова" мы должны будем предположить, что значение: самый большой размер части данных, которые процессор может обрабатывать в одной инструкции. (Иногда называется шириной шины данных, хотя это simplicifaction.)
на различных процессорах: s,size_t, uintptr_t и ptrdiff_t может быть что угодно - они относятся к ширина шины адреса, а не ширина данных процессора. Поэтому мы можем забыть об этих типах, они не говорят нам что угодно.
на всех основных процессорах: s,char всегда 8 бит, short всегда 16 бит и long long всегда 64 бита. Таким образом, единственные интересные типы, оставшиеся int и long.
существует следующий основной процессор: s:
8 бит
int = 16 bits
long = 32 bits
16 бит
int = 16 bits
long = 32 bits
32 бит
int = 32 bits
long = 32 bits
64 Битс!--30-->
int = 32 bits
long = 32 bits
могут существовать нетрадиционные вариации вышеизложенного, но, как правило, из вышеизложенного невозможно отличить 8-бит от 16-бит или 32-бит от 64-бит.
выравнивание также не помогает нам, потому что оно может или не может применяться к различным процессорам. Многие CPU: S могут читать несоосные слова просто отлично, но при дорогом медленном коде.
таким образом, нет способа сообщить "размер машинного слова" с помощью стандарта С.
однако можно написать полностью портативный C, который может работать на чем угодно между 8 и 64 битами, используя типы из stdint.h, в частности uint_fast типы. Некоторые вещи, чтобы иметь в виду:
-
неявное целое число акций в разных системах. Ничего
uint32_tили больше, как правило, безопасный и портативный. - тип целочисленных констант по умолчанию ("литералы"). Это чаще всего (но не всегда)
intиintна данной системе может различаться. - выравнивание и struct/Союз обивка.
- размер указателя не обязательно совпадает с размером машинного слова. Особенно верно для многих 8, 16 и 64-разрядных компьютеров.
выберите sizeof (int *) * CHAR_BIT, чтобы получить размер архитектуры машины в битах.
причина в том, что архитектура может быть сегментирована, size_t дает максимальный размер для одного объекта (который может быть тем, что вы хотите, но не то же самое, что естественный размер бита архитектуры машины). Если CHAR_BIT равен 8, но базовые байты не 8 бит, указатели символов и void могут иметь дополнительные биты, позволяющие им обращаться к 8-битным единицам. int * наиболее маловероятно иметь такую прокладку. CHAR_BIT не может быть 8, однако.