Avmystifisering av BEAM-binærfiler: Slik gjorde vi DNS-biblioteket vårt lynraskt (uten bruk av NIF-er)

Avmystifisering av BEAM-binærfiler: Slik gjorde vi DNS-biblioteket vårt lynraskt (uten bruk av NIF-er)

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

Når du driver en plattform som håndterer domeneregistrering, DNS-hosting og skyinfrastruktur slik vi gjør hos NameOcean, er ytelse ikke bare en bonus – det er selve fundamentet for pålitelighet. Det er derfor vi blir begeistret når vi ser smarte optimaliseringshistorier fra Erlang-økosystemet, og hvorfor vi ønsker å bryte ned en av våre favoritter for deg.

Nylig kom vi over en fascinerende dybdeanalyse fra DNSimple-teamet om deres arbeid med å optimalisere dns_erlang, deres open-source DNS-bibliotek. Resultatene deres var slående: 12 % mindre CPU per spørring og 14 % flere spørringer servert per sekund – alt fra rene BEAM-optimaliseringer, uten behov for NIF-er. For alle som bygger systemer med høy gjennomstrømning i Elixir eller Erlang, er dette obligatorisk lesning.

La oss pakke ut hva som gjør dette mulig.

BEAM-modellen for binærdata

Her er noe som overrasker mange Elixir-utviklere: å legge til (append) i binærdata i en tett løkke er ikke så dyrt som du kanskje tror.

Når du skriver noe som:

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

Vil du kanskje forvente at dette er O(n²) – at hver iterasjon skaper en ny binær og kopierer alt. Men BEAM er smartere enn som så.

Fordi binærdata er uforanderlige (immutable), kan kompilatoren bevise at hvis acc nettopp ble opprettet av en tidligere append og ingenting annet holder en referanse til den, er det trygt å skrive direkte inn i det eksisterende minnet. Den første append-operasjonen allokerer litt ekstra kapasitet, og påfølgende append-operasjoner skriver inn i denne. Resultatet? Amortisert O(1) per append – identisk med hvordan en std::vector fungerer i C++.

Haken? Hvis binærdataene dine "unnslipper" (escapes) – det vil si at de lagres i en prosess-ordbok, returneres til en kaller, eller legges til på noe annet – er festen over. Da er du tilbake til kopiering.

Dette mønsteret dukker opp overalt i håndtering av binære protokoller. Når du bygger DNS-responser iterativt, betyr det enormt mye å holde den akkumulator-flyten stram.

Sub-binærdata: Gratis slicing (stort sett)

Mønstergjenkjenning (matching) på binærdata skaper det BEAM kaller sub-binærdata – små termer som peker inn i de originale dataene uten å kopiere dem. Når du skriver:

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

Blir ingen data kopiert. Rest er bare et vindu inn i den originale pakken.

Men her er subtiliteten som lurer folk: BEAM beholder en "match-kontekst" internt når du matcher rekursivt, i praksis en markør som går gjennom bytene. Den vil ikke materialisere en sub-binær med mindre du faktisk lar den unnslippe – ved å returnere den, lagre den, eller sende den et sted hvor den blir værende.

Dette er enormt viktig for DNS-parsing. Hver spørring ankommer som binærdata, matches mot protokollstrukturen, og må produsere en respons. Å forstå når du skaper arbeid kontra når du bare ser på data, er forskjellen mellom en parser som skalerer og en som kveles under belastning.

Hvorfor heltallsrepresentasjon betyr mer enn du tror

På en 64-bits BEAM tar heltall (integers) noen få bits til sin type-tag. Det etterlater 60 bits til selve verdien. Heltall som får plass i dette rommet er "små heltall" – umiddelbare verdier som lever direkte i prosess-registrene og aldri berører heapen.

Alt som er større blir et "bignum", noe som krever heap-allokering og hensyn til søppeltømming (garbage collection). Dette høres esoterisk ut, men det blir kritisk når du driver med den typen bit-manipulering som binær protokollhåndtering krever.

Teknikken kalles SWAR (SIMD Within A Register) – å pakke flere verdier inn i et enkelt heltall slik at du kan operere på dem parallelt på CPU-nivå. For DNS betyr dette å parse mange bytes med data i hver operasjon. Men det fungerer bare hvis de pakkede verdiene dine passer innenfor "små heltall"-sonen.

Når de ikke gjør det, betaler du ikke bare for minneallokering – du utløser potensielt sykluser med søppeltømming som ellers ikke ville eksistert. Ved millioner av spørringer per sekund blir disse syklusene betydelige.

Hva dette betyr for koden din

Her er den praktiske lærdommen: du trenger ikke å skrive NIF-er eller ty til C for å oppnå seriøse ytelsesgevinster. Du må forstå hvordan BEAM tenker på data.

Noen mønstre som er verdt å lære seg:

1. Hold binær-akkumulatorer stramme. Append-only-løkker er din venn. Når en binærdata først unnslipper, endres kostnadsmodellen dramatisk.

2. Match på stedet når det er mulig. Bruk mønstergjenkjenning for å se på data fremfor å kopiere dem. BEAM er optimalisert for dette.

3. Pass på heltallstørrelsen. Når du gjør aritmetikk eller bit-manipulasjon, tenk over om verdiene dine vil holde seg innenfor området for "små heltall".

4. Profiler med tanke på reduksjoner. BEAM planlegger arbeid i reduksjoner (omtrent funksjonskall). En varm løkke som gjør mer matching i færre funksjonskall, gjør mindre arbeid fra scheduleren sitt perspektiv.

For alle som bygger systemer som håndterer binære protokoller – enten det er DNS, WebSocket-rammer eller egendefinerte protokollimplementasjoner – gjelder disse mønstrene direkte. BEAM gir deg utrolige verktøy for å skrive rask kode, men de er bare nyttige hvis du forstår modellen.

Vi har sett team ty til NIF-er eller Rust NIF-er når en dypere forståelse av BEAM-semantikk ville ha gitt dem 90 % av gevinstene uten kompleksiteten. Kjøretiden er smartere enn vi ofte gir den kreditt for – vår jobb er å skrive kode som lar den vise seg frem.

Hos NameOcean tenker vi alltid på ytelse i stor skala. Enten du setter opp Vibe Hosting for ditt neste prosjekt eller administrerer DNS for en applikasjon med høy trafikk, hjelper forståelsen av disse fundamentene deg med å bygge systemer som holder seg raske når det blir travelt.

God koding, og måtte dine binærdata alltid appendere effektivt.

Read in other languages:

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