Omezení asynchronního logování v C++: analýza kritických bodů
Asynchronní logování v C++ se často považuje za univerzální řešení pro zrychlení aplikací. Ve skutečnosti však jen přesouvá zátěž, místo aby odstraňovalo základní omezení. Výkon je určen nejen frontami, ale také kopírováním dat, rychlostí sinků (cílových zařízení) a chováním při přetížení. Dobře optimalizované synchronní logování někdy překonává asynchronní variantu co do celkové propustnosti (throughput).
Struktura asynchronního logovacího kanálu
Asynchronní logování kombinuje dva klíčové kroky: zachycení a formátování argumentů plus asynchronní zápis.
Zachycení argumentů
- Kopírování řetězců z
string_viewnebochar*. - Serializace složitých objektů.
- Kontrola životnosti (lifetime) pro pozdější formátování.
Bezpečné obnovení dat v pozdější fázi je možné pouze při jejich kopírování. Jinak se formátování provádí synchronně.
Asynchronní zápis
- Producenti (volající kódy) umisťují záznamy do fronty.
- Jeden backendový vlákno zpracovává formátování a zápis do sinků (soubor, konzole).
- Operace
flushafsyncrozhodují o skutečné rychlosti zápisu.
Schéma:
Synchronně: volající → formátování → zápis
Asynchronně: volající → zachycení → zařazení do fronty → backend → formátování → zápis
Zavádí se režijní náklady na synchronizaci a paměť.
Problémy s odloženým formátováním
Odložené formátování (deferred formatting) ukládá argumenty pro zpracování v backendu. Avšak:
string_view sv = s; LOG_INFO("{}", sv); s.clear();— data jsou neplatná.- Totéž platí pro
char*na zásobníku, views na kontejnery nebo objekty obsahující ukazatele.
Možná řešení:
- Kopírování:
string_view→std::string. Bezpečné, ale spotřebuje více CPU i paměti než synchronní formátování. - Synchronní formátování: Zvyšuje latenci volajícího kódu.
- Uživatelská serializace: Vyžaduje vlastní kodeky, kopie dat nebo záruky životnosti.
Přetížení front a jeho důsledky
Asynchronní logování nezrychluje zápis do sinku. Pokud generujete 1 milion zpráv za sekundu, ale sink zvládne jen 100 tisíc, hromadí se zpoždění.
Jeden backendový vlákno je tvrdým úzkým místem ve scénářích s více producenty.
Při situaci „producent > spotřebitel“ dochází k:
- Růstu front a potenciálnímu úniku paměti.
- Blokování producentů a ztrátě výhod asynchronního přístupu.
- Zahození zpráv — ztráta dat.
- Přepisování starých záznamů — ztráta historie.
Pomalý sink (např. konzole: 200 µs vs. soubor: 10 µs) zpomaluje celý kanál.
Srovnání se synchronním přístupem
Synchronní formátování + asynchronní zápis je jednodušší:
- Žádné problémy s životností nebo visícími referencemi (dangling references).
- Předvídatelnost: „co jste zalogovali, to uvidíte“.
- Méně chyb z podmínkových závodů (race conditions) nebo poškození řetězců.
Asynchronní logování je užitečné při:
- Krátkodobých špičkách (burst) logů.
- Vícevláknovosti s jednoduchými argumenty.
Nepoužitelné při:
- Trvalém přetížení.
- Složitých objektech.
- Pomalých sinkech.
Benchmarky populárních logovacích knihoven
Testy na logbench ukazují:
| Logovací knihovna | Synchronní režim | Asynchronní režim |
|-------------------|------------------|-------------------|
| spdlog | Vysoká propustnost | + režijní náklady |
| logme | Vysoká propustnost | + režijní náklady |
| Quill | Není k dispozici | Blokování nebo zahození při přetížení |
Synchronní verze spdlog a logme jsou často rychlejší než jejich asynchronní protějšky kvůli absenci front a synchronizačních operací. Quill (plně asynchronní) postupně zaplňuje vyrovnávací paměti: backend → frontend → blokování nebo zahození.
Klíčové závěry
- Asynchronní logování snižuje latenci p99 „horké cesty“, ale nezvyšuje celkovou propustnost.
- Kopírování pro odložené formátování eliminuje potenciální výhodu.
- Jeden backendový vlákno je limitní faktor škálovatelnosti.
- Fronty maskují, ale neřeší přetížení.
- Volba modelu závisí na charakteru zátěže: krátkodobé špičky vs. ustálený stav.
— Editorial Team
Zatím žádné komentáře.