Bu sayfada, verimli Bazel kuralları yazmayla ilgili belirli sorunlar ve zorluklar hakkında üst düzey bir genel bakış sunulmaktadır.
Özet ş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 işleme hızı sağlamaktır. 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 kuralları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 yapmak artık geçerli olmasa da API'yi etkilemeye devam ediyor.
Teknik olarak, bir kuralın, uzaktan yürütmeye gönderilmeden hemen önce bir 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ılmıştı. 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, kurallar API'sinin kural arayüzünün (hangi özelliklere sahip olduğu, özellik türleri) bildirimsel bir açıklamasını gerektirdiği anlamına gelir. 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 olanlardan bazıları aşağıdaki bölümlerde açıklanmıştır.
Uzaktan yürütme ve önbelleğe alma zor olabilir
Uzaktan yürütme ve önbelleğe alma, derlemeyi tek bir makinede çalıştırmaya kıyasla büyük depolardaki 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'i genel olarak bu durumu yönetebilecek şekilde tasarladık. Skyframe, bir hedef düğümü (ör. "//foo'yu bu seçeneklerle oluştur") alan ve bunu bileşenlerine ayıran bir grafik kitaplığı ve değerlendirme çerçevesidir. Bu bileşenler daha sonra değerlendirilir ve bu sonucu elde etmek için birleştirilir. Bu süreç kapsamında 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 durumuna geri yüklemek için minimum düzeyde çalışma yapılır.
Bu kapsamda, her düğüm bir 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 da 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, diğer düğümlere yapılan tüm erişimlerin, ilgili bağımlılıkları izleyebilmesi için çerçeveden geçmesi 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 bu işlemleri 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 kalmasını önlemenin önemini 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.
İşleri daha da kötüleştiren şey, Skyframe'in getirdiği gereksinimlerin, Java 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 tanıtılmasının, kitaplık ve ikili kurallara dayalı herhangi bir derleme sisteminde temel bir sorun olmasıdır. Karesel bellek tüketimine (ve dolayısıyla karesel zaman tüketimine) yol açan iki çok yaygın kalıp vardır.
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 istiyoruz. Ö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.
Aynı kitaplık kurallarına bağlı ikili kurallar: Aynı kitaplık kurallarına bağlı bir dizi ikilinin 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ı dize 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.
Ne yazık ki, depsets kullanımı tüm sorunları otomatik olarak çözmez. Özellikle her kuralda bir depset üzerinde yineleme yapmak bile ikinci dereceden 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.