Bir veri grid'inde ana iş parçacığını asıl ne bloke ediyor
· 4 dk okuma
Bir grid'i sanallaştırın, DOM darboğaz olmaktan çıkar: bir milyon satır, yirmi küsur eleman. Bu düzeltme gerçek ve bu yazının konusu değil — çizim tarafını zaten yazdık.
Bu düzeltme, bu yazının konusuna hiç dokunmuyor. Bir sütun başlığına tıkladığınızda veya bir filtreye yazdığınızda grid'in yaptığı tek şey çizim değil. Bir şeyin hâlâ satırları sıralaması, filtrelemesi veya araması gerekiyor, ve bu iş — ekrana kaçı ulaşırsa ulaşsın — gerçek milisaniyelere, bazen gerçek saniyelere mal oluyor.
Önemli olan sayı "kaç satır çiziliyor" değil
Önemli olan "işlem ne kadar sürüyor, ve hangi iş parçacığında."
1.000.000 satırda, üç koşunun medianı, motorumuzun kendi sayıları:
| İşlem | Maliyet |
|---|---|
| Sayısal sıralama | 101 ms |
| Metin sıralama (önbellekli) | 90 ms |
| Metin sıralama (ilk sorgu — sütun başına sıra indeksi kurar) | 1,8 sn |
| Eşitlik filtresi | 18 ms |
| Serbest metin arama (önbellekli) | 65 ms |
| Serbest metin arama (ilk sorgu — sütunu küçük harfe çevirir) | 413 ms |
Bunlar hızlı sayılar — kolonlu yol, tipli diziler, karşılaştırıcı callback'i yok. Yöntem ve bu yolun yerini aldığı satır-tabanlı sayıları da içeren tam tablo benchmarks sayfasında. Hiçbiri sıfır değil. 18 ms'de çalışan bir filtre fark edilmez. Bir kullanıcının bir sütuna ilk kez dokunduğu anda 1,8 saniye süren bir metin sıralaması öyle değil — ve ikisinden biri, grid'i kaydırması ve bir sonraki tıklamaya yanıt vermesi gereken aynı iş parçacığında çalışırsa, sayfa o sürenin tamamında yanıtsız kalır.
Worker için asıl argüman bu. "Worker'lar hızlıdır" değil — yukarıdaki sayılar tek çekirdekte ölçüldü, paralellik yok. Argüman daha dar ve göz ardı edilmesi daha zor: bu iş nerede çalışırsa çalışsın, o iş parçacığındaki girdi iş bitene kadar durur. Worker sıralamayı hızlandırmaz. Donmayı kullanıcının hissedemeyeceği bir yere taşır.
Eşik neden ~20.000 satır, sıfır değil
Her postMessage'ın bir maliyeti var, ve veriyi sıfır-kopya aktarım için
yapılandırmak da bedava değil. Belirli bir boyutun altında, worker'a gidip
gelmek işi yerinde yapmaktan daha yavaş — bu yüzden motorumuzun kendi eşiği
bilerek sıfır değil. Performans sayfasında belgelendiği gibi:
kabaca 20.000 satırın üstünde hat ana iş parçacığından çıkar; altında, ana iş
parçacığında kalmak gerçekten daha hızlıdır.
Ölçek hakkında fikir vermesi için, aynı ölçülen işlemler 100.000 satırda şöyle:
| İşlem | Maliyet |
|---|---|
| Sayısal sıralama | 5,7 ms |
| Metin sıralama (önbellekli) | 5,2 ms |
| Metin sıralama (ilk sorgu) | 147 ms |
| Eşitlik filtresi | 1,8 ms |
Beş milisaniye kimse için bir UX sorunu değil. Bir kez sıraladığınız ve sonra bıraktığınız bir sütunda yüz kırk yedi milisaniye sınırda — arıyorsanız fark edilir, aramıyorsanız unutulur. On kat satırda aynı ilk metin sıralaması yaklaşık on iki kat yavaşladı (1,8 sn), çünkü farklı değerleri sıralamak da bir sıralama ve bu sütundaki her değer farklı. "Bu iş hangi iş parçacığında çalışıyor" sorusunun ayrıntı olmaktan çıktığı aralık burası.
20.000 sayısı bu tablodan okunmuyor. Bu, altında worker'a gidip gelmenin işin kendisinden pahalıya geldiği satır sayısı; o yüzden daha küçük grid'ler ana iş parçacığında kalıyor.
"Bloke" kullanıcı tarafından nasıl görünür
Bir spinner değil. Spinner, hâlâ çalışan ve size meşgul olduğunu göstermeyi seçen bir arayüzdür. Bloke bir ana iş parçacığı, spinner'ı çizen kodu da çalıştıramaz.
Gerçekte nasıl göründüğü: sürüklerken ortada duran bir kaydırma çubuğu, yazdığınız harflerin iş bitene kadar görünmediği, sonra hepsinin birden geldiği bir filtre kutusu, güncellenmeyen bir hover durumu, blok yeterince uzun sürerse Chrome'un kendi "sayfa yanıt vermiyor" diyaloğunun kapatmayı teklif edeceği bir sekme. Bunların hiçbiri kullanıcıya "bir sütun sıralanıyor" olarak okunmaz. "Bozuk" olarak okunur.
Yukarıdaki 1,8 saniyelik ilk metin sıralaması en keskin örnek, çünkü tam da medyan durum değil — bu tabloda, yanlış iş parçacığında çalıştırılınca "biraz takılıyor"dan "bu az önce çöktü mü"ye geçen tek işlem.
Bedava olmayan kısım da var
İşi ana iş parçacığından taşımak, yalnızca veriyi worker'a ulaştırmanın
kendisi ana-iş-parçacığı zamanı harcamıyorsa yardımcı olur. Satır nesnelerini
o sınırın öbür tarafına taşımak ucuz değil: postMessage gönderdiğiniz her
şeyin yapısal bir kopyasını (structured clone) çıkarır, ve bir milyon
JavaScript nesnesini kopyalamak da gönderen iş parçacığında uzun bir iştir.
Motorun önce sütunları tipli dizilere kodlamasının (sayılar için
Float64Array, metin için bir sözlük + Int32Array) ve herhangi bir şeyi
kopyalamak yerine altta yatan buffer'ları aktarmasının nedeni bu: bir
serileştir-ve-klonla değil, sıfır-kopya bir devir. O kodlama geçişinin kendi
maliyeti var, ve çalıştığı iş parçacığını bloke etmeme konusunda kendi
hikâyesi var — performans sayfasında anlatıldı,
burada kapsam dışı.
Bir grid inşa ediyor veya değerlendiriyorsanız
Bir değil iki soru sorun.
"Worker kullanıyor mu?" tek başına yetmez. Bir grid worker kullanıp devri yine ana iş parçacığında yapabilir, ya da sıralamayı taşıyıp filtre ve aramayı senkron bırakabilir.
"Neye mal oluyor, ve bu maliyet nereye düşüyor?" kullanıcılarınızın fark edip etmeyeceğini asıl öngören soru. Rakam isteyin, ve o rakamları yeniden üretmenin bir yolunu. Bizimki yayınlanmış pakete karşı çalıştırabileceğiniz bir script.
Bunun kurulumu — workerUrl yok, public/'a kopyalanacak dosya yok —
worker'ı npm paketine nasıl gömdüğümüzde
anlatılıyor. Tam ölçülen tablo, makine ve yöntem benchmarks sayfasında.