deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak Hacker News – Front Page (native)

Rust'ta zero-copy parsing: Bir u64 okumak neden heap'e dokunmamalı

Hacker News üzerinden geniş çapta paylaşılan Sebastian Sastre'nin yazısı, Rust'ta bir u64 parser'ındaki ihmalkar bir byte kopyasının nasıl heap allocation'a yol açtığını ve zero-copy decoding'in saniyede yüz milyonlarca işlemeye nasıl ulaştığını gösteriyor.

Rust'ta zero-copy parsing: Bir u64 okumak neden heap'e dokunmamalı

Minik bir parser'ın içinde saklı allocation

Hacker News'in ana sayfasına çıkan Sebastian Sastre'nin blog yazısı, bir buffer'daki sekiz byte'ı u64'e dönüştüren küçük bir Rust fonksiyonunu irdeliyor. Başlangıçtaki hali zararsız görünüyor: uzunluğu kontrol ediyor, ilk sekiz byte'ı bir Vec'e kopyalıyor ve bunu u64::from_le_bytes'e geçiriyor. Kod kısa, düzgün hatalar döndürüyor ve ucuz görünüyor.

Sorun Vec'te. Rust'ta bir Vec her zaman heap üzerinde allocation yapar ve bu allocation'ın ucuz olması, allocator'ın hazır bir boş blok bulmasına bağlıdır. Yük altında bu blok bulunmayabilir ve allocator işletim sisteminden daha fazla bellek istemek zorunda kalır; böylece önemsiz görünen bir parse, potansiyel bir syscall'a dönüşür. Sastre'nin argümanı şu: bu kopyanın hiçbir işlevi yok; buffer'da duran sekiz byte zaten sayının kendisidir, onları okumak için kopyalamak, sonuca giden yolda tesadüfi bir karmaşıklıktan ibarettir.

Değeri durduğu yerde okumak

Çözümü, yerinde okumak; genel olarak zero-copy parsing olarak bilinen yaklaşım. Fonksiyon zaten bir &[u8] aldığından, bytes.get(..8) ile okunabilir sekiz byte'ın varlığını kontrol edebilir, bunları stack üzerinde sabit boyutlu bir [u8; 8]'e dönüştürebilir ve işi from_le_bytes'e bırakabilir. Byte'lar yine de stack'e taşınır ama heap hiç devreye girmez. Sastre bunu Rust'ın daha geniş gücüne bağlıyor: tipler ve tüm hata durumları düzgün modellendiğinde, derleyicinin garantileri ekstra maliyeti olmayan bir çalışma zamanı davranışına dönüşür.

Fikrin tam bir codec'e dönüşmesi

Sastre ardından tekniği tek bir tamsayının ötesine taşıyor. Üzerinde çalıştığı bir matching-engine projesi için her komutun bir enum varyantı olduğu bir komut codec'i kuruyor: limit order'lar account, instrument, price ve quantity alanlarını taşıyor, iptal işlemleri tek bir order id ile tanımlanıyor ve piyasa emirlerinde ise price alanı hiç yok. Her alan türü bilinçli olarak Copy, enum içinde hiçbir şey bir buffer'a sahip değil ve bu nedenle enum'un tamamı Copy — bu, parsing'in ve downstream işlemenin yerinde yapılabilmesi için bilinçli bir tasarım tercihi diye açıklıyor.

Hat üzerinde bir tür byte'ı düzeni seçiyor ve her düzenin sabit bir uzunluğu var: limit order için 57 byte, order'a göre iptal için 16 byte. Decoder'lar alanları bilinen offset'lerde okuyup tipli constructor'lara sarıyor. Encoding, decoding'in aynası: değerler, çağıran tarafın zaten sahip olduğu bir buffer'a aynı offset'lere yazılıyor, ara allocation yapılmıyor ve journal frame ile datagram yolları tek bir write'ı paylaşıyor.

Pratikte ne kazandırıyor

Yazı, komut türleri arasında decode edilen öğe başına yaklaşık 4.1 nanosaniyelik benchmark sonuçları bildiriyor; bu da saniyede yaklaşık 238 ile 246 milyon komut anlamına geliyor: order'a göre iptal yaklaşık 243 milyon, yeni limit order'lar yaklaşık 238 milyon ve yeni piyasa emirleri yaklaşık 245 milyon. Sastre ayrıca okuyucuları, Docker altında çalışan küçük bir Rust programıyla syscall davranışını kendilerinin doğrulamaya davet ediyor; glibc'nin 128 KiB üzeri allocation'ları mmap ile karşıladığını, bunun da maliyeti küçük allocation'ların gizlediği bir şekilde gözlemlenebilir kıldığını belirtiyor.

Neden önemli

Parsing, neredeyse her veri hattının, feed handler'ın ve mesaj odaklı servisin hot path'inde yer alır ama nadiren gözden geçirilir. Yazının temel dersi Rust ticaret sistemlerinin ötesine iyi genellenir: kullanışlı kopyalar masum görünen kodların içinde gizlenir ve tam da bir sistem yük altındayken pahalı hale gelir; çünkü allocator'ların işletim sistemine başvurduğu an odur. Veri tiplerini zaten durdukları yerde okunabilecek — ve çağıranın sahip olduğu buffer'lara yazılabilecek — şekilde tasarlamak, ister market verisi, log toplama, metrics ister herhangi bir serialization sınırı olsun, önlenebilir maliyetlerin tüm bir sınıfını ortadan kaldırır. Yazarın ölçümlerinin gösterdiği gibi, kazanç marjinal iyileştirmeler değil, saniyede yüz milyonlarca öğelik bir throughput.

  • #rust
  • #zero-copy
  • #performance
  • #parsing
  • #systems-programming

İlgili yazılar