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

🤖🚫 Контент вільний від AI. Цей пост на 100% написаний людиною, як і все на моєму блозі. Насолоджуйтесь!

04.03.2023

Вимірюємо витрати памʼяті у JavaScript

Для закінчення моєї поверхневої серії про витрати памʼяті, розглянемо JavaScript. А точніше, інтерпретатор Node.JS, бо інтерпретаторів існує чимало, та поводитись вони можуть по-різному. Код все ще на gist.

В JavaScript є вбудована функція для отримання витрат памʼяті - process.memoryUsage(). А от для виклику збирача сміття треба запускати скрипт з ключем --expose-gc, інакше функція global.gc() не буде доступною.

Так само як і в Ruby та Go, числа в JavaScript займають 8 байтів, тобто зберігаються без обгорток. Так само масив чисел займає стільки памʼяті, скільки самі числа. Виникає питання — навіщо тоді в JavaScript є ще класи для типізованих масивів - Int8Array та інші? Знайшов пояснення. Головна причина в тому, що типізовані масиви відповідають єдиному блоку памʼяті, та їх можна передавати в усілякі API, які такий блок очікують. Тож не варто заміняти свої масиви на типізовані, якщо немає на них споживача — звичайні масиви не менш ефективні.

Константні рядки так само зберігаються одноразово. Але це не єдине, що тут незвично. Рядки у JavaScript представляються у кодуванні UTF-16, де “прості” символи займають по 2 байти. А памʼять під них Node.js виділяє блоками по 8 байтів, напевно, щоб рядок був кратним розрядності процесора. До речі, не писав про це окремо, але як в Ruby, так і в Go рядки зберігаються в “рідному” кодуванні, що для більшості сучасних програм значить UTF-8. Так що, для зберігання текстів латиницею програма на JavaScript споживатиме вдвічі більше памʼяті, ніж Ruby або Go. Але тут теж все не так просто, бо мені здається, що Node.JS стискає рядки, бо на рядки з повторюваними значеннями уходить менше памʼяті. Треба це детальніше роздивитись.

Зі структурами все теж цікаво. В JavaScript все є обʼєктом. Будь то асоціативний масив, клас, будь-що інше. Це підтверджують мої тести: обʼєкти, створені літералом, та такі, що створені конструктором, памʼяті займають однаково. Але з літералами не все так просто. Помітив, що додаткове чисельне поле в літералі збільшує витрати тільки на 8 байтів. Але ж де тоді зберігається ключ цього поля? Моя інтуїція каже, що в JavaScript структура обʼєкта зберігається окремо від значень, тому обʼєкти, створені одним та тим самим літералом містять тільки свої значення. Таким чином, JavaScript по використанню памʼяті точно краще, ніж Ruby, та має показувати витрати, схожі на Go. Дуже приємний висновок.

На цьому поки закінчую дослідження витрат памʼяті. Короткі висновки: для зберігання простих типів та масивів всі 3 мови підходять приблизно однаково; Ruby суттєво програє в витратах на структури; JavaScript навпаки, майже не гірше за системну мову; Go, звісно, буде оптимальним, якщо тільки не зберігати все в асоціативних масивах.