Чому int в OCaml становить лише 31 біт?


115

Більше ніде не бачив цієї "функції". Я знаю, що 32-й біт використовується для збору сміття. Але чому це так тільки для ints, а не для інших основних типів?


10
Зауважте, що в 64-бітних операційних системах int в OCaml становить 63 біта, а не 31. Це усуває більшість практичних проблем (наприклад, обмеження розміру масиву) біта тегів. І звичайно, є тип int32, якщо вам потрібне дійсне 32-бітове ціле число для якогось стандартного алгоритму.
Porculus

1
nekoVM ( nekovm.org ) також недавно мав 31 біт.
TheHippo

Відповіді:


244

Це називається тегованим представленням покажчика і є досить поширеним оптимізаційним трюком, який використовується у багатьох різних інтерпретаторах, віртуальних машинах та системах виконання протягом десятиліть. Практично кожна програма Lisp використовує їх, багато віртуальних машин Smalltalk, багато інтерпретаторів Ruby тощо.

Зазвичай на цих мовах ви завжди передаєте вказівники на об’єкти. Сам об’єкт складається із заголовка об'єкта, який містить метадані об’єкта (як-от тип об'єкта, його клас (-и), можливо обмеження контролю доступу або примітки щодо безпеки тощо), а потім власне дані об’єкта. Отже, просте ціле число буде представлено у вигляді вказівника плюс об'єкта, що складається з метаданих та фактичного цілого числа. Навіть при дуже компактному поданні це щось на зразок 6 байт для простого цілого числа.

Крім того, ви не можете передавати такий цілий об'єкт до процесора для виконання швидкої цілої арифметики. Якщо ви хочете додати два цілих числа, у вас дійсно є лише два покажчики, які вказують на початок заголовків об’єктів двох цілих об'єктів, які ви хочете додати. Отже, спочатку потрібно виконати цілу арифметику на першому вказівнику, щоб додати зміщення до об'єкта до нього, де зберігаються цілі дані. Тоді вам доведеться скасувати цю адресу. Зробіть те ж саме знову з другим цілим числом. Тепер у вас є два цілих числа, які ви можете фактично попросити CPU додати. Звичайно, вам зараз потрібно побудувати новий цілий об'єкт для утримання результату.

Отже, для того, щоб виконати одне ціле додавання, вам потрібно виконати три додавання цілих чисел плюс два відхилення покажчика плюс одна конструкція об'єкта. А ви займаєте майже 20 байт.

Однак хитрість полягає в тому, що з так званими типами незмінних значень, як цілі числа, вам зазвичай не потрібні всі метадані в заголовку об'єкта: ви можете просто залишити все це і просто синтезувати його (що є VM-nerd- говорять за "підробляти це"), коли хтось байдуже дивиться. Ціле число завжди матиме клас Integer, не потрібно окремо зберігати цю інформацію. Якщо хто - то використовує відображення , щоб з'ясувати клас ціле число, ви просто відповісти , Integerі ніхто ніколи не дізнається , що ви на самому ділі не зберігати цю інформацію в заголовку об'єкта , і що насправді, там НЕ навіть заголовок об'єкта (або об’єкт).

Таким чином, хитрість полягає в тому, щоб зберегти значення з об'єкта в межах покажчика на об'єкт, ефективно руйнуючись два в одному.

Є процесори, які насправді мають додатковий простір усередині вказівника (так звані біти тегів ), які дозволяють зберігати додаткову інформацію про вказівник у самому покажчику. Додаткова інформація на кшталт "це насправді не вказівник, це ціле число". Приклади включають Burroughs B5000, різні машини Lisp або AS / 400. На жаль, більшість поточних центральних процесорів не мають такої функції.

Однак є вихід: більшість поточних центральних процесорів працюють значно повільніше, коли адреси не вирівнюються за межами слова. Деякі навіть взагалі не підтримують нестандартний доступ.

Це означає, що на практиці всі вказівники будуть розділені на 4, а значить, вони завжди закінчуються двома 0бітами. Це дозволяє нам розрізняти реальні покажчики (що закінчуються в 00) та покажчики, які є фактично цілими цілими числами (ті, що закінчуються 1). І це все ще залишає нас з усіма вказівниками, які закінчуються у 10вільному виконанні інших речей. Крім того, більшість сучасних операційних систем резервують для себе дуже низькі адреси, що дає нам ще одну область, з якою возитися (покажчики, які починаються, скажімо, з 24 0с і закінчуються 00).

Отже, ви можете кодувати 31-бітове ціле число в покажчик, просто перемістивши його 1 біт вліво і додавши 1до нього. І ви можете виконувати дуже швидку цілу арифметику з тими, просто перемістивши їх належним чином (іноді навіть не потрібно).

Що ми робимо з цими іншими адресними просторами? Ну, типові приклади включають кодує floatз в іншому великому адресному просторі , а також ряд спеціальних об'єктів , таких як true, false, nil, 127 ASCII символів, деякі часто використовувані короткі строки, порожній список, порожній об'єкт, порожній масив і так далі поруч з 0адреса.

Наприклад, в інтерпретаторах MRI, YARV і Rubinius Ruby цілі числа кодуються так, як я описав вище, falseкодується як адреса 0(що так само буває і представленням falseв C), trueяк адреса 2(що саме так відбувається) представлення С trueзміщене на один біт) і nilяк 4.


5
Є люди, які кажуть, що ця відповідь неточна . Я поняття не маю, чи так це, чи вони нитко. Я просто думав, що я вкажу на це у випадку, якщо він містить деяку правду.
surfmuggle

5
@threeFourOneSixOneThree Ця відповідь не є повністю точною для OCaml, оскільки в OCaml частина відповіді "синтезувати її" ніколи не відбувається. OCaml не є об'єктно-орієнтованою мовою, як Smalltalk або Java. Ніколи не існує жодних причин для отримання таблиці методів OCaml int.
Паскаль Куок

V8 двигун Chrome також використовує позначений покажчик і зберігає 31-бітове ціле число, яке називається smi (Small Integer) як оптимізація \
phuclv

@phuclv: Звичайно, це не дивно. Як і HotSpot JVM, V8 заснований на VM Animorphic Smalltalk VM, який, в свою чергу, базується на Self VM. І V8 був розроблений (деякими) тими ж людьми, які розробили HotSpot JVM, Animorphic Smalltalk VM та Self VM. Ларс Бак, зокрема, працював над усім цим, а також власний VM Smalltalk VM під назвою OOVM. Отже, зовсім не дивно, що V8 використовує відомі трюки зі світу Smalltalk, оскільки його створили Smalltalkers на основі технології Smalltalk.
Йорг W Міттаг

28

Див. Розділ "представлення цілих чисел, бітів тегів, виділених у купі значень" https://ocaml.org/learn/tutorials/performance_and_profiling.html для хорошого опису.

Коротка відповідь - це для продуктивності. При передачі аргументу функції він передається як ціле число або вказівник. На рівні мови машинного рівня немає способу визначити, чи регістр містить ціле число чи покажчик, це лише 32 або 64 бітове значення. Тож час запуску OCaml перевіряє біт тегу, щоб визначити, що отримане ним число було цілим чи покажчиком. Якщо біт тегу встановлений, то значення є цілим числом і передається правильному перевантаженню. В іншому випадку це вказівник і тип шукається вгору.

Чому цей тег мають лише цілі числа? Тому що все інше передається як вказівник. Що передається, це або ціле число, або вказівник на якийсь інший тип даних. Маючи лише один біт тегів, може бути лише два випадки.


1
"Коротка відповідь - це для продуктивності". Зокрема, про ефективність Coq. Виконання майже всього іншого страждає від цього дизайнерського рішення.
JD

17

Це не зовсім "використовується для збору сміття". Він використовується для внутрішнього розрізнення вказівника і нерозміщеного цілого числа.


2
І наслідком цього є те , що саме так принаймні один інший тип, а саме вказівники. Якщо плавці також не є 31 бітом, то я припускаю, що це тому, що вони зберігаються як об'єкти на купі і посилаються на покажчики. Я б припустив, що для масивів їх є компактна форма.
Том Андерсон

2
Ця інформація - саме те, що потрібно GC для навігації у графіку вказівника.
Тобу

Msgstr "Він використовується для внутрішнього розрізнення вказівника і нерозбірного цілого числа". Хтось ще використовує це для іншого, крім GC?
JD

13

Мені потрібно додати це посилання, щоб допомогти ОП зрозуміти більше 63-розрядний тип з плаваючою комою для 64-розрядного OCaml

Хоча заголовок статті здається float, він насправді говорить про теextra 1 bit

Виконання OCaml дозволяє поліморфізм через рівномірне представлення типів. Кожне значення OCaml представлене як одне слово, так що можливо мати єдину реалізацію для, скажімо, "списку речей", з функціями доступу (наприклад, List.length) та складання (наприклад List.map) цих списків які працюють точно так само, чи є вони списками ints, floats або списками наборів цілих чисел.

Все, що не вміщується в слові, виділяється блоком у купу. Слово, що представляє ці дані, є вказівником на блок. Оскільки купа містить лише блоки слів, усі ці вказівники вирівнюються: їх кілька найменш значущих бітів завжди не встановлені.

Конструктори без аргументів (на кшталт цього: тип фруктів = Яблуко | Помаранчевий | Банан) і цілі числа не представляють стільки інформації, що їх потрібно виділити в купу. Їх представництво є безкомплексним. Дані знаходяться безпосередньо всередині слова, яке інакше було б вказівником. Отже, хоча список списків насправді є списком покажчиків, список int містить вкладені символи з одним меншим непрямим напрямком. Функції, що отримують доступ та створюють списки, не помічають, оскільки вставки та покажчики мають однаковий розмір.

Проте колектор сміття повинен вміти розпізнавати покажчики з цілих чисел. Вказівник вказує на добре сформований блок в купі, який за визначенням живий (оскільки його відвідує GC) і повинен бути позначений таким чином. Ціле число може мати будь-яке значення і, якщо запобіжні заходи не будуть вжиті, випадково виглядатиме як вказівник. Це може призвести до того, що мертві блоки виглядають живими, але набагато гірше, це також призведе до того, що GC змінить біти в тому, що, на його думку, є заголовком живого блоку, коли воно насправді слідує за цілим числом, схожим на покажчик і псує користувача дані.

Ось чому некомплектовані цілі числа надають програмісту OCaml 31 біт (для 32-розрядного OCaml) або 63 біта (для 64-розрядного OCaml). У поданні за лаштунками завжди встановлюється найменш значущий біт слова, що містить ціле число, щоб його відрізняти від вказівника. 31- або 63-бітні цілі числа досить незвичні, тому кожен, хто взагалі використовує OCaml, це знає. Що зазвичай користувачі OCaml не знають, це те, чому для 64-розрядного OCaml не існує 63-розрядного неукомплектованого плаваючого типу.


3

Чому int в OCaml становить лише 31 біт?

В основному, для досягнення найкращої ефективності в доказів теореми Кок, де домінуюча операція відповідає узгодженню, а домінуючі типи даних є варіантами. Найкращим представленням даних було встановлено рівномірне представлення з використанням тегів для відмежування покажчиків від небіткових даних.

Але чому це так тільки для ints, а не для інших основних типів?

Не тільки int. Інші типи, такі як charі enums, використовують таке ж теговане представлення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.