CSS @property özelliğinin performansını karşılaştırma

Yayınlanma tarihi: 2 Ekim 2024

Yeni bir CSS özelliğini kullanmaya başlarken, bu özelliğin web sitelerinizin performansı üzerindeki olumlu veya olumsuz etkisini anlamanız önemlidir. @property artık Temel Değer'de. Bu yayında, @property'in performans üzerindeki etkisi ve olumsuz etkileri önlemek için neler yapabileceğiniz ele alınmaktadır.

PerfTestRunner ile CSS performansını karşılaştırma

CSS'nin performansını karşılaştırmak için "CSS Seçici Karşılaştırması" test paketini oluşturduk. Chromium'un PerfTestRunner tarafından desteklenir ve CSS'nin performans üzerindeki etkisini karşılaştırmalı olarak gösterir. Chromium'un temel oluşturma motoru olan Blink, dahili performans testleri için bu PerfTestRunner değerini kullanır.

Koşucu, testler için kullanılan bir measureRunsPerSecond yöntemi içerir. Saniye başına çalıştırma sayısı ne kadar yüksek olursa o kadar iyidir. Bu kitaplıkla yapılan temel bir measureRunsPerSecond karşılaştırması aşağıdaki gibi görünür:

const testResults = PerfTestRunner.measureRunsPerSecond({
  "Test Description",
  iterationCount: 5,
  bootstrap: function() {
    // Code to execute before all iterations run
    // For example, you can inject a style sheet here
  },
  setup: function() {
    // Code to execute before a single iteration
  },
  run: function() {
    // The actual test that gets run and measured.
    // A typical test adjusts something on the page causing a style or layout invalidation
  },
  tearDown: function() {
    // Code to execute after a single iteration has finished
    // For example, undo DOM adjustments made within run()
  },
  done: function() {
    // Code to be run after all iterations have finished.
    // For example, remove the style sheets that were injected in the bootstrap phase
  },
});

measureRunsPerSecond ile ilgili her seçenek, kod bloğundaki yorumlar aracılığıyla açıklanır. run işlevi, ölçülen temel parçadır.

CSS seçici karşılaştırmaları için DOM ağacı gerekir

CSS seçicilerinin performansı DOM'un boyutuna da bağlı olduğundan bu karşılaştırmalar için makul boyutta bir DOM ağacı gerekir. Bu DOM ağacı manuel olarak oluşturulmak yerine oluşturulur.

Örneğin, aşağıdaki makeTree işlevi @property karşılaştırmalarının bir parçasıdır. Her biri içinde bazı alt öğeler bulunan 1.000 öğeden oluşan bir ağaç oluşturur.

const $container = document.querySelector('#container');

function makeTree(parentEl, numSiblings) {
  for (var i = 0; i <= numSiblings; i++) {
    $container.appendChild(
      createElement('div', {
        className: `tagDiv wrap${i}`,
        innerHTML: `<div class="tagDiv layer1" data-div="layer1">
          <div class="tagDiv layer2">
            <ul class="tagUl">
              <li class="tagLi"><b class="tagB"><a href="/" class="tagA link" data-select="link">Select</a></b></li>
            </ul>
          </div>
        </div>`,
      })
    );
  }
}

makeTree($container, 1000);

CSS seçici karşılaştırmaları DOM ağacını değiştirmediğinden bu ağaç oluşturma işlemi yalnızca bir kez, karşılaştırmalar çalıştırılmadan önce yürütülür.

Karşılaştırma yapma

Test paketinin bir parçası olan bir karşılaştırma testi çalıştırmak için önce bir web sunucusu başlatmanız gerekir:

npm run start

Başladıktan sonra, yayınlanmış URL'sinde karşılaştırmayı ziyaret edebilir ve window.startTest()'ü manuel olarak çalıştırabilirsiniz.

Bu karşılaştırmaları, herhangi bir uzantı veya başka faktörler olmadan tek başına çalıştırmak için, iletilen karşılaştırmayı yükleyip yürütmek üzere Puppeteer CLI'den tetiklenir.

Bu @property karşılaştırmaları özellikle yapmak için URL'sinde ilgili sayfayı ziyaret etmek yerine http://localhost:3000/benchmarks/at-rule/at-property.html CLI'de aşağıdaki komutları çağırın:

npm run benchmark at-rule/at-property

Bu işlem, sayfayı Puppeteer aracılığıyla yükler, window.startTest() işlevini otomatik olarak çağırır ve sonuçları bildirir.

CSS özelliklerinin performansını karşılaştırma

Bir CSS özelliğinin performansını karşılaştırmak için stil geçersizliği ve ardından tarayıcının yapması gereken stili yeniden hesaplama görevini ne kadar hızlı işleyebileceğini karşılaştırırsınız.

Stil geçersizliği, DOM&#39;daki bir değişikliğe yanıt olarak stillerinin yeniden hesaplanması gereken öğeleri işaretleme işlemidir. Olası en basit yaklaşım, her değişiklik için her şeyi geçersiz kılmaktır.

Bu durumda, devralan CSS özellikleri ile devralmayan CSS özellikleri arasında bir ayrım yapılması gerekir.

  • Bir CSS özelliği, hedeflenen öğede devralınan değişiklikleri içeriyorsa hedeflenen öğenin altındaki alt ağaçtaki tüm öğelerin stillerinin de değiştirilmesi gerekir.
  • Devralmayan bir CSS özelliği, hedeflenen bir öğede değiştiğinde yalnızca söz konusu öğenin stilleri geçersiz kılınır.

Miras alan mülkleri miras almayan mülklerle karşılaştırmak adil olmayacağından, çalıştırılacak iki referans grubu vardır:

  • Devralan mülkler içeren bir karşılaştırma grubu.
  • Devralmayan özellikler içeren bir dizi karşılaştırma ölçütü.

Karşılaştırma yapılacak mülkleri dikkatlice seçmeniz önemlidir. Bazı özellikler (accent-color gibi) yalnızca stilleri geçersiz kılarken düzen veya boya gibi diğer öğeleri de geçersiz kılan birçok özellik (writing-mode gibi) vardır. Yalnızca stilleri geçersiz kılan mülkleri istiyorsunuz.

Bunu belirlemek için Blink'in CSS özellikleri listesinde arama yapın. Her mülkte, geçersiz kılınanların listeleneceği bir invalidate alanı bulunur.

Ayrıca, bu tür bir mülkle karşılaştırma yapmak sonuçları çarpıtacağından, listeden independent olarak işaretlenmemiş bir mülk seçmek de önemlidir. Bağımsız mülkler, diğer mülkler veya işaretler üzerinde herhangi bir yan etki oluşturmaz. Yalnızca bağımsız mülkler değiştiğinde Blink, alt öğenin stilini kopyalayan ve bu kopyadaki yeni değerleri güncelleyen hızlı bir kod yolu kullanır. Bu yaklaşım, tam bir yeniden hesaplama yapmaktan daha hızlıdır.

Devralınan CSS özelliklerinin performansını karşılaştırma

İlk karşılaştırma grubu, devralınan CSS özelliklerine odaklanır. Test etmek ve birbiriyle karşılaştırmak için devralınan üç tür mülk vardır:

  • Aşağıdakileri devralan normal bir mülk: accent-color.
  • Kayıtlı olmayan özel mülk: --unregistered.
  • inherits: true: --registered adresine kayıtlı özel mülk.

Kayıtlı olmayan özel mülkler varsayılan olarak devraldığı için bu listeye eklenir.

Daha önce de belirtildiği gibi, devralan mülk, yalnızca stilleri geçersiz kılan ve independent olarak işaretlenmeyen bir mülk olacak şekilde dikkatlice seçilmiştir.

Kayıtlı özel mülkler söz konusu olduğunda, bu çalıştırmada yalnızca inherits tanımlayıcısının değeri true olarak ayarlanmış olan mülkler test edilir. inherits açıklayıcısı, mülkün alt öğelere devralınıp devralınmayacağını belirler. Kaydın kendisi karşılaştırmanın bir parçası olmadığından, bu mülkün CSS @property veya JavaScript CSS.registerProperty aracılığıyla kaydedilip kaydedilmediği önemli değildir.

Karşılaştırmalar

Daha önce de belirtildiği gibi, karşılaştırmaları içeren sayfa, bir DOM ağacı oluşturarak başlar. Böylece sayfa, değişikliklerin etkisini görecek kadar büyük bir düğüm grubuna sahip olur.

Her karşılaştırma, bir mülkün değerini değiştirdikten sonra stil geçersizliğini tetikler. Karşılaştırma temel olarak, geçersiz kılınan tüm stillerin yeniden değerlendirilmesi için sayfanın bir sonraki yeniden hesaplanmasının ne kadar süreceğini ölçer.

Tek bir karşılaştırma tamamlandıktan sonra, yerleştirilen tüm stiller sıfırlanır ve bir sonraki karşılaştırmanın başlaması sağlanır.

Örneğin, --registered stilini değiştirmenin performansını ölçen karşılaştırma şu şekilde görünür:

let i = 0;
PerfTestRunner.measureRunsPerSecond({
  description,
  iterationCount: 5,
  bootstrap: () => {
    setCSS(`@property --registered {
      syntax: "<number>";
      initial-value: 0;
      inherits: true;
    }`);
  },
  setup: function() {
    // NO-OP
  },
  run: function() {
    document.documentElement.style.setProperty('--registered', i);
    window.getComputedStyle(document.documentElement).getPropertyValue('--registered'); // Force style recalculation
    i = (i == 0) ? 1 : 0;
  },
  teardown: () => {
    document.documentElement.style.removeProperty('--registered');
  },
  done: (results) => {
    resetCSS();
    resolve(results);
  },
});

Diğer mülk türlerini test eden karşılaştırmalar aynı şekilde çalışır ancak kaydedilecek mülk olmadığından boş bir bootstrap içerir.

Sonuçlar

Bu karşılaştırmaları 16 GB RAM'e sahip 2021 MacBook Pro (Apple M1 Pro) üzerinde 20 iterasyonla çalıştırdığınızda aşağıdaki ortalamalar elde edilir:

  • Devralınan normal mülk (accent-color): Saniyede 163 çalıştırma (= çalıştırma başına 6,13 ms)
  • Kayıtlı olmayan özel mülk (--unregistered): Saniyede 256 çalıştırma (= çalıştırma başına 3,90 ms)
  • inherits: true ile kayıtlı özel mülk (--registered): Saniyede 252 çalıştırma (= çalıştırma başına 3,96 ms)

Birden çok çalıştırmayla karşılaştırmalar benzer sonuçlar verir.

Devralınan mülklere ait sonuçları gösteren çubuk grafik. Daha yüksek sayılar daha hızlı performans gösterir.
Şekil: Devralınan mülklere ait sonuçları gösteren çubuk grafik. Daha yüksek sayılar daha hızlı performans gösterir.

Sonuçlar, özel bir mülkü kaydetmenin, özel mülkü kaydetmemeye kıyasla çok küçük bir maliyete neden olduğunu göstermektedir. Devralan kayıtlı özel özellikler, kaydedilmemiş özel mülklerin hızında% 98 oranında çalışır. Mutlak sayılarla, özel mülkün kaydedilmesi 0,06 ms ek yük ekler.

Devralmayan CSS özelliklerinin performansını karşılaştırma

Karşılaştırma için diğer özellikler, devralınmayan mülklerdir. Burada karşılaştırma yapılabilen yalnızca iki tür mülk vardır:

  • Miras almayan normal mülk: z-index.
  • inherits: false ile kayıtlı bir özel mülk: --registered-no-inherit.

Kayıtlı olmayan özel mülkler her zaman devralındığından bu karşılaştırmaya dahil edilemez.

Karşılaştırmalar

Karşılaştırmalar önceki senaryolara çok benzer. --registered-no-inherit içeren test için karşılaştırmanın bootstrap aşamasına aşağıdaki mülk kaydı eklenir:

@property --registered-no-inherit {
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}

Sonuçlar

Bu karşılaştırmaları 16 GB RAM'e sahip 2021 MacBook Pro (Apple M1 Pro) üzerinde 20 iterasyonla çalıştırdığınızda aşağıdaki ortalamalar elde edilir:

  • Devralmayan normal mülk: Saniyede 290.269 çalıştırma (= çalıştırma başına 3,44 µs)
  • Devralmayan kayıtlı Özel Mülk: Saniyede 214.110 çalıştırma (= çalıştırma başına 4,67 μs)

Test, birden fazla çalıştırma boyunca tekrarlandı ve tipik sonuçlar bunlardı.

Devralmayan mülklerin sonuçlarını içeren çubuk grafik. Daha yüksek sayılar daha hızlı performans gösterir.
Şekil: Miras almayan mülklerin sonuçlarını gösteren çubuk grafik. Daha yüksek sayılar daha hızlı performans gösterir.

Burada dikkat çeken nokta, devralmayan mülklerin devralan mülklerden çok daha hızlı performans göstermesidir. Bu durum, normal mülkler için beklenebilir olsa da özel mülkler için de geçerlidir.

  • Normal mülkler için çalıştırma sayısı saniyede 163 çalıştırmadan saniyede 290 binin üzerine çıktı. Bu da performansta %1.780 artış anlamına geliyor.
  • Özel mülklerde, saniyede 252 çalıştırma olan koşu sayısı 214 binin üzerine çıktı. Bu da performansta% 848 artış anlamına geliyor.

Bundan temel çıkarım, özel mülk kaydederken inherits: false kullanmanın anlamlı bir etkiye sahip olmasıdır. Özel mülkünüzü inherits: false ile kaydettirebiliyorsanız kesinlikle bunu yapmalısınız.

Bonus karşılaştırma: Birden fazla özel mülk kaydı

Karşılaştırılacak bir başka ilginç nokta da, çok sayıda özel mülk kaydının etkisidir. Bunu yapmak için testi, önceden 25.000 diğer özel mülk kaydı yapan --registered-no-inherit ile yeniden çalıştırın. Bu özel özellikler :root'te kullanılır.

Bu kayıtlar karşılaştırmanın setup adımında yapılır:

setup: () => {
  const propertyRegistrations = [];
  const declarations = [];

  for (let i = 0; i < 25000; i++) {
    propertyRegistrations.push(`@property --custom-${i} { syntax: "<number>"; initial-value: 0; inherits: true; }`);
    declarations.push(`--custom-${i}: ${Math.random()}`);
  }

  setCSS(`${propertyRegistrations.join("\n")}
  :root {
    ${declarations.join("\n")}
  }`);
},

Bu karşılaştırma için saniye başına çalıştırma sayısı, "Devralmayan kayıtlı özel mülk" sonucuna çok benzer (saniye başına 214.110 çalıştırma ve saniye başına 213.158 çalıştırma) ancak dikkate alınması gereken ilginç kısım bu değildir. Sonuçta bir özel özelliğin değiştirilmesi, diğer mülklerdeki kayıtlardan etkilenmez.

Bu testin ilginç kısmı, kayıtların etkisini ölçmektir. DevTools'a döndüğünüzde, 25.000 özel mülk kaydının ilk stil yeniden hesaplama maliyetinin 30ms'ün biraz üzerinde olduğunu görebilirsiniz. Bu işlem tamamlandıktan sonra, bu kayıtların varlığının başka bir etkisi olmaz.

25.000 özel mülk kaydı yapmanın &quot;Stili Yeniden Hesapla&quot; maliyetinin vurgulandığı DevTools ekran görüntüsü. İpucu, işlemin 32,42 ms sürdüğünü gösterir.
Şekil: 25.000 özel mülk kaydının yapılmasıyla ilgili "Stili Yeniden Hesapla" maliyetinin vurgulandığı DevTools ekran görüntüsü. İpucu, işlemin 32.42ms
sürdüğünü gösterir.

Sonuç ve ana fikirler

Özetle, tüm bunlardan çıkarılacak üç ders vardır:

  • @property ile özel bir mülk kaydettirmenin biraz performans maliyeti vardır. Özel mülkleri kaydettiğinizde tüm potansiyellerini açığa çıkardığınız için bu maliyet genellikle göz ardı edilebilir. Bunu yapmadan bu potansiyele ulaşmak mümkün değildir.

  • Özel bir mülkü kaydederken inherits: false kullanmak anlamlı bir etki sağlar. Bu sayede mülkün devralınmasını engellemiş olursunuz. Bu nedenle, mülkün değeri değiştiğinde alt ağacın tamamı yerine yalnızca eşleşen öğenin stilleri etkilenir.

  • @property kayıtlarının çok az veya çok sayıda olması, stil yeniden hesaplama işlemini etkilemez. Kayıt işleminde yalnızca çok küçük bir ön maliyet vardır ancak bu işlem tamamlandıktan sonra sorun yaşamazsınız.