Demistificarea binarelor BEAM: Cum am făcut biblioteca noastră DNS extrem de rapidă (fără a folosi NIF-uri)

Demistificarea binarelor BEAM: Cum am făcut biblioteca noastră DNS extrem de rapidă (fără a folosi NIF-uri)

Oct 20, 2026 beam elixir erlang dns performance optimization binary protocols backend development

Când gestionezi o platformă care se ocupă de înregistrarea domeniilor, găzduirea DNS și infrastructură cloud, așa cum facem noi la NameOcean, performanța nu este doar un lux — este fundamentul fiabilității. De aceea, ne entuziasmează când vedem povești de optimizare ingenioase din ecosistemul Erlang și de aceea vrem să analizăm una dintre preferatele noastre pentru voi.

Recent, am dat peste o analiză fascinantă de la echipa DNSimple despre munca lor de optimizare a dns_erlang, biblioteca lor DNS open-source. Rezultatele lor au fost impresionante: cu 12% mai puțin CPU pe interogare și cu 14% mai multe interogări servite pe secundă — totul prin simple optimizări BEAM, fără a fi necesare NIF-uri. Pentru oricine construiește sisteme cu throughput ridicat în Elixir sau Erlang, aceasta este o lectură esențială.

Să analizăm ce face acest lucru posibil.

Modelul Mental pentru Binarii BEAM

Iată ceva ce îi surprinde pe mulți dezvoltatori Elixir: adăugarea (appending) la binari într-o buclă strânsă nu este la fel de costisitoare pe cât crezi.

Când scrii ceva de genul:

def build_response(acc, []) do
  acc
end
def build_response(acc, [h | t]) do
  build_response(<<acc::binary, h>>, t)
end

Te-ai aștepta ca aceasta să fie O(n²) — fiecare iterație creând un nou binar și copierea tuturor datelor. Dar BEAM este mai inteligentă decât atât.

Deoarece binarii sunt imuabili, compilatorul poate demonstra că, dacă acc a fost tocmai creat de o adăugare anterioară și nimic altceva nu deține o referință către el, scrierea direct în acea memorie existentă este sigură. Prima adăugare alocă o anumită capacitate suplimentară, iar adăugările ulterioare scriu în ea. Rezultatul? O complexitate amortizată de O(1) per adăugare — identică cu modul în care funcționează un std::vector în C++.

Problema? Dacă binarul tău „scapează” (escapes) — adică este stocat într-un dicționar de proces, este returnat unui apelant sau este adăugat la altceva — petrecerea s-a terminat. Te-ai întors la copiere.

Acest tip de pattern apare peste tot în manipularea protocoalelor binare. Când construiești răspunsuri DNS iterativ, menținerea acelui flux de acumulare compact este de o importanță enormă.

Sub-binari: Slicing Gratuit (În Mare Parte)

Potrivirea (matching) pe binari creează ceea ce BEAM numește sub-binari — termeni mici care indică în datele originale fără a le copia. Când scrii:

<<Header:12/binary, Rest/binary>> = Packet

Niciun dat nu este copiat. Rest este doar o fereastră către pachetul original.

Dar iată subtilitatea care îi induce în eroare pe oameni: BEAM menține un context de potrivire intern atunci când faci matching recursiv, practic un cursor care parcurge octeții. Nu va materializa un sub-binar decât dacă îl lași efectiv să scape — returnându-l, stocându-l sau transmiterându-l într-un loc unde ar putea rămâne.

Acest lucru contează enorm pentru parsarea DNS. Fiecare interogare vine ca un binar, este potrivită cu structura protocolului și trebuie să producă un răspuns. Înțelegerea momentului în care creezi muncă versus când doar vizualizezi datele este diferența dintre un parser care scalează și unul care se sufocă sub sarcină.

De ce Reprezentarea Numerelor Întregi Contează Mai Mult decât Crezi

Pe un BEAM de 64 de biți, numerele întregi ocupă câțiva biți pentru eticheta lor de tip. Aceasta lasă 60 de biți pentru valoarea propriu-zisă. Numerele întregi care încap în acest spațiu sunt „numere întregi mici” (small integers) — valori imediate care trăiesc direct în registrele procesului, fără a atinge niciodată heap-ul.

Orice mai mare devine un "bignum", ceea ce necesită alocare pe heap și gestionare pentru garbage collection. Sună esoteric, dar devine critic când faci acel tip de manipulare a biților pe care o cere munca cu protocoale binare.

Tehnica se numește SWAR (SIMD Within A Register) — împachetarea mai multor valori într-un singur număr întreg pentru a putea opera asupra lor în paralel la nivel de CPU. Pentru DNS, acest lucru înseamnă parsarea a mulți octeți de date în fiecare operațiune. Dar funcționează doar dacă valorile împachetate încap în zona optimă a „numărului întreg mic”.

Când nu încap, nu plătești doar costurile de alocare de memorie — potențial declanșezi cicluri de garbage collection care altfel nu ar exista. La milioane de interogări pe secundă, acele cicluri se adună.

Ce Înseamnă Acest Lucru pentru Codul Tău

Iată concluzia practică: nu trebuie să scrii NIF-uri sau să apelezi la C pentru a obține câștiguri serioase de performanță. Trebuie să înțelegi cum gândește BEAM despre date.

Câteva pattern-uri care merită internalizate:

1. Menține acumulatorii binari compacți. Buclele de tip "doar adăugare" (append-only) sunt prietenii tăi. Odată ce un binar scapează, modelul de cost se schimbă dramatic.

2. Folosește pattern matching pe loc când este posibil. Folosește pattern matching pentru a vizualiza datele, nu pentru a le copia. BEAM este optimizată pentru acest lucru.

3. Ai grijă de dimensiunea numerelor întregi. Când faci aritmetică sau manipulare de biți, gândește-te dacă valorile tale vor rămâne în intervalul „număr întreg mic”.

4. Profilează având în vedere reducerea (reductions). BEAM planifică munca în reducări (aproximativ apeluri de funcție). O buclă intensivă care face mai mult matching în mai puține apeluri de funcție implică mai puțină muncă din perspectiva scheduler-ului.

Pentru oricine construiește sisteme care gestionează protocoale binare — fie că este vorba de DNS, cadre WebSocket sau implementări de protocoale personalizate — aceste pattern-uri se aplică direct. BEAM îți oferă instrumente incredibile pentru a scrie cod rapid, dar acestea sunt utile doar dacă înțelegi modelul.

Am văzut echipe apelând la NIF-uri sau NIF-uri în Rust când o înțelegere mai profundă a semanticii BEAM le-ar fi oferit 90% din câștiguri fără nicio complexitate. Runtime-ul este mai inteligent decât credem de obicei — treaba noastră este să scriem cod care să îi permită să își arate valoarea.

La NameOcean, ne gândim mereu la performanța la scară mare. Fie că pornești Vibe Hosting pentru următorul tău proiect sau gestionezi DNS pentru o aplicație cu trafic ridicat, înțelegerea acestor fundamentale te ajută să construiești sisteme care rămân rapide atunci când lucrurile devin aglomerate.

Codare plăcută și fie ca binarii tăi să fie întotdeauna adăugați eficient!

Read in other languages:

PL IT BG RU CS EL UZ TR SV FI PT NB HU NL FR DE ES DA ZH-HANS EN