murattunalı.

Bir :has() seçicisi TBT’nin %70’iydi: 369 → 108 ms.

Murat Tunalı YAYIN TARİHİ: OKUMA: 3′ MEASURED Performans

Ana sayfanın engelleme süresi bir turda altı kat arttı; suçlu yeni sahneler ya da animasyon kütüphanesi değil, o sayfada hiç eşleşmeyen on CSS kuralıydı.

Bir turun ardından ana sayfanın engelleme süresi 61 milisaniyeden 369 milisaniyeye çıkmıştı. İlk şüpheli her zamanki gibi JavaScript’ti: o turda iki yeni sahne eklenmişti ve animasyon kütüphanesi uzun görevler üretiyordu. Ölçüm başka bir şey söyledi — pahalı olan betik değil, betiğin dokunduğu belgeydi.

Hipotez

Tarayıcı izini betik, stil ve yerleşim diye ayırdığımızda yükün nerede olduğu görünür hâle geldi: stil hesabı toplamı 423 milisaniyeden 1.392 milisaniyeye çıkmıştı. Belge gerçekten büyümüştü (671 elemandan 1.733 elemana), ama asıl mesele büyüme değildi: her kare belgenin TAMAMINI yeniden hesaplatıyordu. Hipotez buydu — köke bağlı bir seçici var ve her küçük değişiklikte tüm ağacı geçersiz kılıyor.

Yöntem

İki ağaç yan yana koşuldu: dört gün önceki canlı yapı geçici bir çalışma kopyasında, güncel ağaç yanında, ikisi de aynı mobil profilde (412 × 823, piksel oranı 1,75, işlemci dört kat yavaşlatılmış). Sonra stil dosyası istek yakalayıcıyla uçuşta dönüştürüldü — diskte tek bayt değişmeden kural aileleri teker teker çıkarıldı ve her seferinde engelleme süresinin medyanı okundu. Tarayıcının kendi geçersizleme izi de açıldı; o iz hangi elemanın hangi seçiciden etkilendiğini satır satır yazıyor.

Tarih ve ortam

  1. tarih → 2026-09-21 · yerel A/B · iki ağaç yan yana
  2. profil → 412 × 823 · DPR 1,75 · CPU dört kat yavaş
  3. ölçü → TBT medyanı · stil hesabı toplamı · eleman sayısı
  4. iz → devtools.timeline.invalidationTracking

Sonuç: bisect

Kuralları aileler hâlinde çıkarmak suçluyu tek başına bıraktı. Tüm :has() seçicileri kaldırıldığında engelleme süresi 369 milisaniyeden 104 milisaniyeye iniyordu; yalnız öznesi html ya da body olanlar kaldırıldığında 115 milisaniyeye. Tek tek denendiğinde yükün neredeyse tamamı tek bir aileye toplandı.

  1. olduğu gibi → TBT 369 ms
  2. tüm :has() çıkarılmış → 104 ms
  3. yalnız html/body özneli → 115 ms
  4. :has(.menu … → 431 ms
  5. :has(main.oda) … → 430 ms
  6. :has(.sayfa__ … → 112 ms

Suçlu: hiç eşleşmeyen on kural

Kalan aday, temel stil dosyasının on kuralıydı ve hepsi aynı kalıptaydı: öznesi html olan bir :has(), ardından geniş bir sağ taraf. Bu kuralların ana sayfada eşleşmediğini de ölçtük — hiçbiri o sayfada bir şey boyamıyordu. Yine de her tween, her motor kurulumu belgenin tamamını geçersiz kılıyordu. Çünkü tarayıcı «bu değişiklik kökteki :has() sonucunu değiştirmiş olabilir mi» sorusuna ancak alt ağacı yeniden hesaplayarak cevap verebiliyor. Geçersizleme izi bunu adıyla söylüyordu: html ve body üzerinde «:has() tarafından etkilenen» 350 ve 347 kayıt, yanında «geçersizleme kümesi alt ağacı geçersiz kılar» notu.

Çözüm: soruyu derlemeye taşımak

Sorulan soru aynı kaldı, soran taraf değişti. Bir sayfanın hangi konağı taşıdığı yükleme sonrası değişmeyen bir yapıdır — yani derleme zamanında zaten bilinir. Üretici son HTML’e bakıyor ve kökün üstüne bir im basıyor, stil de artık o imi soruyor. Özgüllük birebir aynı kaldı, yani hiçbir kuralın sırası kaymadı; hesaplanan stil farkı on iki rotada, iki genişlikte, on beş binden fazla eleman üzerinde sıfır çıktı.

  1. yerel TBT → 369 → 97–108 ms (5 koşum)
  2. stil hesabı → 1.415 → 783 ms
  3. hesaplanan stil farkı → 0 (12 rota × 2 genişlik)
  4. canlı TBT medyanı → 293 → 83 / 86 ms

Nöbetçi

Bulgu bir kapıya bağlandı, çünkü yorumla yazılmış bir karar bir sonraki turda sessizce bozulur. Kapı üç şey soruyor: im ile konak diskte iki yönlü eşleşiyor mu, üretilmiş stil dosyasında o kalıp kaldı mı, ve iki rotanın yüklenmesinde kökün alt ağacı kaç kez geçersiz kılınıyor. Kapının düşebildiği, kusur geri konarak doğrulandı: eski kurallar geri yazıldığında sayaç ikiden kırk dokuza çıkıyor. Düşemeyen bir nöbetçi yeşil değildir, yalnız sessizdir.

Sınırlar

Buradaki sayılar bu makinenin ve bu belgenin sayıları; başka bir sayfada oran başka çıkar. Çıkarılacak kural «:has() yavaştır» da değil — o seçici yerinde kullanıldığında ucuzdur. Kural şu: öznesi html ya da body olan bir :has(), eşleşmediği sayfada bile her değişiklikte belgenin tamamını geçersiz kılar. Yükleme sonrası değişmeyen bir yapıyı çalışma zamanında seçiciyle sormak yerine derlemede işaretlemek gerekiyor.

Bir seçicinin maliyeti eşleştiği yerde değil, aradığı yerde.

Murat Tunalı

Kurucu. 20 yıl kurumsal IT altyapısı + web geliştirme.

Sonraki yazı

KAYNAKLAR

ŞEMA: BLOGPOSTING + PERSON + BREADCRUMBLIST — YAZAR, TARİH VE OKUMA SÜRESİ ALANLARI ŞEMAYI BESLER