Yazma Kurallarının Zorlukları

Bu sayfada, verimli Bazel kuralları yazmayla ilgili belirli sorunlar ve zorluklar hakkında üst düzey bir genel bakış sunulmaktadır.

Özetle ilgili şartlar

  • Varsayım: Doğruluk, İşleme Hızı, Kullanım Kolaylığı ve Gecikmeyi Hedefleyin
  • Varsayım: Büyük Ölçekli Depolar
  • Varsayım: BUILD benzeri açıklama dili
  • Geçmiş: Yükleme, analiz ve yürütme arasındaki katı ayrım güncelliğini yitirmiştir ancak API'yi etkilemeye devam etmektedir.
  • Doğasında Var: Uzaktan Yürütme ve Önbelleğe Alma Zordur
  • Intrinsic: Doğru ve hızlı artımlı derlemeler için Değişiklik Bilgilerini Kullanma Alışılmadık Kodlama Kalıpları Gerektirir
  • Doğal: Karesel zaman ve bellek tüketiminden kaçınmak zordur.

Varsayımlar

Derleme sistemiyle ilgili olarak doğruluk, kullanım kolaylığı, işleme hızı ve büyük ölçekli depolar gibi bazı varsayımlar yapılmıştır. Aşağıdaki bölümlerde bu varsayımlar ele alınmakta ve kuralların etkili bir şekilde yazılmasını sağlamak için yönergeler sunulmaktadır.

Doğruluk, işleme hızı, kullanım kolaylığı ve gecikmeyi hedefleyin.

Derleme sisteminin, artımlı derlemeler açısından öncelikle doğru olması gerektiğini varsayıyoruz. Belirli bir kaynak ağacı için, çıkış ağacının görünümü ne olursa olsun aynı derlemenin çıkışı her zaman aynı olmalıdır. Bu, ilk yaklaşıma göre Bazel'in belirli bir derleme adımına giren her girişi bilmesi gerektiği anlamına gelir. Böylece, girişlerden herhangi biri değişirse bu adımı yeniden çalıştırabilir. Bazel, derlemenin tarihi / saati gibi bazı bilgileri sızdırdığı ve dosya özelliklerindeki değişiklikler gibi belirli değişiklik türlerini göz ardı ettiği için ne kadar doğru olabileceği konusunda sınırlar vardır. Korumalı alan, bildirilmemiş giriş dosyalarının okunmasını engelleyerek doğruluğu sağlamaya yardımcı olur. Sistemin doğal sınırlarının yanı sıra, bilinen birkaç doğruluk sorunu vardır. Bunların çoğu, hem zor problemler olan Fileset veya C++ kurallarıyla ilgilidir. Bu sorunları düzeltmek için uzun süredir çalışıyoruz.

Derleme sisteminin ikinci hedefi, yüksek gönderim hacmi elde etmektir. Uzaktan yürütme hizmeti için mevcut makine tahsisi kapsamında yapılabileceklerin sınırlarını sürekli olarak zorluyoruz. Uzaktan yürütme hizmeti aşırı yüklenirse kimse iş yapamaz.

Kullanım kolaylığı da önemlidir. Aynı (veya benzer) uzaktan yürütme hizmeti ayak izine sahip birden fazla doğru yaklaşım arasından, kullanımı daha kolay olanı seçeriz.

Gecikme, derleme başlatıldıktan sonra istenen sonucun (geçen veya başarısız olan bir testin test günlüğü ya da BUILD dosyasında yazım hatası olduğunu belirten bir hata mesajı) alınması için geçen süreyi ifade eder.

Bu hedeflerin genellikle çakıştığını unutmayın. Gecikme, uzaktan yürütme hizmetinin işleme hızının bir fonksiyonu olduğu kadar, kullanım kolaylığı için de doğrulukla ilgilidir.

Büyük ölçekli depolar

Derleme sisteminin, büyük ölçekli depoların ölçeğinde çalışması gerekir. Büyük ölçek, tek bir sabit sürücüye sığmayacağı anlamına gelir. Bu nedenle, neredeyse tüm geliştirici makinelerinde tam bir ödeme yapmak imkansızdır. Orta ölçekli bir derleme için on binlerce BUILD dosyasının okunup ayrıştırılması ve yüz binlerce blob'un değerlendirilmesi gerekir. Tek bir makinede tüm BUILD dosyalarını okumak teorik olarak mümkün olsa da bunu henüz makul bir süre ve bellek miktarı içinde yapamadık. Bu nedenle, BUILD dosyalarının bağımsız olarak yüklenebilmesi ve ayrıştırılabilmesi çok önemlidir.

BUILD benzeri açıklama dili

Bu bağlamda, kitaplık ve ikili program kurallarının bildiriminde BUILD dosyalarına ve bunların karşılıklı bağımlılıklarına kabaca benzeyen bir yapılandırma dili varsayıyoruz. BUILD dosyaları bağımsız olarak okunup ayrıştırılabilir ve mümkün olduğunda kaynak dosyaları (varlık hariç) incelemekten kaçınırız.

Tarihi

Bazel sürümleri arasında zorluklara neden olan farklılıklar vardır. Bunlardan bazıları aşağıdaki bölümlerde açıklanmıştır.

Yükleme, analiz ve yürütme arasında katı bir ayrım yapılması eski bir yöntem olsa da API'yi etkilemeye devam ediyor.

Teknik olarak, bir kuralın, bir işlem uzak yürütmeye gönderilmeden hemen önce işlemin giriş ve çıkış dosyalarını bilmesi yeterlidir. Ancak orijinal Bazel kod tabanında paketlerin yüklenmesi, ardından bir yapılandırma (temelde komut satırı işaretleri) kullanılarak kuralların analiz edilmesi ve ancak ondan sonra herhangi bir işlemin çalıştırılması kesin bir şekilde ayrılıyordu. Bazel'in temelinde bu ayrım artık gerekli olmasa da (daha fazla ayrıntı aşağıda), bu ayrım bugün hâlâ kurallar API'sinin bir parçasıdır.

Bu nedenle, kurallar API'si, kural arayüzünün (hangi özelliklere sahip olduğu, özellik türleri) bildirimsel bir açıklamasını gerektirir. API'nin, çıkış dosyalarının örtülü adlarını ve özelliklerin örtülü değerlerini hesaplamak için yükleme aşamasında özel kodun çalışmasına izin verdiği bazı istisnalar vardır. Örneğin, "foo" adlı bir java_library kuralı, derleme grafiğindeki diğer kurallardan referans verilebilecek "libfoo.jar" adlı bir çıkışı dolaylı olarak oluşturur.

Ayrıca, bir kuralın analizi herhangi bir kaynak dosyayı okuyamaz veya bir işlemin çıktısını inceleyemez. Bunun yerine, yalnızca kuralın kendisinden ve bağımlılıklarından belirlenen, derleme adımlarının ve çıktı dosya adlarının kısmi yönlendirilmiş iki parçalı bir grafiğini oluşturması gerekir.

Gerçek

Kural yazmayı zorlaştıran bazı temel özellikler vardır. En yaygın olanları aşağıdaki bölümlerde açıklanmıştır.

Uzaktan yürütme ve önbelleğe alma zordur

Uzak yürütme ve önbelleğe alma, derlemeyi tek bir makinede çalıştırmaya kıyasla büyük depolarda derleme sürelerini yaklaşık iki kat daha kısaltır. Ancak bu hizmetin çalışması gereken ölçek şaşırtıcıdır: Google'ın uzaktan yürütme hizmeti, saniyede çok sayıda isteği işleyecek şekilde tasarlanmıştır ve protokol, gereksiz gidiş dönüşleri ve hizmet tarafında gereksiz işleri dikkatli bir şekilde önler.

Şu anda protokol, derleme sisteminin belirli bir işlemin tüm girişlerini önceden bilmesini gerektirir. Ardından derleme sistemi benzersiz bir işlem parmak izi hesaplar ve planlayıcıdan önbellek isabeti ister. Önbellek isabeti bulunursa zamanlayıcı, çıkış dosyalarının özetleriyle yanıt verir. Dosyaların kendileri daha sonra özetle ele alınır. Ancak bu durum, tüm giriş dosyalarını önceden bildirmesi gereken Bazel kuralları üzerinde kısıtlamalar getirir.

Doğru ve hızlı artımlı derlemeler için değişiklik bilgilerini kullanmak, alışılmadık kodlama kalıpları gerektirir.

Yukarıda, Bazel'in doğru olması için bir derleme adımına giren tüm giriş dosyalarını bilmesi gerektiğini, böylece bu derleme adımının hâlâ güncel olup olmadığını tespit edebileceğini belirtmiştik. Aynı durum paket yükleme ve kural analizi için de geçerlidir. Skyframe, genel olarak bu durumu yönetmek üzere tasarlanmıştır. Skyframe, bir hedef düğümü (ör. "//foo'yu bu seçeneklerle oluştur") alan ve bunu bileşenlerine ayıran, ardından bu bileşenleri değerlendirip birleştirerek sonucu elde eden bir grafik kitaplığı ve değerlendirme çerçevesidir. Bu süreçte Skyframe, paketleri okur, kuralları analiz eder ve işlemleri yürütür.

Skyframe, her düğümde belirli bir düğümün kendi çıkışını hesaplamak için kullandığı düğümleri, hedef düğümden giriş dosyalarına (bunlar da Skyframe düğümleridir) kadar tam olarak izler. Bu grafiğin bellekte açıkça gösterilmesi, derleme sisteminin bir giriş dosyasında yapılan belirli bir değişiklikten (giriş dosyasının oluşturulması veya silinmesi dahil) tam olarak hangi düğümlerin etkilendiğini belirlemesine olanak tanır. Böylece, çıkış ağacını amaçlanan duruma geri yüklemek için minimum düzeyde çalışma yapılır.

Bu kapsamda her düğüm, bağımlılık keşfi işlemi gerçekleştirir. Her düğüm bağımlılıkları bildirebilir ve ardından daha da fazla bağımlılık bildirmek için bu bağımlılıkların içeriklerini kullanabilir. Bu, prensip olarak düğüm başına bir iş parçacığı modeline iyi bir şekilde karşılık gelir. Ancak orta ölçekli derlemeler yüz binlerce Skyframe düğümü içerir. Bu da mevcut Java teknolojisiyle kolayca mümkün değildir (ve geçmişteki nedenlerden dolayı şu anda Java kullanmak zorundayız. Bu nedenle, hafif iş parçacıkları ve devamlılıklar kullanılamaz).

Bunun yerine Bazel, sabit boyutlu bir iş parçacığı havuzu kullanır. Ancak bu, bir düğüm henüz kullanılamayan bir bağımlılık bildirdiğinde bu değerlendirmeyi iptal edip bağımlılık kullanılabilir olduğunda yeniden başlatmamız (muhtemelen başka bir iş parçacığında) gerekebileceği anlamına gelir. Bu da düğümlerin bunu aşırı yapmaması gerektiği anlamına gelir. N bağımlılığı seri olarak bildiren bir düğüm, N kez yeniden başlatılabilir ve O(N^2) zaman maliyetine neden olabilir. Bunun yerine, bağımlılıkların önceden toplu olarak bildirilmesini amaçlıyoruz. Bu bazen kodun yeniden düzenlenmesini, hatta yeniden başlatma sayısını sınırlamak için bir düğümün birden fazla düğüme bölünmesini gerektirir.

Bu teknolojinin şu anda kurallar API'sinde kullanılamadığını unutmayın. Bunun yerine, kurallar API'si yükleme, analiz ve yürütme aşamalarının eski kavramları kullanılarak tanımlanmaya devam etmektedir. Ancak temel bir kısıtlama olarak, ilgili bağımlılıkları izleyebilmesi için diğer düğümlere yapılan tüm erişimlerin çerçeve üzerinden yapılması gerekir. Derleme sisteminin uygulandığı veya kuralların yazıldığı dilden bağımsız olarak (aynı olmak zorunda değildir) kural yazarları, Skyframe'i atlayan standart kitaplıkları veya kalıpları kullanmamalıdır. Java için bu, java.io.File'ın yanı sıra yansıtma biçimlerinin ve her ikisini de yapan kitaplıkların kullanılmaması anlamına gelir. Bu düşük düzeyli arayüzlerin bağımlılık eklenmesini destekleyen kitaplıkların Skyframe için doğru şekilde ayarlanması gerekir.

Bu durum, kural yazarlarının en başından itibaren tam dil çalışma zamanına maruz kalmamaları gerektiğini açıkça gösteriyor. Bu tür API'lerin yanlışlıkla kullanılma tehlikesi çok büyüktür. Geçmişte, kurallar Bazel ekibi veya diğer alan uzmanları tarafından yazılmış olsa bile, güvenli olmayan API'ler kullanan kurallar nedeniyle çeşitli Bazel hataları oluşmuştur.

Karesel zaman ve bellek tüketiminden kaçınmak zordur.

Daha da kötüsü, Skyframe'in getirdiği şartların, Java'yı kullanmanın tarihsel kısıtlamalarının ve kurallar API'sinin güncelliğini yitirmiş olmasının yanı sıra, karesel zaman veya bellek tüketiminin yanlışlıkla ortaya çıkması, kitaplık ve ikili kurallara dayalı tüm derleme sistemlerinde temel bir sorundur. Karesel bellek tüketimine (ve dolayısıyla karesel zaman tüketimine) yol açan iki çok yaygın kalıp vardır.

  1. Kitaplık kuralları zincirleri: A'nın B'ye, B'nin C'ye ve bu şekilde devam eden bir kitaplık kuralları zinciri olduğunu düşünün. Ardından, bu kuralların geçişli kapanımı üzerinde bazı özellikler hesaplamak isteriz. Örneğin, Java çalışma zamanı sınıf yolu veya her kitaplık için C++ bağlayıcı komutu. Basit bir yaklaşımla standart bir liste uygulaması kullanabiliriz. Ancak bu, zaten ikinci dereceden bellek tüketimine yol açar: İlk kitaplıkta sınıf yolunda bir giriş, ikincisinde iki, üçüncüsünde üç giriş bulunur ve bu şekilde devam eder. Toplam giriş sayısı 1+2+3+...+N = O(N^2) olur.

  2. Aynı Kitaplık Kurallarına Bağlı İkili Programlar - Aynı kitaplık kurallarına bağlı bir dizi ikili programın olduğu durumu düşünün. Örneğin, aynı kitaplık kodunu test eden bir dizi test kuralınız var. N kuraldan yarısının ikili kural, diğer yarısının ise kitaplık kuralı olduğunu varsayalım. Şimdi de her ikilinin, kitaplık kurallarının geçişli kapanması üzerinde hesaplanan bazı özelliklerin (ör. Java çalışma zamanı sınıf yolu veya C++ bağlayıcı komut satırı) bir kopyasını oluşturduğunu düşünün. Örneğin, C++ bağlantı işleminin komut satırı dizesi gösterimini genişletebilir. N/2 öğesinin N/2 kopyası için O(N^2) bellek gerekir.

İkinci dereceden karmaşıklığı önlemek için özel koleksiyon sınıfları

Bazel, bu senaryoların her ikisinden de büyük ölçüde etkilenir. Bu nedenle, her adımda kopyalamayı önleyerek bellekteki bilgileri etkili bir şekilde sıkıştıran bir dizi özel koleksiyon sınıfı oluşturduk. Bu veri yapılarının neredeyse tamamı belirli bir anlama sahiptir. Bu nedenle, bu veri yapısına depset adını verdik (dahili uygulamada NestedSet olarak da bilinir). Son birkaç yılda Bazel'in bellek tüketimini azaltmak için yapılan değişikliklerin çoğu, daha önce kullanılanların yerine bağımlılık kümelerinin kullanılmasını sağlayan değişikliklerdi.

Maalesef, bağımlılık kümelerinin kullanılması tüm sorunları otomatik olarak çözmez. Özellikle her kuralda bir bağımlılık kümesi üzerinde yineleme yapmak bile karesel zaman tüketimini yeniden ortaya çıkarır. NestedSets, normal koleksiyon sınıflarıyla birlikte çalışabilirliği kolaylaştırmak için bazı yardımcı yöntemlere de sahiptir. Ancak NestedSet'in bu yöntemlerden birine yanlışlıkla iletilmesi, kopyalama davranışına yol açar ve ikinci dereceden bellek tüketimini yeniden ortaya çıkarır.