Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

03.03.2023

Витрати памʼяті Ruby та Go, продовження: структури

Продовжую вчорашні тести. Доробив код, зробив зручну функцію для вимірювання, щоб прибрати копіпасту. На Go згодилися узагальнені типи; безумовно, корисна можливість, та ще й така що не погіршує швидкодію програми — про це далі. Сьогодні роздивимось структури. За тестовий приклад я обрав структуру з трьох чисельних значень; тоді розмір самих значень відомий - 24 байти, та можна підрахувати накладні витрати від самого механізму структури.

Починаючи з Ruby, найбільш очевидним здається клас Struct. Тут на кожний атрибут додаткові витрати ставлять 22 байти. Такі самі витрати будуть, якщо створити власний клас, без Struct. Тобто схоже, що Struct не оптимізує, а тільки спрощує код.

Хеш інтуїтивно буде менш ефективним. Тест це підтверджує: тут, на перший погляд, витрати аж в 3 рази більше. Але ось що цікаво: той самий хеш розміром у 224 байти спроможний зберігати до 8 значень без додаткового виділення памʼяті!. Схоже на те, що памʼять виділяється блоками, бо з девʼятого значення використання стрибає у 2 рази. Виходить, що для маленьких структур краще клас, а для великих можна не перейматись, бо хеш не додає зайвих витрат. Також, хеш, де ключі — це рядкові константи, важить так само як і хеш з символами. Ось скільки дивних нюансів такого простого, на перший погляд, типу.

Як щодо Go, то тут структури вбудовані в мову, та в традиції системних мов програмування, звичайна структура не створює жодних додаткових витрат — займає рівно стільки, скільки значення, які вона містить. Сподіваюсь, це пояснює, чому для потреб, де памʼять критична, беруть Go, а не Ruby.

Вказівник на структуру додасть свої очевидні 8 байтів. Більш цікаво, що перетворення типу в interface{} додає 9 байтів — тобто якщо використати конкретні типи, то вони не мають додаткових витрат, а інтерфейсний тип — має. Що підкреслює цінність впровадження узагальнених типів, бо як раз узагальнені типи працюють так само як еквівалентний код, де всі типи підставлені вручну.

Про асоціативні масиви в Go коротко скажу, що вони дуже схожі на хеші в Ruby. Таке саме виділення памʼяті блоками, та приблизно такі самі витрати на елемент — десь 24 байти. Різниця тільки в тому, що в Go структури набагато вигідніше в порівнянні. Наприклад, якщо використати тип map[string]interface{} для лінивої обробки JSON, то платити доведеться не тільки за масив, а ще й за інтерфейсний тип. Так що інтуїція по Go - якщо форма структури відома заздалегідь, завжди треба брати структури, а якщо ні — тільки тоді масиви.

Завтра — все то саме, тільки для JavaScript.