Это называется представлением тегового указателя и является довольно распространенным приемом оптимизации, используемым во многих различных интерпретаторах, виртуальных машинах и системах времени выполнения в течение десятилетий.Практически каждая реализация Lisp использует их, множество виртуальных машин Smalltalk, много интерпретаторов Ruby и т. Д.
Обычно в этих языках вы всегда передаете указатели на объекты.Сам объект состоит из заголовка объекта, который содержит метаданные объекта (например, тип объекта, его класс (ы), возможно, ограничения контроля доступа или аннотации безопасности и т. Д.), А затем сами данные объекта.Таким образом, простое целое число будет представлено как указатель плюс объект, состоящий из метаданных и фактического целого числа.Даже с очень компактным представлением, это что-то вроде 6 байт для простого целого числа.
Кроме того, вы не можете передать такой целочисленный объект в CPU для выполнения быстрой целочисленной арифметики.Если вы хотите добавить два целых числа, у вас на самом деле есть только два указателя, которые указывают на начало заголовков объектов двух целочисленных объектов, которые вы хотите добавить.Итак, сначала вам нужно выполнить целочисленную арифметику с первым указателем, чтобы добавить смещение в объект, в котором хранятся целочисленные данные.Тогда вы должны разыменовать этот адрес.Сделайте то же самое снова со вторым целым числом.Теперь у вас есть два целых числа, которые вы можете попросить добавить процессор.Конечно, теперь вам нужно создать новый целочисленный объект для хранения результата.
Итак, чтобы выполнить одно сложение целых чисел, вам действительно нужно выполнить три целочисленные дополнения плюс две разыменования указателя плюс одна конструкция объекта.И вы берете почти 20 байт.
Однако, хитрость в том, что с так называемыми типами неизменяемых значений , такими как целые числа, вам обычно не нужно всеметаданные в заголовке объекта: вы можете просто оставить все это и просто синтезировать его (что говорит VM-nerd для «фальсификации»), когда кто-нибудь захочет посмотреть.Целое число будет всегда иметь класс Integer
, нет необходимости отдельно хранить эту информацию.Если кто-то использует отражение для определения класса целого числа, вы просто отвечаете Integer
, и никто никогда не узнает, что вы на самом деле не сохранили эту информацию в заголовке объекта и что на самом деле нет даже заголовок объекта (или объект).
Итак, хитрость заключается в том, чтобы эффективно хранить значение из объекта в указателе - объектасвертывание двух в один.
Существуют ЦП, которые на самом деле имеют дополнительное пространство внутри указателя (так называемые биты тега ), которые позволяют хранить дополнительную информацию о указателе в самом указателе.,Дополнительная информация типа «это на самом деле не указатель, а целое число».Примеры включают в себя Burroughs B5000, различные машины Lisp или AS / 400.К сожалению, большинство современных основных процессоров не имеют этой функции.
Однако есть выход: большинство современных основных процессоров работают значительно медленнее, когда адреса не выровнены по границам слов.Некоторые даже вообще не поддерживают выравниваемый доступ.
Это означает, что на практике все указатели будут делиться на 4, что означает, что они будут всегда заканчивается двумя 0
битами.Это позволяет нам различать действительные указатели (которые заканчиваются на 00
) и указатели, которые на самом деле являются замаскированными целыми числами (те, которые заканчиваются на 1
).И это все еще оставляет нам все указатели, которые заканчиваются 10
свободными для других вещей.Кроме того, большинство современных операционных систем резервируют для себя очень низкие адреса, что дает нам еще одну область для возни (указатели, начинающиеся, скажем, с 24 0
s и заканчивающиеся 00
).
Таким образом, вы можете закодировать 31-битное целое число в указатель, просто сместив его на 1 бит влево и добавив к нему 1
. И вы можете выполнить очень быструю целочисленную арифметику с ними, просто сместив их соответствующим образом (иногда даже не нужно).
Что мы делаем с этими другими адресными пространствами? Ну, типичные примеры включают кодирование float
s в другом большом адресном пространстве и ряд специальных объектов, таких как true
, false
, nil
, 127 символов ASCII, некоторые часто используемые короткие строки, пустой список, пустой объект, пустой массив и т. д. рядом с адресом 0
.
Например, в интерпретаторах MRI, YARV и Rubinius Ruby целые числа кодируются так, как я описал выше, false
кодируется как адрес 0
(что так и происходит также , чтобы быть представление false
в C), true
в качестве адреса 2
(который точно так же является представлением C в true
, сдвинутом на один бит) и nil
в качестве 4
.