Почему булевы Java должны иметь размер не менее 1 байта?
Известно, что C++ bool
s должен иметь размер не менее 1 байта, чтобы можно было создать указатели для каждого [https://stackoverflow.com/a/2064565/7154924] . но в Java нет указателей на примитивные типы. Тем не менее, они все еще занимают по крайней мере 1 байт [https://stackoverflow.com/a/383597/7154924].
Почему это так - почему Java boolean
s не может быть размером 1 бит? Помимо времени вычислений, если у вас есть большой массив boolean
, Конечно, можно представить компилятор, который делает соответствующее смещение для извлечения отдельного бита, соответствующего значению boolean
?
1 ответ:
Нет никаких причин, почему булево должно быть размером в один байт. На самом деле, вполне вероятно, что булевы уже не являются 1 байтом в (эффективном) размере в некоторых сценариях: когда они упакованы рядом с другими элементами размером более 1 байта в стеке или в объекте, они, вероятно, будут больше (т. е. добавление их к объекту может привести к увеличению размера более чем на один байт).
Любая JVM может свободно реализовывать булевы в виде 1 бита, но, насколько известно, ни один из них не хочет этого делать, вероятно во многом потому, что:
Доступ к одному биту часто обходится дороже, чем доступ к байту, особенно при записи.
Для чтения бита процессору, использующему "классический набор команд RISC", часто требуется дополнительная командаand
для извлечения соответствующего бита из упакованного байта (или большего слова) булевых битов. Некоторым может даже понадобиться дополнительная инструкция для загрузки константы вand
. В случае индексации массиваboolean, where the bit-index isn't fixed at compile-time, you'd need a variable shift. Some CPUs such as x86 have an easier time since they have memory source
testinstructions, including specific bit-test instructions taking a variable position such as
bt`. Такой процессор наверное имеет сходную производительность чтения в обоих представлениях.Запись хуже: вместо простой записи байта для установки значения
boolean
Теперь вам нужно прочитать значение, изменить соответствующий бит и записать его обратно. Некоторые платформы, такие как x86, имеют инструкции RMW источника и назначения памяти, такие какand
иor
, которые помогут, но они все еще значительно дороже, чем простая запись. В худшем случае повторная запись одного и того же элемента приведет к цепочке зависимостей через память, которая может замедлить ваш код на порядок (ряд простых хранилищ не может сформировать цепочку зависимостей).Что еще хуже, описанный выше метод записи полностью небезопасен для потоков. Два потока, работающие над "независимыми" булевыми значениями, могут сталкиваться друг с другом, поэтому среда выполнения должна будет использовать атомарные операции обновления только для записи бита для любого поля, где объект не может быть доказан локальным для потока.
Экономия пространства за пределами массивов обычно очень высока. небольшие и часто нулевые: проблемы выравнивания означают, что один бит часто будет занимать то же место, что и байт в стеке или в макете объекта. Только если у вас есть много примитивных значений
boolean
в стеке или объекте, вы увидите экономию (например, объекты обычно выровнены по 8-байтовым границам, поэтому, если у вас есть объект, чьи нелогические поляint
или больше, вам потребуется по крайней мере 4 значенияboolean
, чтобы сэкономить место, и часто вам понадобится 8).- Это оставляет последний оставшийся "большой выигрыш" для битового представления
boolean
в массивахboolean
, где вы могли бы иметь асимптотическую экономию пространства 8x для больших массивов. На самом деле, этот случай был достаточно мотивирующим в мире C++, чтоvector<bool>
имеет "специальную" реализацию, где каждыйbool
занимает один бит - бесконечный источник головной боли из-за всех требуемых особых случаев и неинтуитивного поведения (и часто используется в качестве примера mis-функции, которую нельзя удалить сейчас).Если бы не модель памяти, я мог бы представить себе мир, где Java реализованные массивы
boolean
в битовом порядке. У них этого нет. те же проблемы, что иvector<bool>
(в основном из-за дополнительного слоя абтракции, предоставляемого JIT, а также потому, что массив обеспечивает более простой интерфейс, чемvector
), и это может быть сделано эффективно, я думаю. Однако есть эта надоедливая модель памяти. Эта модель позволяет безопасно выполнять записи в различные элементы массива, если они выполняются разными потоками (то есть,. они действуют как независимые переменные для целей модели памяти). Все общие процессоры поддерживают это напрямую, если вы реализуетеboolean
как байт, так как они имеют независимые байтовые обращения. Однако ни один процессор не предлагает независимого битового доступа: вы застряли с использованием атомарных операций (x86 предлагает операцииlock bt*
, но они медленные: другие платформы имеют еще худшие варианты). Это разрушило бы производительность любого логического массива, реализованного как битовый массив.Наконец, как описанная выше реализация
boolean
в виде бита имеет существенные недостатки - но как насчет плюсов?Как оказалось, если пользователь действительно хочет это бит-упакованное представление for boolean, они могут сделать это сами! Они могут упаковать 8 булевых значений в
byte
(или 32 значения вint
или что-то еще) в объекте (и это обычно для флагов и т. д.), и сгенерированный код доступа должен быть примерно таким же эффективным, как если бы JVM изначально поддерживал boolean-as-bit. На самом деле, когда Вы знаете , что вам нужно представление массива битов для большого числа булевых символов, вы можете просто использоватьBitSet
-это имеет представление, которое вы хотите, и обходит атомарные проблемы, не предлагая никаких гарантий потокобезопасности. Таким образом, реализуяboolean
в виде байта, вы обходите все вышеперечисленные проблемы, но все же позволяете пользователю "подписаться" на представление на уровне битов, если они хотят, без особых штрафов во время выполнения.