Powrót do strony głównej

Zig dla demonów sieciowych: doświadczenie MTProto i AI

Artykuł analizuje zastosowanie Zig w wysokonakładowym MTProto-proxy i infrastrukturze edge AI. Opisana architektura z epoll slotami, comptime dla TLS, vtable dispatch. Porównanie z C i Rust pod względem pamięci, błędów, kompilacji.

Zig kontra Rust: proxy i edge AI bez alokacji
Advertisement 728x90

Zig w usługach sieciowych o wysokim obciążeniu: doświadczenia z realnych projektów

W wysokowydajnych usługach sieciowych i aplikacjach brzegowych Zig wykazuje zalety jawnej kontroli pamięci i minimalistycznej architektury. Dwa projekty — proxy MTProto i infrastruktura agentów AI — pokazują, jak język radzi sobie z analizą bez alokacji, operacjami bitowymi i ścisłymi limitami RAM bez kompromisów w bezpieczeństwie C lub sztywności Rusta.

MTProto.zig maskuje ruch sieciowy jako HTTPS, aby omijać DPI. Nullclaw pakuje stos AI do binarki dla urządzeń z 32 MB RAM, obsługując gorącą wymianę dostawców.

Porównanie języków według kluczowych aspektów

| Kryterium | C | Rust | Zig |

Google AdInline article slot

|------------------|----------------------------|---------------------------------|---------------------------------|

| Pamięć | Niejawna, malloc w bibliotekach | Niejawna, globalny alokator | Jawna, alokator jako argument |

| Wielowątkowość | Pthreads, ręczna synchronizacja | Send/Sync, Borrow Checker | Atomiki, odpowiedzialność na kodzie |

Google AdInline article slot

| Błędy | Kody zwrotu, errno | Result<T, E>, ? | !T, catch, errdefer |

| Meta-kod | Makra preprocesora | Makra proceduralne | comptime |

| Budowanie | CMake, Makefiles | Cargo | build.zig, LLVM z pudełka |

Google AdInline article slot

Jawne zarządzanie pamięcią w Zig eliminuje ukryte alokacje, co jest kluczowe dla pętli zdarzeń na epoll.

Architektura MTProto.zig: zero-alokacji i sloty

Serwer używa jednowątkowej pętli zdarzeń bez wątków na połączenie. Zamiast alokacji dla każdego klienta — wstępnie przydzielona pula slotów:

  • Start: przydzielamy tablicę slotów w pamięci statycznej.
  • Nowy klient: bierzemy wolny slot O(1).
  • Odczyt: asynchronicznie przez epoll.
  • Rozłączenie: resetujemy stan slotu.

Zapobiega to fragmentacji sterty i OOM przy długożyjących bezczynnych gniazdach. W Rust wątki z 256 KB stosu szybko wyczerpałyby RAM.

Klasyczne podejście z wątkami i malloc prowadzi do alokacji w gorącej ścieżce. Maszyny stanu Ziga zapewniają przewidywalność.

Problemy logowania i optymalizacji

Domyślny logger Ziga blokuje pętlę zdarzeń wywołaniami systemowymi write pod obciążeniem. Setki log.debug od połączeń wywołały kaskadową awarię.

Rozwiązanie: profil ReleaseFast ignoruje logi debug na etapie kompilacji. Logowanie zostało przeniesione poza gorącą ścieżkę.

Dynamiczna dyspozycja w Nullclaw

Do wymiany dostawców AI w locie użyto vtable, analogicznej do jądra Linux:

const AiProvider = struct {
    ptr: *anyopaque, // Surowy wskaźnik na stan
    vtable: const VTable,

    pub const VTable = struct {
        generate_response: *const fn(ptr: *anyopaque, prompt: []const u8) anyerror![]const u8,
        deinit: *const fn(ptr: *anyopaque) void,
    };

    pub fn ask(self: AiProvider, prompt: []const u8) ![]const u8 {
        return self.vtable.generate_response(self.ptr, prompt);
    }
};

Daje to wywołania bez fat-pointer, zimny start w 2 ms. W Rust dyn Trait + Arc<Mutex<Box<dyn Provider>>> + Tokio powiększa binarkę.

Comptime dla fałszywego TLS

Generowanie fałszywego TLS 1.3 ServerHello odbywa się na etapie kompilacji:

const NGINX_HELLO_BYTES = comptime blk: {
    var template: [128]u8 = undefined;
    fillFakeTlsExtensions(&template);
    std.debug.assert(template.len == EXPECTED_TLS_SIZE);
    break :blk template;
};

Tablica bajtów jest wbudowana w .rodata. Runtime poprawia tylko dane sesyjne bez alokacji. W Rust wymagałoby to makr proceduralnych.

Co jest ważne

  • Jawna pamięć: Brak ukrytych alokacji, idealne do analizy zero-copy i urządzeń brzegowych.
  • Comptime: Meta-programowanie jako zwykły kod, generuje poprawne struktury pakietów na etapie budowania.
  • Minimalizm: Binar bez runtime, zimny start <3 ms, limity RAM są przestrzegane.
  • Dyspozycja vtable: Architektura wtyczkowa bez narzutu Rust traits.
  • Epoll + sloty: Skalowalność bez wątków, O(1) na połączenia.

Ostateczne rekomendacje

Zig zastępuje C w niskopoziomowych demonach z rygorystycznymi wymaganiami dotyczącymi pamięci i wydajności. Dla złożonych backendów z zespołami Rust pozostaje wyborem dzięki compile-time sprawdzaniu. Ekosystem Ziga się rozwija, ale wymaga ostrożności z zależnościami.

— Editorial Team

Advertisement 728x90

Czytaj dalej