10 puan yazan GN⁺ 2025-08-05 | 1 yorum | WhatsApp'ta paylaş
  • V8 motorunda JSON.stringify fonksiyonunun performansı 2 katından fazla artırılarak veri serileştirme hızında iyileşme sağlandı
  • Yan etkisiz nesneler için bir optimizasyon yolu eklenerek çok sayıdaki savunmacı kontrol mantığı atlandı ve yaygın veri nesnelerinde büyük hız artışı elde edildi
  • String işleme sırasında 1 bayt/2 bayt ayrımı, SIMD kullanımı, geçici tampon yapısının değiştirilmesi gibi donanım ve bellek tarafında gelişmiş yöntemler uygulandı
  • Sayı dönüştürme sürecinde mevcut Grisu3 algoritması Dragonbox ile değiştirildi; böylece Number.toString() çağrılarının genelinde daha hızlı dönüşüm mümkün hale geldi
  • Bazı argüman ve biçimlerde normal serileştirme yoluna geri dönülse de, çoğu web geliştirme senaryosunda optimizasyon etkisinden otomatik olarak yararlanılabiliyor

Genel bakış

  • JSON.stringify, JavaScript'te veriyi stringe dönüştüren temel bir fonksiyondur
  • Bu fonksiyondaki performans artışı, ağ istekleri veya localStorage'a kayıt gibi web için çok önemli işlemleri de olumlu etkiler
  • En güncel V8 mühendisliğiyle bu özelliğin hızı 2 katından fazla artırıldı ve başlıca optimizasyon yöntemleri ayrıntılı biçimde tanıtıldı

Yan etkisiz Fast Path yolu

  • Optimizasyonun çekirdeği, yalnızca yan etki (side-effect) olmayan durumlarda kullanılabilen hızlı serileştirme yolunun uygulanmasıdır
  • Bu durumlarda nesneler özyinelemeli değil, iteratif bir yapıyla dolaşıldığı için stack overflow kontrolüne gerek kalmaz ve daha derin nesnelerin serileştirilmesi de mümkün olur
  • Veri nesnesi basit olduğunda V8, yavaş genel mantık yerine bu Fast Path'i kullanarak birçok kontrolü atlar ve hızı artırır

Farklı string gösterimlerinin işlenmesi

  • V8, 1 bayt/2 bayt karakterlere (ASCII/ASCII dışı) göre stringleri farklı biçimde saklar; tek bir ASCII dışı karakter bile varsa tamamı 2 bayt olarak yönetilir
  • String serileştirme performansı için string türüne göre ayrı algoritma sürümleri üretilip derlenir
  • İşlem sırasında string örneğinin türü kontrol edilmek zorunda olduğundan, 2 baytlık bir string algılanırsa uygun 2 baytlık serileştirici durumu devralır
  • Bu sayede string kodlamasına göre yol değiştirme yükü pratikte yok denecek kadar azdır
  • Sonuçta 1 bayt ve 2 bayt tamponları ayrı ayrı oluşturulur, ardından en sonda basitçe birleştirilir
Reklam

SIMD ile string serileştirme optimizasyonu

  • JavaScript stringleri, JSON serileştirmesinde kaçışlanması gereken karakterler içerebilir
  • Uzun stringler, SIMD donanım komutlarıyla (ARM64 Neon gibi) aynı anda birden çok bayt taranarak işlenir
  • Kısa stringler ise SWAR yöntemiyle, genel amaçlı register'larda yapılan bit işlemleri sayesinde birden çok karakteri aynı anda işler
  • Hangi yöntem kullanılırsa kullanılsın, çoğu durumda tüm string neredeyse hiç dönüşüm olmadan hızla kopyalanabilir

Express Lane (ultra hızlı yol) eklendi

  • Fast Path içinde de property kontrolü gibi tekrarlayan işler olmadan yalnızca anahtar kopyalayarak serileştirme yapılabilmesi için Express Lane eklendi
  • Nesnenin hidden class bayrağı kullanılarak, anahtarlarda Symbol bulunmadığı, hepsinin enumerable olduğu ve kaçış gerektirmeden serileştirilebildiği durumda nesne fast-json-iterable olarak işaretlenir
  • Aynı hidden class'a sahip başka bir nesne serileştirilirken ek kontroller olmadan doğrudan anahtar kopyalama yapılır
  • Bu teknik JSON.parse içinde de hızlı anahtar karşılaştırması için kullanılır

Daha hızlı double-to-string algoritması

  • Sayıların stringe dönüştürülmesi de hem sık gerçekleşen hem de karmaşık bir işlemdir
  • Mevcut Grisu3 algoritması Dragonbox ile değiştirilerek, Number.prototype.toString() çağrılarının tamamında performans artışı sağlandı
Reklam

Geçici tampon yapısının optimize edilmesi

  • String oluşturulurken önceden tek ve kesintisiz bir tampon kullanılıyordu; yer yetmediğinde tüm içeriğin kopyalanması gerektiği için ek yük oluşuyordu
  • Yeni yöntem parçalı (segmented) tampon yapısı kullanıyor; birden çok küçük tampon ihtiyaç oldukça birbirine ekleniyor
  • Böylece alan yetersiz kaldığında tümünü kopyalamak yerine yalnızca yeni bir tampon ayırmak yeterli oluyor

Sınırlar

  • Fast Path yalnızca basit veri serileştirmesinde çalışır
  • Aşağıdaki koşullar karşılanmazsa genel yol kullanılır
    • replacer veya space argümanları kullanılamaz (Pretty-Print veya dönüştürme yok)
    • toJSON gibi özel metodu olmayan basit nesneler olmalıdır
    • İndeks tabanlı property varsa yavaş yola geçilir
    • ConsString gibi özel stringler işlenmez
  • Yine de veri serileştirme, API yanıtı üretme, ayar önbellekleme gibi yaygın kullanım alanlarında optimizasyon etkisi otomatik olarak uygulanır

Sonuç

  • JSON.stringify için temel tasarımdan bellek ve karakter işlemeye kadar tüm alanlarda yaklaşım yeniden düzenlenerek JetStream2 benchmark'ına göre 2 katından fazla hız artışı sağlandı
  • Bu iyileştirmeler V8 sürüm 13.8 (Chrome 138) ve sonrasında doğrudan deneyimlenebilir

1 yorum

 
GN⁺ 2025-08-05
Hacker News yorumu
  • JSON kodlamasının NodeJS'te süreçler arası iletişimde büyük bir darboğaz olduğu hissediliyor

    • Çoğu kişi sonunda event loop gecikmesini azaltmak için işi başka bir thread'e devretmeye çalışıyor, ama ana thread'in CPU yükünün sonunda 3 katına çıktığını fark ediyor
    • Dizileri tek tek stringify eden örnekleri de çok gördüm; görünüşe göre içeride de buna benzer bir yaklaşım kullanılıyor
    • V8 ekibinin bu kısmı daha da güçlendirmesini isterdim
    • Bazı veri setleri için bailout olmadan işlenip işlenemeyeceğini ya da CString işleme sorununun nasıl ele alındığını merak ediyorum; faststr özelliğinin geri dönüp dönmeyeceği de ilgimi çekiyor
    • Geçen yıl ilk kez Node performans analizi yaptığımda, JSON.stringify'ın Node servislerinde performansın önündeki en büyük engellerden biri olduğunu görmüştüm
      • Dictionary key olarak stringify kullanmak gerekiyor ve apollo/express tüm yanıtı tek seferde string'e serileştiriyor, streaming yapmıyor
      • JVM ya da Go tarafından gelen biri olarak, Node'daki bu durum bana epey amatörce gelmişti
    • Python'da da aynı sorun var
      • Yaygın kalıplar için yüksek seviyeli API'lerin üzerinde verimli IPC primitive'leri olsa güzel olur diye düşünüyorum
    • JSON kodlamasının iletişimde büyük bir engel olduğu görüşüne katılıyorum
      • Dünya genelinde iletişimde JSON işlemenin yarattığı hesaplama overhead'inin ne kadar büyük olduğunu ve bytes'ı sabit format gibi daha verimli ayrıştırılabilen bir biçimde (ör. ASN.1) doğrudan göndermenin daha iyi olup olmayacağını merak ediyorum
    • V8 ekibinin bu alanı daha agresif biçimde güçlendirmesine karşıyım; bu sorunla karşılaşan geliştiricilerin başka araçlar aramasını öneririm
      • Node/V8'in backend ya da yüksek performanslı hesaplama problemleri için pek uygun olmadığını düşünüyorum
      • Javascript web kullanımı için tasarlandı ve uzun süre de öyle kalacak, bu yüzden V8 ekibinin bu tür sorunları çözmesi gerekmiyor
      • Typescript ekibi de Go'ya geçti ve diller arası dönüşümün otomasyonu da mümkün
    • Worker'a offload ederek serileştirme/deserileştirme süresinden daha fazla kazanç sağladığım durum neredeyse sadece bir kez oldu
      • Veri büyüdükçe büyük ve pahalı mesaj iletimi, paralelleştirmeden elde edilen kazanca yetişiyor
  • Son 10 küsur yılda kayan noktalı sayı serileştirme performansının ne kadar ilerlediği gerçekten şaşırtıcı

    • IEEE kayan nokta değerlerini decimal UTF-8 string'lere çevirip sonra geri dönüştürmek yalnızca yavaş değil, aynı zamanda oldukça kırılgan
      • İkilik ve ondalık sistemde tam olarak ifade edilebilen değerler farklı olduğu için küçük hatalar oluşabiliyor
  • JSON.stringify içinde replacer ya da space argümanı varsa fast path'in uygulanmadığı söyleniyor

    • O halde JSON.stringify(data, null, 0) kullanıldığında da fast path mümkün mü, yoksa argümanların mutlaka undefined olması mı gerekiyor, merak ediyorum
  • SWAR escaping algoritması[1], Folly JSON'da uyguladığımız şeye çok benziyor[2]

  • Bu çalışmanın değerinden şüphe duymuyorum ama gerçek V8 ekosisteminde JSON.stringify'ın runtime'ı baskıladığı somut problemler ya da veriler hakkında daha çok şey merak ediyorum

    • Çalışma süresinin payının mutlaka ezici olması gerekmiyor; sonuçta her gün yüz milyonlarca sayfada çağrılıyor, dolayısıyla küresel elektrik tasarrufu etkisi kayda değer olurdu
  • Bence v8'in performansı yeterince takdir edilmiyor; bugünlerde JS inanılmaz hızlandı

    • Gerçekten hayranlık uyandırıcı; bunu, “1 milyar dolarınız varsa her sorunu çözebilirsiniz” sözünün iyi bir örneği olarak görüyorum
      • Gelecekte JS'nin “strict”, “stricter” gibi biçimlerde daha da evrilip derleme/JIT açısından kolay ve basit bir dile dönüşmesini isterdim
    • Öte yandan v8 öyle uç noktalara kadar optimize edildi ki, dünyada iç yapısını gerçekten anlayan insan sayısı belki 100 civarındadır; çoğu geliştirici de muhtemelen “neden benim JS'im hızlı değil?” diye düşünüyordur
  • Bunu diğer ekosistemlerle kıyaslayınca ne kadar etkileyici olduğunu merak ediyorum

    • 10 yılı aşkın süredir JSON serileştirme yapıyorum ama o kadar hızlı ki neredeyse hiç endişelendiğim olmadı
    • simdjson çekirdek başına saniyede GB düzeyinde işleyebiliyor; prefetching/branch prediction gibi optimizasyonları düşününce JSON serileştirmenin çoğu gerçek dünyadaki workload için göz ardı edilebilecek kadar küçük kaldığını düşünüyorum
    • JSON'un en büyük sorunu IO overhead'i; serializer ne kadar hızlı olursa olsun, her seferinde 100MB'lık bir blob'u depolamaya yazmanız gerekiyorsa bunun pek anlamı kalmıyor
  • “No indexed properties on objects” — fast path'in yalnızca sıradan string tabanlı key'lere sahip objeler için optimize edildiği söyleniyor; array benzeri index property'leri varsa slow path'e dönüyor

    • Bunun sebebini merak ediyorum
    • Integer gibi görünen key'lere sahip objelerin JSON array olarak serileştirildiği anlamına mı geliyor? Yoksa gerçekten öyle değil herhalde...?
  • Segmented buffer yaklaşımını beğendim; eskiden fast-json-stringify gibi userland kütüphanelerle rope numaralarını elle kurmak gerekiyordu, şimdi native olması çok daha iyi

    • Bailout koşullarının (ör. replacer, space, özel .toJSON()) ne kadar sık görüldüğünü merak ediyorum; bu gibi durumlarda doğrudan yavaş yola mı fallback ediliyor diye soruyorum
  • V8 mükemmel ama performans açısından, belki de JS'nin kendisi yüzünden, LuaJIT ya da JVM'in gerisinde kalıyor

    • JVM'in ısınma süresi uzun ama buna rağmen yine de JS'den daha iyi
    • Sebep JS; V8'in LuaJIT ve JVM'den çok daha ileri olduğunu düşünüyorum
      • Java'nın gerçek zamanlı kısıtları daha az (bir derleyicisi var) ve bu ona avantaj sağlıyor
    • JS'deki birçok overhead, dinamik doğasından kaynaklanıyor
      • asm.js, object shape değişimi gibi dinamik davranışları yasaklayarak birçok kontrolün atlanabilmesini sağlamıştı
    • “JVM bile” ifadesine itiraz ediyorum; JVM üst düzey bir sistemdir