Почему булевы Java должны иметь размер не менее 1 байта?


Известно, что C++ bools должен иметь размер не менее 1 байта, чтобы можно было создать указатели для каждого [https://stackoverflow.com/a/2064565/7154924] . но в Java нет указателей на примитивные типы. Тем не менее, они все еще занимают по крайней мере 1 байт [https://stackoverflow.com/a/383597/7154924].

Почему это так - почему Java booleans не может быть размером 1 бит? Помимо времени вычислений, если у вас есть большой массив boolean, Конечно, можно представить компилятор, который делает соответствующее смещение для извлечения отдельного бита, соответствующего значению boolean?

1 2

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 в виде байта, вы обходите все вышеперечисленные проблемы, но все же позволяете пользователю "подписаться" на представление на уровне битов, если они хотят, без особых штрафов во время выполнения.