HttpArena'da Birinci Olmak, Bir Gün Sonra Madalya Kaybetmek
23 Ekim'de HttpArena'nın sonuç sayfası güncellendi. Viltrum, Experimental kategorisinde HTTP/1.1'de 13 giriş arasında birinci. WebSocket'te 11 giriş arasında birinci. 9 altın, 1 gümüş.
Aynı gün README'ye madalya tablosunu koydum.
Ertesi gün tabloya tekrar baktım: 7 altın, 2 gümüş, 1 bronz.
Bu yazı o iki gün arasında ne olduğunu ve rakamlarımın nereden geldiğini anlatıyor. Bir önceki yazıda motorun kendisini anlattım, bu yazı ölçüm tarafı.
Neden HttpArena
Web framework benchmark'ı denince akla TechEmpower gelirdi. 24 Mart 2026'da repo arşivlendi, yeni giriş kabul etmiyor. Aktif olan devam halkası HttpArena.
Herkes aynı makinede koşuyor: 64 çekirdekli bir Threadripper PRO 3995WX, sunucuya 64 thread veriliyor, yük üreticisi ayrı çekirdeklerde. Girişini kendin yazıyorsun (Dockerfile, handler'lar, bir meta.json), PR açıyorsun, bakımcı benchmark'ı koşturup sonuçları commit'liyor.
Viltrum'un girişi repo içinde, benches/httparena/ altında. İlk PR 14 Ekim'de merge oldu.
Puan nasıl hesaplanıyor
Bunu anlamadan hangi işin önemli olduğunu bilemiyorsun.
- Her profil 0 ile 1000 arası. Profilde en iyi sonuç 1000 alıyor, diğerleri ona oranla. Referans bütün alan, filtrelediğin görünüm değil.
- Composite toplam, ortalama değil. Bir profile hiç girmezsen o profilden 0 alıyorsun. Yani eksik her profil 1000 puan.
- json-comp'ta boyut cezası var. İstek/sn önce
(en küçük cevap boyutu / senin cevap boyutun)²ile çarpılıyor. Cevabın iki kat büyükse puanın dörtte bire iniyor. - Sabit hızlı profiller farklı. latency-1m, latency-10k ve 8gbit istek hızını sabitliyor. Orada puanı CPU, ortalama gecikme ve p99 belirliyor.
- Completeness. Routing, middleware, request ve response işlerinden her birini framework yerine sen yapıyorsan composite'ten %2,5 düşüyor.
21 Ekim'de durum şuydu: composite 5267, Experimental'da 13 giriş arasında yedinci. json-tls ve 8gbit sıfırdı, çünkü TLS yoktu. 2000 puan masada duruyordu. json-comp 418'di, puanlanan en zayıf hücre.
Yani iş listesi kendiliğinden çıktı: önce TLS, sonra json-comp.
Ölçtüğüm yer
Benim test makinem hostlab: 8 vCPU'lu bir VMware VM, Xeon E5-2690 v2. Üstünde canlı servislerim de çalışıyor. Benchmark için ideal değil, ama elimdeki bu.
Bu VM'de bir koşu ile bir sonraki arasında ±%10 oynama normal. O yüzden birkaç kuralım var:
- A/B'yi dönüşümlü yap. Önce A'yı üç kez, sonra B'yi üç kez koşturmak yerine A, B, A, B, A, B. VM'in o anki havası iki tarafa da eşit dağılsın.
- Toplamı değil oranı oku. Bu makinede bir HTTP keep-alive isteği yaklaşık 80 µs sunucu CPU'su yiyor, çoğu kernel'de. Mutlak rakam HttpArena makinesiyle kıyaslanmaz. "B, A'dan %20 az CPU harcıyor" kıyaslanır.
- İstek başına CPU'yu ölç. Sunucuyu
taskset -c 0-3ile dört çekirdeğe, istemciyi diğer dördüne sabitliyorum. Koşu öncesi ve sonrası/proc/<pid>/stat'tan sunucunun CPU zamanını okuyup istek sayısına bölüyorum. Throughput gürültülü, CPU/istek çok daha sakin. - Mikrobenchmark'ta min-of-N. En iyi koşu, gürültünün en az karıştığı koşu.
Bir pürüz daha var: HttpArena'nın yük üreticisi gcannon io_uring buffer ring'leri kullanıyor ve hostlab'in 5.15 kernel'i bunu çalıştıramıyor. Ben de kendime küçük bir epoll istemcisi yazdım. Tek işi bağlantı başına bir istek gönderip cevabı beklemek, gecikmeyi histograma yazmak.
Her PR'dan önce de HttpArena'nın kendi validate.sh script'ini koşturuyorum. Son girişte 73/73 geçti, TLS kontrolleri dahil.
Neler kazandırdı
Kısaca, büyükten küçüğe:
- TLS (1.2.0) ve kendi AES-GCM kayıt katmanım (1.3.2). İki sıfır hücre doldu. 10 KB TLS echo başına CPU yarıya indi.
- Kendi DEFLATE'im (1.2.2) ve JSON yolu (1.3.1). json-comp 418'den 902'ye çıktı.
- GC pin'lerini hot path'ten çıkarmak (1.0.1). Allocation testinin göremediği bir global kilit. Ryzen'da json-comp 19,9 binden 51,6 bin istek/sn'ye.
- fd başına Conn hatası (1.0.3). HttpArena'da short-lived 158 bin istek/sn'de 2,2 GiB bellekle tıkanmıştı.
22 Ekim koşusunda composite 7011'e çıktı. 23 Ekim koşusunda 7786: Experimental'da birinci.
Kovalamadığım şey
Sıralamada json-comp'ta 2,49 milyon istek/sn ile açık ara önde bir giriş var: vanilla-epoll. Nasıl yaptığına baktım. Sıkıştırılmış cevapları (sayı, parametre) çiftine göre önbelleğe alıyor. Yani aynı isteği bir daha sıkıştırmıyor.
vanilla-epoll'un tipi "engine" ve o kategori kendi kurallarıyla, ayrı puanlanıyor. Framework kuralları buna izin vermiyor. Ben de girişimde bunu yapmadım. Puan için kural esnetirsem o puanın bir anlamı kalmaz.
Bir gün sonra
24 Ekim'de tabloya bakınca yeni bir isim gördüm: httparena-io_uring. Arkasında libioxd var: C23 ile yazılmış, io_uring üstünde, thread başına bir ring, raw syscall'lar, TLS kayıtları kernel'de (kTLS). HttpArena'yı yöneten MDA2AV'nin kendi kütüphanesi. Experimental olarak girmiş.
Üç profilde önüme geçti:
| Profil | libioxd | Viltrum | Fark |
|---|---|---|---|
| Baseline | 4,25M | 4,06M | %4,6 |
| Short-lived | 2,74M | 2,70M | %1,7 |
Async (/delay/10) | 2,76M | 2,49M | %11 |
İlk soru "bir şey mi bozuldu" oldu. Bozulmamıştı, rakamlarım aynıydı. Sadece biri daha hızlı geldi. Composite 7686'ya indi ama HTTP/1.1'de hâlâ 14 giriş arasında birinciyim. Altın 9'dan 7'ye indi, gümüş 1'den 2'ye çıktı, bir de bronz geldi.
Baseline ve short-lived'da ikimiz de 64 çekirdeği dolduruyoruz. Fark büyük ihtimalle io_uring ile epoll arasındaki syscall maliyeti. %1,7 koşudan koşuya oynayabilecek kadar küçük. %4,6 daha gerçek görünüyor. Onu geri almak için motorun içine io_uring yolu gerekir, bu büyük bir iş.
Async ise başka bir şey söylüyordu. Orada Viltrum 64 çekirdeğin sadece 44'ünü kullanıyordu. CPU eksik değildi. Ortalama gecikme 12,77 ms, libioxd'de 11,53 ms. Her istek 10 ms bekliyor, bağlantı sayısı sabit, yani istek/sn tamamen gecikmeyle belirleniyor.
Fazladan 1,2 ms nereden geliyordu? Cevap basitti. Epoll döngüsü bir sonraki timer'a kadar beklerken timeout'u milisaniyeye yukarı yuvarlıyordum. epoll_wait başka birim almıyor. 0,2 ms sonra gitmesi gereken cevap 1 ms sonra gidiyordu.
Düzeltme epoll_pwait2 ile nanosaniye timeout. hostlab'da 64 ve 128 bağlantıda, dönüşümlü üç turda:
| Bağlantı | İstek/sn | p50 | CPU/istek | |
|---|---|---|---|---|
| 64 | önce | ~5,8 bin | ~11,0 ms | 57-75 µs |
| 64 | sonra | ~6,15 bin | ~10,3 ms | 71-77 µs |
| 128 | önce | ~10,9 bin | ~11,55 ms | 52-62 µs |
| 128 | sonra | ~12,2 bin | ~10,3 ms | 62-72 µs |
10 ms'nin üstündeki fazlalık 1,0-1,7 ms'den 0,3-0,4 ms'ye indi. Bedel istek başına %15-25 daha fazla CPU, çünkü timer'lar artık toplu değil, tek tek ateşleniyor. HttpArena makinesinde 20 boş çekirdek vardı, yer var.
Bir de 200 µs'lik bir pay ekleyip timer'ları toplu ateşlemeyi denedim. Neredeyse eskisi kadar kötü çıktı, CPU'da da kazandırmadı. Attım.
Async'te bronzdan yukarı çıkıp çıkmadığımı bir sonraki koşuda göreceğiz. Bu VM'de 32.000 bağlantıyı deneyemiyorum, o yüzden söz vermiyorum.
Experimental ne demek
Viltrum'un HttpArena'daki tipi "experimental". Varsayılan sıralama tablosu flagship ve emerging girişleri gösteriyor, Experimental'ı görmek için kutucuğu işaretlemen gerekiyor. Puanlama aynı, sadece görünüm farklı.
İlk başta "emerging" yazmıştım. Bakımcı değiştirmemi istedi. Kriteri açık: üretimde kullanım, kullanıcılar, ekosistem. Dışarıdan katkı, açılan issue'lar, internette ondan bahseden insanlar. Yaş yardımcı oluyor ama ikinci planda. Kendi motorunu yazmış olmak da eksi sayılıyor, çünkü sertleştirilmesi gereken bir katman daha demek.
Haklı. Dışarıdan birkaç katkı geldi (en sonuncusu bu haftaki yeni logo), ama gerçek kullanıcı henüz yok denecek kadar az. Bu yüzden yol haritasında sıradaki faz performans değil, topluluk. Bu yazı da onun bir parçası.
Sonuç
Benchmark'tan öğrendiğim üç şey:
- Puanlama formülünü oku. Eksik profil 1000 puan, json-comp'ta bayt karesiyle ceza. Neyin önemli olduğunu formül söylüyor.
- Oranlara güven, toplamlara değil. Gürültülü bir VM'de bile dönüşümlü A/B ve CPU/istek seni doğru yere götürüyor.
- Madalya kaybetmek iyi bir şey. Bir gün önce birinciydim ve async'teki 1 ms'lik yuvarlama hatasını görmüyordum. Biri daha hızlı gelince gördüm.
Sonuç sayfası: http-arena.com/frameworks/viltrum. Girişin kodu: benches/httparena.
Yöntemde bir hata görürsen issue aç. Rakamlara itiraz etmek, rakam yayınlamaktan daha değerli.