renderNG ayrıntılı incelemesi: BlinkNG

Stefan Zager
Stefan Zager
Kişi adı 1
Chris Harrelson

Yanıp sönme, Chromium'da web platformu kullanımını ifade eder ve birleştirmeden önceki tüm oluşturma aşamalarını kapsar ve en sonunda birleştirici kaydetme ile sonuçlanır. Yanıp sönme mimarisi hakkında daha fazla bilgiyi bu serinin önceki makalelerinde bulabilirsiniz.

Blink, hayatı 1998 yılına kadar uzanan KHTML'nin bir çatalı olan WebKit'in bir çatalı olarak başladı. Chrome, Chromium'daki en eski (ve en önemli) kodlardan bazılarını içerir ve 2014 yılına gelindiğinde kesinlikle eski olduğunu gösteriyordu. Bu yıl, BlinkNG adını verdiğimiz kuruluş başlığı altında, Blink kodunun kuruluşundaki ve yapısındaki uzun süredir devam eden eksiklikleri gidermeyi amaçlayan bir dizi iddialı projeye giriş yaptık. Bu makalede BlinkNG ve onu oluşturan projelere değineceğiz: Bu projeleri neden yaptığımız, neler başardıkları, tasarımlarını şekillendiren yol gösterici ilkeler ve gelecekte sunabilecekleri iyileştirmeler için fırsatlar.

BlinkNG'den önceki ve sonraki oluşturma ardışık düzeni.

NG öncesi oluşturma

Blink'teki görüntü oluşturma ardışık düzeni her zaman kavramsal olarak aşamalara (stil, düzen, boya vb.) bölündü ancak soyutlama bariyerleri sızıyordu. Genel olarak oluşturma ile ilişkili veriler, uzun ömürlü ve değişebilir nesnelerden oluşuyordu. Bu nesneler herhangi bir zamanda değiştirilebilir ve eskiden değiştirilebilirdi. Ayrıca, art arda yapılan oluşturma güncellemeleriyle sıklıkla geri dönüştürülüp yeniden kullanıldı. Aşağıdaki gibi basit soruları güvenilir bir şekilde yanıtlamak imkansızdı:

  • Stil, düzen veya boya çıktısının güncellenmesi gerekiyor mu?
  • Bu veriler ne zaman "nihai" değerlerine ulaşır?
  • Bu verilerin ne zaman değiştirilmesi uygundur?
  • Bu nesne ne zaman silinecek?

Bunun aşağıdakiler gibi birçok örneği vardır:

Stil, stil sayfalarını temel alarak ComputedStyle'ler oluşturur; ancak ComputedStyle sabit değildi; bazı durumlarda, sonraki ardışık düzen aşamaları tarafından değiştirilirdi.

Stil, LayoutObject ağacını oluşturur ve düzen bu nesnelere boyut ve konumlandırma bilgileri ekler. Bazı durumlarda düzen, ağaç yapısını bile değiştirir. Düzen'in girişleri ve çıkışları arasında net bir ayrım yoktu.

Stil, birleştirme sürecini belirleyen aksesuar veri yapıları oluşturur ve bu veri yapıları, style'dan sonraki her aşamada değiştirilmiştir.

Daha düşük düzeyde, veri türlerini oluşturma işlemi büyük ölçüde özel ağaçlardan (ör. DOM ağacı, stil ağacı, düzen ağacı, boyama mülk ağacı) oluşur. Oluşturma aşamaları ise yinelemeli ağaç yürüyüşleri olarak uygulanır. İdeal olarak, ağaç yürüyüşünde bağımlı olması gerekir: Belirli bir ağaç düğümünü işlerken, kök ağacın bu düğümde yer aldığı alt ağacın dışındaki hiçbir bilgiye erişmememiz gerekir. RenderingNG böyle bir durum değildir. Ağaçlar, işlenmekte olan düğümün üstlerinden sıklıkla erişilen bilgileri gezer. Bu da sistemi son derece hassas hale getirdi ve hataya açık hale getirdi. Ayrıca, bir ağaç yürüyüşüne ağacın kökü hariç herhangi bir yerden başlamak da imkansızdı.

Son olarak, oluşturma ardışık düzeninde kodun tamamına yerleştirilmiş çok sayıda giriş çıkışı bulunuyordu: JavaScript tarafından tetiklenen zorunlu düzenler, doküman yükleme sırasında kısmi güncellemeler tetikleniyor, etkinlik hedeflemeye hazırlanırken zorunlu güncellemeler, görüntüleme sistemi tarafından istenen programlanmış güncellemeler ve yalnızca test koduna maruz kalan özel API'ler. Hatta oluşturma ardışık düzenine giden birkaç yinelemeli ve tekrar giriş yapan yol bile vardır (yani bir aşamanın başlangıcına bir başka aşamanın ortasından atlama). Bu geçişlerin her birinin kendine özgü davranışı vardır ve bazı durumlarda oluşturma çıktısı, oluşturma güncellemesinin tetiklenme şekline bağlı olur.

Neleri değiştirdik?

BlinkNG, her biri daha önce açıklanan mimari eksiklikleri ortadan kaldırmayı hedefleyen irili ufaklı birçok alt projeden oluşur. Bu projelerde, oluşturma ardışık düzenini gerçek bir ardışık düzene daha uygun hale getirmek için tasarlanmış birkaç yol gösterici ilke vardır:

  • Tek tip giriş noktası: Ardışık düzeni her zaman en başta girmemiz gerekir.
  • İşlevsel aşamalar: Her aşamanın iyi tanımlanmış giriş ve çıkışları olmalıdır. Aşamanın davranışı işlevsel, yani belirleyici ve tekrarlanabilir olmalı, çıkışlar ise yalnızca tanımlanan girişlere bağlı olmalıdır.
  • Sabit girişler: Sahne çalışırken herhangi bir aşamanın girişleri etkin bir şekilde sabit olmalıdır.
  • Sabit çıkışlar: Bir aşama tamamlandıktan sonra, oluşturma güncellemesinin geri kalanında çıkışları sabit olmalıdır.
  • Kontrol noktası tutarlılığı: Her aşamanın sonunda, o ana kadar üretilen oluşturma verileri kendiliğinden tutarlı bir durumda olmalıdır.
  • İşin tekilleştirilmesi: Her öğe yalnızca bir kez hesaplanır.

BlinkNG alt projelerinin tam bir listesi yorucu bir okuma gerektirebilir, ancak bunlardan bazıları aşağıda sıralanmıştır.

Belgenin yaşam döngüsü

DocumentLifecycle sınıfı, oluşturma ardışık düzeni aracılığıyla ilerleme durumumuzu takip eder. Bu araç, daha önce listelenen değişmez değerleri zorunlu kılan temel kontrolleri yapmamızı sağlar. Örneğin:

  • Bir ComputedStyle özelliği değiştiriliyorsa belge yaşam döngüsü kInStyleRecalc olmalıdır.
  • DocumentLifecycle durumu kStyleClean veya daha yeniyse NeedsStyleRecalc() eklenen tüm düğümler için false (yanlış) değerini döndürmelidir.
  • paint yaşam döngüsü aşamasına girerken yaşam döngüsü durumu kPrePaintClean olmalıdır.

BlinkNG'yi uygulama sürecinde, bu sabitleri ihlal eden kod yollarını sistematik olarak ortadan kaldırdık ve gerileme yapmadığımızdan emin olmak için kod geneline çok daha fazla onay ekledik.

Daha önce düşük seviyeli oluşturma koduna bakıp tavşan deliğe düştüyseniz kendinize "Buraya nasıl geldim?" diye sorabilirsiniz. Daha önce de belirtildiği gibi, oluşturma ardışık düzenine girilecek çeşitli noktalar vardır. Önceden, yinelemeli ve yeniden giriş yapan çağrı yolları ve ardışık düzene baştan başlamak yerine ara bir aşamada girdiğimiz yerler bu kapsama giriyordu. BlinkNG sürecinde, bu arama yollarını analiz ettik ve hepsinin iki temel senaryoya indirgenebileceğini belirledik:

  • Tüm oluşturma verilerinin güncellenmesi gerekir (örneğin, görüntüleme için yeni pikseller oluştururken veya etkinlik hedefleme için isabet testi yaparken).
  • Belirli bir sorgu için, tüm oluşturma verileri güncellenmeden yanıtlanabilecek güncel bir değere ihtiyacımız vardır. Bu, çoğu JavaScript sorgusunu (örneğin, node.offsetTop) içerir.

Şu anda, oluşturma ardışık düzenine bu iki senaryoya karşılık gelen yalnızca iki giriş noktası bulunmaktadır. Devam eden kod yolları kaldırılmış veya yeniden düzenlendiğinden ara aşamadan itibaren ardışık düzene girilemiyor. Bu da güncellemelerin tam olarak ne zaman ve nasıl gerçekleştiği konusundaki bilinmeyenleri ortadan kaldırarak sistemin davranışı hakkında akıl yürütmeyi çok daha kolay hale getirdi.

Ardışık düzen stili, düzeni ve boyama öncesi

Boya işleminden önceki oluşturma aşamaları toplu olarak aşağıdakilerden sorumludur:

  • DOM düğümlerinin nihai stil özelliklerini hesaplamak için stil basamakları algoritmasını çalıştırma.
  • Dokümanın kutu hiyerarşisini temsil eden düzen ağacı oluşturuluyor.
  • Tüm kutular için boyut ve konum bilgilerinin belirlenmesi.
  • Boyama işlemi için alt piksel geometrisinin bütün piksel sınırlarına yuvarlanması veya tutturulması.
  • Birleştirilmiş katmanların özelliklerini belirleme (afin dönüşüm, filtreler, opaklık veya GPU ile hızlandırılabilen diğer her şey).
  • Önceki boyama aşamasından bu yana hangi içeriğin değiştiği ve boyanması veya yeniden boyanması (boyanın geçersiz kılınması) belirleniyor.

Bu liste değişmedi ancak BlinkNG'ten önce bu işin büyük kısmı, birden fazla oluşturma aşamasına yayılmış, birden fazla oluşturma aşamasına ve birden fazla işlevle ve yerleşik verimsizliklerle anlık bir şekilde gerçekleştiriliyordu. Örneğin, düğümlerin nihai stil özelliklerinin hesaplanması her zaman öncelikli olarak style aşamasını oluşturmuştur, ancak style aşaması tamamlanana kadar nihai stil özelliği değerlerini belirlemediğimiz birkaç özel durum vardır. Oluşturma sürecinde, stil bilgilerinin eksiksiz ve değiştirilemez olduğunu kesin bir şekilde söyleyebileceğimiz resmi veya uygulanabilir bir nokta yoktu.

BlinkNG öncesi soruna bir başka iyi örnek de boyanın geçersiz kılınmasıdır. Önceden, boyama geçersiz kılma işlemi, boyamadan önceki tüm oluşturma aşamalarında yayılıyordu. Stil veya düzen kodu değiştirilirken, geçersiz kılma mantığı için hangi değişikliklerin gerekli olduğunu bilmek zordu. Ayrıca, eksik veya fazla geçersiz kılma hatalarına yol açan bir hata yapmak da kolaydı. LayoutNG'a ayrılmış bu serideki makalede, eski boya geçersiz kılma sisteminin incelikleri hakkında daha fazla bilgi edinebilirsiniz.

Boyama amacıyla alt piksel düzeni geometrisinin tüm piksel sınırlarına tutturulması, aynı işlevin birden çok kez uygulanmasına ve birçok gereksiz işlem yaptığımıza bir örnektir. Boyama sistemi tarafından kullanılan bir piksel tutturma kod yolu ve boya kodu dışında piksel eklenmiş koordinatların tek seferlik, anında hesaplanması gerektiğinde tamamen ayrı bir kod yolu kullanılıyordu. Elbette her uygulamanın kendi hataları vardı ve bunların sonuçları her zaman eşleşmedi. Bu bilgiler önbelleğe alınmadığından, sistem bazen tam olarak aynı hesaplamayı tekrar tekrar yapıyordu. Bu da performansta bir başka yük oluşturuyordu.

Aşağıda, boyadan önceki oluşturma aşamalarındaki mimari eksiklikleri ortadan kaldıran bazı önemli projeler verilmiştir.

Proje Ekibi: Stil aşamasının planlanması

Bu proje, stil aşamasındaki iki ana eksikliği ele aldı. Bu eksiklikler, ardışık düzene düzenli bir şekilde geçilmesini engelliyordu:

Stil aşamasının iki ana çıkışı vardır: DOM ağacı üzerinde CSS basamaklı algoritmasının çalıştırılmasının sonucunu içeren ComputedStyle ve düzen aşaması için işlem sırasını belirleyen LayoutObjects ağacı. Kavram olarak, basamaklı algoritmanın çalıştırılması yalnızca düzen ağacı oluşturulmadan önce gerçekleşmelidir; ancak daha önce bu iki işlem aralıklı olarak kullanılıyordu. Project Squad, bu ikisini farklı, ardışık aşamalara ayırmayı başarıyla başardı.

Önceden stil yeniden hesaplaması sırasında ComputedStyle her zaman nihai değerini alamıyordu. Daha sonraki bir ardışık düzen aşamasında ComputedStyle güncellendiği birkaç durum meydana gelmişti. Project Squad, bu kod yollarını başarıyla yeniden düzenledi. Böylece, stil aşamasından sonra ComputedStyle hiçbir zaman değiştirilmedi.

LayoutNG: Düzen aşamasını ardışık düzen haline getirme

RenderingNG'nin temel taşlarından biri olan bu anıt projesi, düzen oluşturma aşamasının baştan sona yeniden yazılmasıydı. Burada tüm projenin hakkını vermeyeceğiz, ancak genel olarak BlinkNG projesinin göze çarpan birkaç yönü var:

  • Önceden, düzen aşamasında stil aşaması tarafından oluşturulan bir LayoutObject ağacı alınıyordu ve ağaç boyut ile konum bilgilerini ek olarak ekliyordu. Bu nedenle, girdiler ile çıktılar arasında net bir ayrım yoktu. LayoutNG, düzenin birincil salt okunur çıkışı olan ve sonraki oluşturma aşamalarında birincil giriş görevi gören parça ağacını kullanıma sunmuştur.
  • LayoutNG, kapsayıcı özelliğini düzene getirdi: Belirli bir LayoutObject öğesinin boyutunu ve konumunu hesaplarken, artık bu nesneye rootlanmış alt ağacın dışına bakmayız. Belirli bir nesnenin düzenini güncellemek için gereken tüm bilgiler önceden hesaplanır ve algoritmaya salt okunur bir giriş olarak sağlanır.
  • Daha önce, düzen algoritmasının kesin olarak işlevsel olmadığı uç durumlar vardı: Algoritmanın sonucu önceki en son düzen güncellemesine dayanıyordu. LayoutNG bu durumları ortadan kaldırdı.

Boyama öncesi aşaması

Önceden, resmî boya öncesi oluşturma aşaması yoktu, yalnızca düzen sonrası operasyonları için bir torba vardı. Boyama öncesi aşaması, düzen tamamlandıktan sonra düzen ağacında sistematik bir geçiş olarak en iyi şekilde uygulanabilecek birkaç ilgili işlevin olduğunun kabul edilmesiyle ortaya çıktı. En önemlisi ise:

  • Boya geçersiz kılma işlemlerini yayınlama: Eksik bilgiler olduğunda, düzen süresince boyama geçersiz kılma işlemlerini doğru bir şekilde yapmak çok zordur. İki farklı sürece bölündüğünde daha doğru sonuçlar elde etmek çok daha kolaydır ve içerik, stil ve düzen sırasında "boyanın geçersiz kılınması gerekebilir" şeklinde basit bir boole flag'i ile işaretlenebilir. Ağaç boyama öncesi yürüyüşü sırasında bu işaretleri kontrol eder ve gerektiğinde geçersiz kılma işlemleri gerçekleştiririz.
  • Boya ağaçları oluşturma: Daha ayrıntılı olarak açıklanan bir süreç.
  • Piksel içeren boyama konumlarını hesaplama ve kaydetme: Kaydedilen sonuçlar, boyama aşamasında ve fazladan hesaplamaya gerek kalmadan bunlara ihtiyaç duyan herhangi bir aşağı akış kodu tarafından kullanılabilir.

Mülk ağaçları: Tutarlı geometri

Mülk ağaçları, web'de diğer tüm görsel efekt türlerinden farklı bir yapıya sahip olan kaydırma işleminin karmaşıklığıyla başa çıkmak için RenderingNG'nin başlarında kullanıma sunulmuştur. Mülk ağaçlarından önce, Chromium'un toplayıcısı, birleştirilmiş içeriğin geometrik ilişkisini temsil eden tek bir "katman" hiyerarşisi kullanıyordu. Ancak, location:fixed gibi özelliklerin tüm karmaşıklıkları ortaya çıktıkça bu katman hızlı bir şekilde ortadan kalktı. Katman hiyerarşisinde, bir katmanın "kaydırma üst öğesini" veya "klip üst öğesini" belirten yerel olmayan ekstra işaretçiler arttı ve çok geçmeden kodu anlamak çok zor hale geldi.

Mülk ağaçları, içeriğin taşma kaydırma ve kırpma özelliklerini diğer tüm görsel efektlerden ayrı olarak göstererek bu sorunu giderdi. Bu da web sitelerinin gerçek görsel ve kaydırma yapısının doğru şekilde modellenmesini mümkün kıldı. Ardından, "tek" yapmamız gereken, mülk ağaçlarının üzerine, birleştirilmiş katmanların ekran alanı dönüşümü veya hangi katmanların kaydırılıp hangilerinin kaydırmadığını belirlemek gibi algoritmalar uygulamaktı.

Aslında kısa süre içinde, kodda benzer geometrik soruların gündeme getirildiği başka birçok yer olduğunu fark ettik. (Önemli veri yapıları yayınında daha kapsamlı bir liste bulunmaktadır.) Bunların bazıları birleştirici koduyla aynı şeyi tekrar tekrar uygulamaya başladı, hepsinde farklı hata alt kümeleri vardı ve hiçbiri doğru bir şekilde modellenmemiş gerçek web sitesi yapısına sahipti. Daha sonra çözüm netleşti: Tüm geometri algoritmalarını tek bir yerde toplayın ve kodu kullanmak için yeniden düzenleyin.

Bu algoritmaların tümü mülk ağaçlarına bağlıdır. Bu nedenle, mülk ağaçları, RenderingNG'nin ardışık düzeninde kullanılan anahtar bir veri yapısıdır. Dolayısıyla, bu merkezi geometri kodu hedefine ulaşmak için mülk ağaçları kavramını boru hattının çok daha erken bir aşamasında (boyamadan önce) kullanıma sunmamız ve şu anda bu ağaçlara bağımlı olan tüm API'leri, çalışmaya başlamadan önce boyamanın çalıştırılmasını gerektirecek şekilde değiştirmemiz gerekiyordu.

Bu hikaye BlinkNG yeniden düzenleme modelinin bir başka yönüdür: Önemli hesaplamaları belirleyin, yinelemeleri önlemek için yeniden düzenleyin ve bunları besleyen veri yapılarını oluşturan iyi tanımlanmış ardışık düzen aşamaları oluşturun. Mülk ağaçlarını tam olarak gerekli tüm bilgiler elde edildiği anda hesaplarız ve sonraki oluşturma aşamaları çalışırken mülk ağaçlarının değişmemesini sağlarız.

Boya sonrası bileşik: Boru hattı boyama ve birleştirme

Katmanlaştırma, hangi DOM içeriğinin kendi birleştirilmiş katmanına (dolayısıyla bir GPU dokusunu temsil eder) gireceğini bulma işlemidir. Katmanlaştırma, RenderingNG öncesinde değil, boyadan önce çalıştırıldı (mevcut ardışık düzen için buraya bakın; sıra değişikliğine dikkat edin). İlk olarak DOM'un hangi bölümlerinin hangi birleştirilmiş katmana gittiğine karar veririz ve ancak daha sonra bu dokular için görüntü listeleri çizeriz. Kararlar doğal olarak hangi DOM öğelerinin animasyon veya kaydırma yaptığı ya da 3D dönüşümleri içerdiği ve bunların üzerine hangi öğelerin boyandığı gibi faktörlere dayanıyordu.

Bu durum büyük sorunlara yol açtı. Çünkü kodda döngüsel bağımlılıklar olması az ya da çok gerekliydi. Bu, oluşturma ardışık düzeni için büyük bir sorundu. Bunun nedenini bir örnek üzerinden inceleyelim. Boyayı invalidate gerektiğini varsayalım (bu da görüntüleme listesini yeniden çizmemiz ve ardından tekrar kafeslememiz gerektiği anlamına gelir). Geçersiz kılma ihtiyacı, DOM'daki bir değişiklikten veya değiştirilmiş bir stil ya da düzenden kaynaklanabilir. Ama tabii ki yalnızca gerçekten değişen bölümleri geçersiz kılmak istiyoruz. Bu da hangi birleştirilmiş katmanların etkilendiğinin belirlenmesi ve ardından bu katmanlara ait görüntüleme listelerinin bir kısmının veya tamamının geçersiz kılınması anlamına geliyordu.

Bu, geçersiz kılma işleminin DOM, stil, düzen ve geçmiş katman oluşturma kararlarına (geçmiş: oluşturulan önceki karenin anlamı) bağlı olduğu anlamına gelir. Ancak mevcut katmanlaştırma, tüm bu unsurlara da bağlıdır. Tüm katmanlaştırma verilerinin iki kopyası olmadığı için, geçmiş ve gelecekteki katman oluşturma kararlarını birbirinden ayırt etmek zordu. Sonuçta döngüsel akıl yürütmeye sahip çok sayıda kod bulduk. Bu da bazen mantıksız veya yanlış koda, hatta çok dikkatli olmamamız halinde kilitlenmelere veya güvenlik sorunlarına yol açıyordu.

Bu durumla başa çıkmak için, erken bir aşamada DisableCompositingQueryAsserts nesnesi kavramını kullanıma sunmuştuk. Çoğu zaman, kod geçmiş katman oluşturma kararlarını sorgulamaya çalışırsa onaylama hatasına neden olur ve hata ayıklama modundayken tarayıcıyı kilitler. Bu yaklaşım, yeni hataların ortaya çıkmasını engellememize yardımcı oldu. Ayrıca, kodun geçmişteki katmanlaştırma kararlarını sorgulamak için yasal olarak gerekli olduğu her durumda, bir DisableCompositingQueryAsserts nesnesi ayırarak bu işleme izin vermek için kod ekleriz.

Planımız, zaman içinde çağrı sitelerinin tüm DisableCompositingQueryAssert nesnelerini kaldırıp kodun güvenli ve doğru olduğunu beyan etmekti. Ancak, boyadan önce katmanlara ayırma işlemi yapıldığı sürece bazı çağrıları kaldırmanın imkansız olduğunu keşfettik. (Bunu çok kısa süre önce nihayet kaldırabildik!) Boyadan Sonra Kompozit projesinin keşfedilmesinin ilk nedeni budur. Öğrendiğimiz şu ki, bir işlem için iyi tanımlanmış bir ardışık düzen aşamanız olsa bile, ardışık düzende yanlış yerde bulunursa sonunda bir noktada takılıp kalacağınızdı.

Boyadan Sonra Kompozit projesinin ikinci nedeni Temel Birleştirme hatasıydı. Bu hatayı belirtmenin bir yolu, DOM öğelerinin web sayfası içerikleri için etkili veya eksiksiz bir katmanlaştırma şemasını iyi bir 1:1 temsili olmadığıdır. Birleştirme işlemi boyadan önce yapıldığından, doğal olarak görüntüleme listelerine veya özellik ağaçlarına değil, DOM öğelerine bağlıydı. Bu durum, mülk ağaçlarını kullanıma sunmamıza çok benziyor. Mülk ağaçlarında olduğu gibi, doğru ardışık düzen fazını belirlerseniz, doğru zamanda çalıştırırsanız ve doğru anahtar veri yapılarını sağlarsanız çözüm doğrudan ortadan kalkar. Mülk ağaçlarında olduğu gibi bu da boyama aşaması tamamlandıktan sonra sonucun sonraki tüm boru hattı aşamalarında değişmez olmasını garanti etmek için iyi bir fırsattı.

Avantajları

Gördüğünüz gibi, iyi tanımlanmış bir oluşturma ardışık düzeni, uzun vadede çok büyük avantajlar sağlar. Tahmin edebileceğinizden daha fazla seçenek var:

  • Çok iyileştirilmiş güvenilirlik: Bu sürüm oldukça basittir. İyi tanımlanmış ve anlaşılır arayüzlere sahip daha temiz kodların anlaşılması, yazılması ve test edilmesi daha kolaydır. Bu, hesabın daha güvenilir olmasını sağlar. Ayrıca, daha az kilitlenme ve sorun giderildikten sonra daha az hata oluşmasıyla kodu daha güvenli ve daha kararlı hale getirir.
  • Genişletilmiş test kapsamı: BlinkNG sırasında paketimize çok sayıda yeni test ekledik. Bu testler, dahili öğelerin odaklanmış doğrulamasını sağlayan birim testleri, düzelttiğimiz (pek çok sayıda!) eski hataları tekrar göstermemizi engelleyen regresyon testleri ve tüm tarayıcıların web standartlarına uygunluğu ölçmek için kullandığı, toplu olarak yönetilen ve herkese açık Web Platformu Test paketi'ne yapılan birçok eklemeyi içerir.
  • Genişletilmesi daha kolay: Bir sistem, net bileşenlere bölünmüşse mevcut bileşen üzerinde ilerleme kaydetmek için diğer bileşenleri herhangi bir ayrıntı düzeyinde anlamak gerekmez. Bu, derin bir uzman olması gerekmeden herkesin oluşturma koduna değer katmasını kolaylaştırır. Ayrıca, tüm sistemin davranışı hakkında akıl yürütmeyi de kolaylaştırır.
  • Performans: Spagetti kodunda yazılan algoritmaları optimize etmek yeterince zordur ancak böyle bir ardışık düzen olmadan evrensel iş parçacığı şeklinde kaydırma ve animasyonlar veya site izolasyonu için işlemler ve iş parçacıkları gibi daha da büyük hedeflere ulaşmak neredeyse imkansızdır. Paralellik, performansı önemli ölçüde iyileştirmemize yardımcı olabilse de son derece karmaşıktır.
  • Verimlilik ve kontrol altına alma: Ardışık düzeni yeni ve yeni yollarla kullanan BlinkNG tarafından mümkün hale getirilmiş çeşitli yeni özellikler mevcuttur. Örneğin, yalnızca oluşturma ardışık düzenini bütçe süresi dolana kadar çalıştırmak istersek ne olur? Veya şu anda kullanıcıyla alakalı olmadığı bilinen alt ağaçların oluşturulması atlansın mı? content-visible CSS özelliği bunu etkinleştirir. Bir bileşenin stilini düzenine bağlı hale getirmenin yolu nedir? İşte bu kapsayıcı sorguları.

Örnek olay: Kapsayıcı sorguları

Kapsayıcı sorguları, merakla beklenen, yakında kullanıma sunulacak bir web platformu özelliğidir (Yıllardır CSS geliştiricilerinin en çok talep ettiği özelliklerden biridir). Çok iyi bir şeyse, neden henüz yoktu? Bunun nedeni, kapsayıcı sorgularının uygulanması için stil ve düzen kodu arasındaki ilişkinin çok dikkatli bir şekilde anlaşılması ve kontrol edilmesidir. Biraz ayrıntılarına inelim.

Kapsayıcı sorgusu, bir öğe için geçerli olan stillerin bir üst öğenin yerleştirilmiş boyutuna bağlı olmasına olanak tanır. Düzenlenen boyut düzen sırasında hesaplandığı için bu, düzenden sonra stil yeniden hesaplama işlemi çalıştırmamız gerektiği anlamına gelir. Ancak stil yeniden hesaplama işlemi düzenden önce çalışır. BlinkNG'den önce container sorgularını uygulayamamamızın nedeni bu tavuk-yumurta paradoksudur.

Bu sorunu nasıl çözebiliriz? Geriye doğru ardışık düzen bağımlılığı, yani Kompozit After Paint gibi projelerin çözdüğü sorunla aynı sorun değil mi? Daha da kötüsü, yeni stiller üst öğenin boyutunu değiştirirse ne olur? Bu durum bazen sonsuz bir döngüye yol açmaz mı?

Prensipte, döngüsel bağımlılık, içerme CSS mülkünün kullanılmasıyla çözülebilir. Bu da, bir öğenin dışında oluşturmanın söz konusu öğenin alt ağacında oluşturmaya bağlı olmamasına olanak tanır. Bu, kapsayıcı sorguları içerme gerektirdiğinden bir kapsayıcı tarafından uygulanan yeni stillerin kapsayıcının boyutunu etkileyemeyeceği anlamına gelir.

Ancak aslında bu yeterli değildi. Yalnızca boyutun sınırlandırılmasından çok daha zayıf bir kapsama türünü uygulamak gerekiyordu. Bunun nedeni, kapsayıcı sorguları kapsayıcısının satır içi boyutlarına göre yalnızca bir yönde (genellikle blok) yeniden boyutlandırılabilmesini istemenin yaygın olmasıdır. Bu nedenle, satır içi boyut kapsama kavramı eklenmiştir. Ancak, bu bölümdeki çok uzun nottan görebileceğiniz gibi, satır içi boyut kapsamasının mümkün olup olmadığı uzun zamandır pek net değildi.

Kapsayıcılığı soyut spesifikasyon dilinde tanımlamak bir şey, bunu doğru bir şekilde uygulamak ise gayet başka bir şey. BlinkNG'nin hedeflerinden birinin, oluşturma işleminin ana mantığını oluşturan ağaç yürüyüşlerine kapsama ilkesini getirmek olduğunu hatırlayın: Bir alt ağacın üzerinden geçerken, alt ağacın dışından hiçbir bilgi istenmemelidir. Ancak bu durum tesadüfen oluşmamıştır. Oluşturma kodunun kapsama ilkesine uygun olması durumunda CSS kapsama alanının uygulanması çok daha sadedir ve daha kolaydır.

Gelecek: Ana ileti dizisi dışında birleştirme ... ve daha fazlası!

Burada gösterilen oluşturma ardışık düzeni, mevcut RenderingNG uygulamasının biraz ilerisindedir. Katmanlaştırma, ana iş parçacığının dışında olduğunu gösterirken hâlâ ana iş parçacığındadır. Ancak boyama işlemi tamamlanmadan önce yeterli olacaktır, çünkü artık kompozit boyama gönderilmiş ve katmanlaştırma da boyadan sonradır.

Bunun neden önemli olduğunu ve başka nerelerde olabileceğini anlamak için oluşturma motorunun mimarisini biraz daha yüksek bir bakış açısından düşünmemiz gerekir. Chromium'un performansını iyileştirmenin önündeki en dayanıklı engellerden biri, oluşturucunun ana iş parçacığının hem ana uygulama mantığını (yani, komut dosyasını çalıştırmayı) hem de oluşturma işlemini toplu olarak yönetmesidir. Bunun sonucunda, ana iş parçacığı genellikle işle doygun hale gelir ve ana iş parçacığı tıkanıklığı çoğu zaman tüm tarayıcıdaki performans sorunudur.

Neyse ki böyle olmak zorunda değilsiniz! Chromium mimarisinin bu yönü, en çok tek iş parçacıklı yürütmenin baskın program modeli olduğu KHTML günlerine kadar uzanıyor. Tüketici sınıfı cihazlarda çok çekirdekli işlemciler yaygın hale gelene kadar, tek iş parçacıklı olduğu varsayımı tümüyle Blink'e (eski adıyla WebKit) dönüştü. Uzun bir süredir oluşturma motoruna daha fazla iş parçacığı eklemek istiyorduk ancak eski sistemde bunu yapmak imkansızdı. NG oluşturma işleminin ana hedeflerinden biri, kendimizi bu delikten çıkarıp oluşturma işlemini kısmen veya tamamen başka bir iş parçacığına (ya da iş parçacığına) taşımayı mümkün kılmaktı.

Artık BlinkNG tamamlanmak üzere olduğuna göre bu alanı keşfetmeye çoktan başladık. Non-Block Commit, oluşturucunun iş parçacığı modelini değiştirmede ilk adımdır. Birleştirici kaydetme (veya yalnızca kaydetme), ana iş parçacığı ile birleştirici iş parçacığı arasındaki bir senkronizasyon adımıdır. Kaydetme sırasında, birleştirici iş parçacığında çalışan aşağı akış birleştirme kodu tarafından kullanılmak üzere ana iş parçacığında oluşturulan oluşturma verilerinin kopyalarını oluştururuz. Bu senkronizasyon gerçekleşirken, kopyalama kodu birleştirici iş parçacığında çalışırken ana iş parçacığının yürütülmesi durdurulur. Bu, birleştirici ileti dizisi kopyalarken ana iş parçacığının oluşturma verilerini değiştirmemesini sağlamak için yapılır.

Engellemeyen Kaydetme ana iş parçacığının durması ve kaydetme aşamasının sona ermesini bekleme ihtiyacını ortadan kaldırır. Kaydetme işlemi birleştirici iş parçacığında eşzamanlı olarak çalışırken ana iş parçacığı çalışmaya devam eder. Engellemeyen Kaydetme özelliğinin net etkisi, ana iş parçacığında yapılan çalışmaları oluşturmaya ayrılan zamandan tasarruf etmenizi sağlayacak ve bu da ana iş parçacığındaki tıkanıklığı azaltıp performansı artıracaktır. Bu yazının hazırlandığı tarih itibarıyla (Mart 2022) çalışan bir Engelleme Kaydı prototipi var ve bunun performans üzerindeki etkisiyle ilgili ayrıntılı bir analiz yapmaya hazırlanıyoruz.

Uçlarda beklemek, Ana iş parçacığı dışında birleştirme yöntemidir. Buradaki amaç, katmanlaştırmayı ana iş parçacığından bir çalışan iş parçacığına taşıyarak oluşturma motorunun resimle uyumlu olmasını sağlamaktır. Engellemeyen Kaydetmede olduğu gibi bu da oluşturma iş yükünü azaltarak ana iş parçacığındaki tıkanıklığı azaltır. Boya sonrası kompozitin mimari iyileştirmeleri olmadan bu tür bir proje mümkün olamazdı.

Üstelik üzerinde çalışılan başka projeler de var. Nihayet oluşturma çalışmasını yeniden dağıtmayı mümkün kılacak bir temele sahibiz ve nelerin mümkün olduğunu görmek için çok heyecanlıyız!