Avmystifiering av BEAM-binärer: Så gjorde vi vårt DNS-bibliotek bli blixtsnabbt (utan några NIFs)

Avmystifiering av BEAM-binärer: Så gjorde vi vårt DNS-bibliotek bli blixtsnabbt (utan några NIFs)

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

När du driver en plattform som hanterar domänregistrering, DNS-hosting och molninfrastruktur, precis som vi gör på NameOcean, är prestanda inte bara något som är trevligt att ha – det är grunden för tillförlitlighet. Det är därför vi blir entusiastiska när vi ser smarta optimeringsberättelser från Erlang-ekosystemet, och varför vi vill bryta ner en av våra favoriter åt dig.

Nyligen stötte vi på en fascinerande djupdykning från DNSimple-teamet om deras arbete med att optimera dns_erlang, deras open-source DNS-bibliotek. Deras resultat var slående: 12 % mindre CPU per förfrågan och 14 % fler förfrågningar per sekund – allt genom rena BEAM-optimeringar, utan behov av NIFs. För alla som bygger högpresterande system i Elixir eller Erlang är detta obligatorisk läsning.

Låt oss nysta i vad som gör detta möjligt.

BEAM:s mentala modell för binärer

Här är något som överraskar många Elixir-utvecklare: att lägga till (append) i binärer i en tät loop är inte så dyrt som man kan tro.

När du skriver något i stil med:

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

Skulle man kunna förvänta sig att detta är O(n²) – där varje iteration skapar en ny binär och kopierar allt. Men BEAM är smartare än så.

Eftersom binärer är immutabla kan kompilatorn bevisa att om acc just skapades av en tidigare append och inget annat håller en referens till den, så är det säkert att skriva direkt i det befintliga minnet. Den första append-operationen allokerar lite extra kapacitet, och efterföljande appends skriver in i den. Resultatet? Amortiserad O(1) per append – identiskt med hur en std::vector fungerar i C++.

Haken? Om din binär "flyr" (escapes) – vilket innebär att den lagras i en process-dictionary, returneras till en anropare eller läggs till på något annat – så är festen över. Då är du tillbaka i kopieringsläget.

Detta mönster dyker upp överallt i hantering av binära protokoll. När du bygger DNS-svar iterativt är det av enorm betydelse att hålla det ackumulerande flödet tight.

Sub-binärer: Gratis slicing (för det mesta)

Att matcha (match) på binärer skapar vad BEAM kallar sub-binärer – små termer som pekar in i originaldatan utan att kopiera den. När du skriver:

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

Kopieras ingen data. Rest är bara ett fönster in i det ursprungliga paketet.

Men här är subtiliteten som lurar folk: BEAM håller en matchningskontext internt när du matchar rekursivt, i princip en markör som vandrar genom byterna. Den kommer inte att materialisera en sub-binär förrän du faktiskt låter den flyta iväg – genom att returnera den, lagra den eller skicka den någonstans där den kan bli kvar.

Detta är av enorm betydelse för DNS-parsing. Varje förfrågan anländer som en binär, matchas mot protokollstrukturen och behöver producera ett svar. Att förstå när du skapar arbete kontra när du bara betraktar data är skillnaden mellan en parser som skalar och en som kvävs under belastning.

Varför heltalstyp representerar mer än du tror

På en 64-bitars BEAM tar heltal några bitar för sin typ-tagg. Det lämnar 60 bitar för själva värdet. Heltal som får plats i det utrymmet är "små heltal" (small integers) – omedelbara värden som lever direkt i processens register och aldrig rör heapen.

Allt som är större blir ett "bignum", vilket kräver heap-allokering och hänsyn till skräpinsamling (garbage collection). Detta låter esoteriskt, men det blir kritiskt när du gör den typ av bit-manipulation som binär protokollhantering kräver.

Tekniken kallas SWAR (SIMD Within A Register) – att packa flera värden i ett enda heltal så att du kan operera på dem parallellt på CPU-nivå. För DNS innebär detta att man parsar många bytes data i varje operation. Men det fungerar bara om dina packade värden får plats inom "small integer"-intervallet.

När de inte gör det, betalar du inte bara för minnesallokering – du riskerar också att trigga skräpinsamlingscykler som annars inte skulle ha existerat. Vid miljontals förfrågningar per sekund adderas dessa cykler snabbt.

Vad detta innebär för din kod

Här är den praktiska lärdomen: du behöver inte skriva NIFs eller ta till C för att få seriösa prestandavinster. Du behöver förstå hur BEAM tänker kring data.

Några mönster som är värda att internalisera:

1. Håll binära ackumulatorer tighta. Append-only-loopar är din vän. När en binär väl har "flytt" ändras kostnadsmodellen dramatiskt.

2. Matcha på plats när det är möjligt. Använd pattern matching för att betrakta data snarare än att kopiera den. BEAM är optimerad för detta.

3. Håll koll på dina heltalstorlekar. När du utför aritmetik eller bit-manipulation, tänk på om dina värden kommer att hålla sig inom intervallet för "små heltal".

4. Profilera med reduktioner i åtanke. BEAM schemalägger arbete i reduktioner (ungefär funktionsanrop). En het loop som gör mer matchning med färre funktionsanrop gör mindre arbete ur schemaläggarens perspektiv.

För alla som bygger system som hanterar binära protokoll – oavsett om det är DNS, WebSocket-frames eller anpassade protokollimplementeringar – är dessa mönster direkt tillämpbara. BEAM ger dig otroliga verktyg för att skriva snabb kod, men de är bara användbara om du förstår modellen.

Vi har sett team sträva efter NIFs eller Rust NIFs när en djupare förståelse för BEAMs semantik hade gett dem 90 % av vinsterna utan någon av komplexiteten. Runtime-miljön är smartare än vi ofta ger den kredit för – vår uppgift är att skriva kod som låter den glänsa.

På NameOcean tänker vi alltid på prestanda i stor skala. Oavsett om du sätter upp Vibe Hosting för ditt nästa projekt eller hanterar DNS för en högpresterande applikation, hjälper förståelsen för dessa grunder dig att bygga system som förblir snabba när trycket ökar.

Lycka till med kodandet, och må dina binärer alltid appendas effektivt.

Read in other languages:

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