Плюсы и минусы использования char для небольших целых чисел в C

Есть ли недостатки в использовании char для небольших целых чисел в C? Есть ли какие-либо преимущества, кроме выгоды от занятости/памяти?

В частности, может ли процессор справиться с целочисленной арифметикой на char лучше или хуже, чем на (long/short) int?

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

Обновление: Visual Studio 6.0 на самом деле не имеет stdint.h, как предложил Кристоф. Небольшой бенчмаркинг в Windows (VS 6.0, отладочная сборка, 32-разрядная версия) с несколькими сложенными циклами показывает, что int и long обеспечивают аналогичную производительность, которая примерно в два раза выше, чем char. Выполнение того же теста в Linux с помощью gcc аналогично привязывает int и long к одинаковым, и оба быстрее, чем char, хотя разница менее заметна.

В качестве примечания: я не тратил много времени на поиски, но первая реализация stdint.h для VS 6.0, которую я нашел (через Wikipedia) определяет uint_fast8_t как unsigned char, несмотря на то, что по крайней мере в моих тестах это кажется медленнее. Таким образом, мораль этой истории, как справедливо подсказал Кристоф: всегда ориентируйтесь!


person me_and    schedule 06.12.2009    source источник


Ответы (6)


В C99 для решения этой проблемы были добавлены так называемые «самые быстрые» целочисленные типы минимальной ширины. Для интересующего вас диапазона это будут типы int_fast8_t и uint_fast8_t, которые можно найти в stdint.h.

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

person Christoph    schedule 06.12.2009
comment
Если вы заботитесь о переносимости (например, я делаю это, когда делюсь кодом между встроенной целью и ПК), не используйте быстрые типы для фактического хранения чего-либо (членов класса, членов структуры и т. д.). Используйте быстрые типы для итерации, доступа к массивам или в качестве локальной переменной кэша. - person MaR; 06.12.2009
comment
@MaR: использования целочисленных типов фиксированного размера недостаточно, чтобы гарантировать, что структуры могут быть сериализованы и десериализованы в разных архитектурах из-за заполнения и порядка байтов; поскольку вам все равно придется сериализовать элементы структуры последовательно, я не думаю, что есть что-то неправильное в использовании быстрых типов для переменных-членов. - person Christoph; 06.12.2009
comment
@Christoph: ну, дело не только в сериализации, но правда - мое заявление было слишком сильным. Это должно быть скорее «осторожно», так как увеличенный размер может сильно ударить по вам в определенных сценариях. - person MaR; 06.12.2009
comment
Сравнительный анализ, безусловно, является моралью этой истории; Я только что обновил вопрос результатами некоторых тестов, которые я сделал. Примечательно, что реализация stdint.h, которую я нашел для Visual Studio, определяет uint_fast8_t как далеко не самый быстрый тип в моих тестах. - person me_and; 12.01.2010
comment
Главная проблема с типами stdint.h заключается в том, что для всех практических целей нет никакой гарантии того, когда математика между знаковыми и беззнаковыми типами даст знаковый или беззнаковый результат (поскольку это определяется размерами типы и вопрос, а не используемые типы, и если бы кто-то знал размеры рассматриваемых типов, он бы вообще не нуждался в таких типах, как uint_fast8_t). Например, сумма int32_t и uint_fast8_t может быть 32-битным целым числом со знаком или без знака, а (на 8- или 16-битных машинах) произведением двух значений uint8_fast8_t... - person supercat; 12.08.2014
comment
... может быть 16-битным со знаком int, который не сможет содержать результаты, превышающие 32767. - person supercat; 12.08.2014

Ну, первая проблема заключается в том, что стандарт C не определяет, является ли простой char подписанным или беззнаковым, поэтому единственный диапазон, на который вы можете полагаться при переносе, - это от 0 до 127.

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

Обратите внимание, что операнды уже, чем int, в любом случае расширяются до int или unsigned int во время вычисления выражения.

person caf    schedule 06.12.2009
comment
да, мне сказали, что int имеет размер регистра процессора, поэтому, если вы используете char, регистр не будет заполнен, но все равно будет использовать то же время, чтобы основные операции были полностью использованы регистром или нет. - person Aif; 06.12.2009
comment
Кроме того, лучше использовать типы int8_t и uint8_t, чем char. - person Mike Weller; 06.12.2009
comment
@Aif: Насколько мне известно, преобразование вверх/вниз занимает даже больше времени. - person Carl Smotricz; 06.12.2009
comment
Я наткнулся на эти 2: ‹eventhelix.com/realtimemantra/basics/ int вместо char и short› и ‹en.wikibooks.org/wiki /Optimizing_C%2B%2B/Writing_efficient_code/›, но я не считаю их авторитетными. Извините, нет веских доказательств, больше похоже на анекдот. - person Carl Smotricz; 06.12.2009

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

Использование char вместо int на самом деле полезно только тогда, когда у вас много чисел (т. е. большой массив) и вам нужно сэкономить место.

person Carl Smotricz    schedule 06.12.2009
comment
Для символов требуется меньшее количество строк кэша, и они лучше помещаются в кэш. - person HaltingState; 25.12.2011

Арифметика с символами почти наверняка будет выполняться с использованием тех же регистров, что и арифметика с целыми числами. Например:

char c1 = 1;
char c2 = c1 + 2;

Дополнение компилируется в следующее с помощью VC++:

00401030   movsx       eax,byte ptr [ebp-4]
00401034   add         eax,2
00401037   mov         byte ptr [ebp-0Ch],al

где eax — 32-битный регистр.

Поэтому нет никакого преимущества в использовании символов над целыми числами, когда дело доходит до арифметической производительности.

person Community    schedule 06.12.2009
comment
Да, но разве доступ к байту не медленнее из-за проблем с выравниванием? Тот факт, что доступ представляет собой одну строку ASM, не означает, что больше не выполняется микрокод. Есть ли у вас какие-либо представления об этом? - person Carl Smotricz; 06.12.2009
comment
Виноват, поскольку обвиняется, возможно, в слишком большом упрощении при заполнении нулями и сжатии в байт. Но я верю, что что-то подобное происходит на каком-то уровне. - person Carl Smotricz; 06.12.2009
comment
Боюсь, я понятия не имею о времени доступа к шине (что, как мне кажется, является реальной проблемой) для современных процессоров - я отвечал на вопрос об арифметической производительности. - person ; 06.12.2009

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

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

Веские причины использовать char для хранения целочисленных значений - это когда пространство действительно имеет большое значение (не так часто, как вы могли бы подумать), а также при описании какого-либо внешнего формата данных/протокола, в который вы упорядочиваете данные. Ожидайте, что использование char приведет к небольшому снижению производительности, особенно на оборудовании, таком как Cell SPU, где доступен только доступ к памяти размером с машинное слово, поэтому для доступа к char в памяти требуется несколько сдвигов и масок.

person moonshadow    schedule 06.12.2009

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

typedef char REALLYSHORT;

Таким образом, А) становится понятнее, что вы делаете, и Б) вы можете легко изменить это (например, только в одном месте), если у вас возникнут проблемы.

У вас есть действительно веская причина не использовать int?

person T.J. Crowder    schedule 06.12.2009
comment
Ваша семантическая точка уже используется в единственном месте, где я видел это реализованным. И нет, у меня нет веских причин, мне просто любопытно, имеет ли это значение. - person me_and; 06.12.2009