Yazma Kurallarının Zorlukları

Sorun bildir Kaynağı göster Nightly · 8.0 · 7.4 · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bu sayfada, verimli Bazel kuralları yazmayla ilgili belirli sorunlara ve zorluklara ü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ında Sert Ayrım Eskidir ancak API'yi Etkilemeye Devam Ediyor
  • Doğal: Uzaktan yürütme ve önbelleğe alma zordur
  • Doğal: Doğru ve Hızlı Artımlı Derlemeler İçin Değişiklik Bilgilerini Kullanmak, Olağandışı Kodlama Kalıpları Gerektirir
  • Doğal: Dörtlü Zaman ve Bellek Tüketiminin Önlenmesi Zordur

Varsayımlar

Doğruluk, kullanım kolaylığı, veri akışı ve büyük ölçekli depolar gibi derleme sistemiyle ilgili bazı varsayımlar aşağıda verilmiştir. Aşağıdaki bölümlerde bu varsayımlar ele alınmakta ve kuralların etkili bir şekilde yazılmasını sağlayacak yönergeler sunulmaktadır.

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

Derleme sisteminin öncelikle artımlı derlemeler açısından doğru olması gerektiğini varsayıyoruz. Belirli bir kaynak ağacı için aynı derlemenin çıkışı, çıkış ağacının görünümünden bağımsız olarak her zaman aynı olmalıdır. İlk yaklaşımda bu, 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 türde değişiklikleri yoksadığı için ne kadar doğru bilgi sağlayabileceği konusunda sınırlamalar vardır. Korumalı alan, tanımlanmamış 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, doğrulukla ilgili birkaç bilinen sorun vardır. Bunların çoğu, Fileset veya C++ kurallarıyla ilgilidir ve her ikisi de zor sorunlardır. Bu sorunları düzeltmek için uzun vadeli çalışmalar yapıyoruz.

Derleme sisteminin ikinci hedefi yüksek verim elde etmektir. Uzak yürütme hizmeti için mevcut makine tahsisinden neler yapılabileceğinin sınırlarını sürekli olarak zorluyoruz. Uzaktan yürütme hizmeti aşırı yüklenirse kimse iş yapamaz.

Ardından kullanım kolaylığı gelir. Uzaktan yürütme hizmetinin aynı (veya benzer) ayak izine sahip birden fazla doğru yaklaşım arasından, kullanımı daha kolay olanı seçeriz.

Gecikme, bir derlemenin başlatılmasından istenen sonucun elde edilmesine kadar geçen süreyi belirtir. Bu süre, başarılı veya başarısız bir testin test günlüğü veya bir BUILD dosyasında yazım hatası olduğunu belirten bir hata mesajı olabilir.

Bu hedeflerin genellikle çakıştığını unutmayın. Gecikme, uzaktan yürütme hizmetinin veri hızının bir işlevi olduğu kadar, kullanım kolaylığıyla ilgili doğruluk da bir işlevidir.

Büyük ölçekli depolar

Derleme sisteminin büyük ölçekte çalışması gerekir. Büyük ölçek, tek bir sabit diske sığmadığı anlamına gelir. Bu nedenle, neredeyse tüm geliştirici makinelerinde tam bir ödeme yapılması mümkün değildir. Orta büyüklükte bir derleme için on binlerce BUILD dosyasının okunup ayrıştırılması ve yüz binlerce glob'un değerlendirilmesi gerekir. Tek bir makinede tüm BUILD dosyalarını okumak teorik olarak mümkün olsa da bunu makul bir süre ve bellek içinde henüz başaramadık. Bu nedenle, BUILD dosyalarının bağımsız olarak yüklenmesi ve ayrıştırılması önemlidir.

BUILD benzeri açıklama dili

Bu bağlamda, kitaplık ve ikili kuralların beyanında ve bunların birbirine bağımlılıklarına dair BUILD dosyalarına kabaca benzer bir yapılandırma dili varsayıyoruz. BUILD dosyaları bağımsız olarak okunabilir ve ayrıştırılabilir. Mümkün olduğunda kaynak dosyalara bakmaktan bile kaçınırız (mevcudiyet hariç).

Tarihi

Bazel sürümleri arasında zorluklara neden olan farklılıklar vardır. Bunların bazıları aşağıdaki bölümlerde özetlenmiştir.

Yükleme, analiz ve yürütme arasındaki katı ayrım eski olmasına rağmen API'yi etkilemeye devam ediyor

Teknik olarak, bir kuralın bir işlemin uzaktan yürütmeye gönderilmesinden 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 (temel olarak komut satırı işaretleri) kullanılarak kuralların analiz edilmesi ve ancak daha sonra işlemlerin yürütülmesi gibi işlemler birbirinden sıkı bir şekilde ayrılmıştı. Bazel'in çekirdeği artık bunu gerektirmese de bu ayrım, kurallar API'sinin hâlâ bir parçasıdır (ayrıntılar aşağıdadır).

Bu, kurallar API'sinin kural arayüzünün (ne tür özellikleri olduğu, özellik türleri) açıklayıcı bir açıklamasını gerektirdiği anlamına gelir. API'nin, çıkış dosyalarının gizli adlarını ve özelliklerin gizli değerlerini hesaplamak için yükleme aşamasında özel kodun çalıştırılmasına izin verdiği bazı istisnalar vardır. Örneğin, "foo" adlı bir java_library kuralı, derleme grafiğindeki diğer kurallardan referans verilebilen "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 bir derleme adımı ve çıkış dosyası adları için kısmen yönlendirilmiş iki parçalı bir grafik oluşturması gerekir.

Gerçek

Kural yazmayı zorlaştıran bazı doğal ö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 zordur

Uzak 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 artırır. Ancak bu protokolün performans göstermesi gereken ölçek oldukça büyüktür: Google'ın uzaktan yürütme hizmeti, saniye başına çok sayıda isteği işlemek için tasarlanmıştır ve protokol, gereksiz gidiş dönüşlerin yanı sıra hizmet tarafında gereksiz çalışmalardan dikkatlice kaçınır.

Bu sırada protokol, derleme sisteminin belirli bir işleme ait tüm girişleri önceden bilmesini gerektirir. Derleme sistemi daha sonra benzersiz bir işlem parmak izi hesaplar ve planlayıcıdan bir önbelleğe isabet ister. Önbelleğe isabet bulunursa planlayıcı, çıkış dosyalarının özetlerini göndererek yanıt verir. Dosyaların kendisi daha sonra özet aracılığıyla ele alınır. Ancak bu, tüm giriş dosyalarının önceden bildirilmesi gereken Bazel kurallarına 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ını hâlâ güncel olup olmadığını algılayabilmek amacıyla bu derleme adımına giren tüm giriş dosyalarını bilmesi gerektiğini belirtmiştik. Paket yükleme ve kural analizi için de aynı durum geçerlidir. Skyframe'ı genel olarak bu işlemleri gerçekleştirecek şekilde tasarladık. Skyframe, bir hedef düğümünü ("bu seçeneklerle //foo derle" gibi) alıp bileşenlerine ayıran ve daha sonra bu sonucu elde etmek için değerlendirilip birleştirilen bir grafik kitaplığı ve değerlendirme çerçevesidir. Bu süreç kapsamında Skyframe paketleri okur, kuralları analiz eder ve işlemleri yürütür.

Skyframe, her düğümde hedef düğümden giriş dosyalarına (aynı zamanda Skyframe düğümleridir) kadar belirli bir düğümün kendi çıkışını hesaplamak için tam olarak hangi düğümleri kullandığını 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ı oluşturma veya silme dahil) tam olarak hangi düğümlerin etkilendiğini belirlemesine olanak tanır. Böylece, çıkış ağacını istenen duruma geri yüklemek için minimum düzeyde çalışma yapılır.

Bunun bir parçası olarak her düğüm bir bağımlılık keşif işlemi gerçekleştirir. Her düğüm bağımlılıkları tanımlayabilir ve daha sonra bu bağımlılıkların içeriklerini kullanarak daha da fazla bağımlılığı tanımlayabilir. Bu, prensipte düğüm başına iş parçacığı modeline iyi bir şekilde eşlenir. Ancak orta büyüklükteki derlemeler yüz binlerce Skyframe düğümü içerir. Bu, mevcut Java teknolojisiyle kolayca mümkün değildir (ve geçmiş nedenlerden dolayı şu anda Java'yı kullanmak zorundayız. Bu nedenle hafif iş parçacıkları ve devam ettirmeler kullanılamaz).

Bunun yerine Bazel, sabit boyutlu bir iş parçacığı havuzu kullanır. Ancak bu, bir düğüm henüz mevcut olmayan bir bağımlılık belirtirse bu değerlendirmeyi iptal etmemiz ve bağımlılık kullanılabilir olduğunda (muhtemelen başka bir iş parçacığında) yeniden başlatmamız gerekebileceği anlamına gelir. Bu da, düğümlerin bunu aşırı derecede yapmaması gerektiği anlamına gelir. N bağımlılığı seri olarak bildiren bir düğüm, N kez yeniden başlatılabilir ve bu işlem O(N^2) zamana mal olur. Bunun yerine, bağımlılıkların önceden toplu olarak tanımlanmasını hedefliyoruz. Bu, bazen kodun yeniden düzenlenmesini veya 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ı gibi eski kavramlar kullanılarak tanımlanmaya devam etmektedir. Bununla birlikte, temel bir kısıtlama, diğer düğümlere yapılan tüm erişimlerin, ilgili bağımlılıkları izleyebilmesi için çerçeveden geçmesi gerektiğidir. Derleme sisteminin uygulandığı veya kuralların yazıldığı dilden bağımsız olarak (aynı olmaları gerekmez), kural yazarları Skyframe'ı atlayan standart kitaplıklar veya kalıplar kullanmamalıdır. Java için bu, java.io.File'in yanı sıra tüm yansıma biçimlerinden ve bu tür işlemleri yapan kitaplıklardan kaçınmak anlamına gelir. Bu düşük düzey arayüzlerin bağımlılık eklemeyi destekleyen kitaplıkların Skyframe için doğru şekilde ayarlanması gerekir.

Bu nedenle, kural yazarlarını ilk başta tam dil çalışma zamanına maruz bırakmamak önemle tavsiye edilir. Bu tür API'lerin yanlışlıkla kullanılmasının tehlikesi çok büyüktür. Geçmişte, Bazel ekibi veya diğer alan uzmanları tarafından yazılmış olsalar bile, güvenli olmayan API'lerin kullanıldığı kurallardan dolayı birçok Bazel hatası meydana gelmiştir.

Zaman ve bellek tüketiminin karesel olarak artmasını önlemek zordur

Daha da kötüsü, Skyframe'ın zorunlu kıldığı koşullar, Java'yı kullanmanın geçmişteki kısıtlamaları ve kurallar API'sinin güncel olmamasının yanı sıra, yanlışlıkla ikinci dereceden zaman veya bellek tüketimi sunmak, kitaplık ve ikili kurallara dayalı herhangi bir derleme sisteminde temel bir sorundur. Karesel bellek tüketimi (ve dolayısıyla karesel zaman tüketimi) sağlayan iki yaygın kalıp vardır.

  1. Kitaplık Kuralı Zincirleri: A kitaplık kuralının B'ye, B'nin C'ye ve benzeri bir kitaplık kuralı zinciri düşünün. Ardından, bu kuralların geçişli kapatması üzerinden bazı özellikleri (ör. Java çalışma zamanı sınıf yolu veya her kitaplığın C++ bağlayıcısı komutu) hesaplamak isteriz. Basit bir şekilde standart bir liste uygulaması kullanabiliriz. Ancak bu, zaten kare şeklinde bellek tüketimi oluşturur: İlk kitaplık, sınıf yolu üzerinde bir giriş, ikinci kitaplık iki giriş, üçüncü kitaplık üç giriş ve bu şekilde devam eder. Toplamda 1+2+3+...+N = O(N^2) giriş olur.

  2. Aynı Kitaplık Kurallarına Bağlı İkili Kurallar: Aynı kitaplık kurallarına bağlı bir ikili program grubunun olduğu durumu düşünün (ör. aynı kitaplık kodunu test eden birkaç test kuralınız varsa). N kuraldan yarısının ikili kural, diğer yarısının kitaplık kuralı olduğunu varsayalım. Şimdi her ikili dosyanın, Java çalışma zamanı sınıf yolu veya C++ bağlayıcı komut satırı gibi kitaplık kurallarının geçişli kapatması üzerinden hesaplanan bir mülkün kopyasını oluşturduğunu varsayalım. Örneğin, C++ bağlantı işleminin komut satırı dize temsilini genişletebilir. N/2 öğenin N/2 kopyası O(N^2) bellektir.

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

Bazel bu iki senaryodan da büyük ölçüde etkilendiğinden, her adımda kopyalama işlemini önleyerek bilgileri bellekte etkili bir şekilde sıkıştıran bir dizi özel koleksiyon sınıfı kullanıma sunduk. Bu veri yapılarının neredeyse tamamında küme semantiği olduğundan bu yapıya depset (dahili uygulamada NestedSet olarak da bilinir) adını verdik. Bazel'in bellek tüketimini azaltmak için geçtiğimiz birkaç yıl içinde yapılan değişikliklerin çoğu, daha önce kullanılanlar yerine depsetlerin kullanılmasıyla ilgili değişikliklerdi.

Maalesef depo kümelerinin kullanımı tüm sorunları otomatik olarak çözmez. Özellikle de her kuralda bir depo kümesi üzerinde iterasyon yapmak bile kare zaman tüketimini yeniden ortaya çıkarır. İçeride, normal koleksiyon sınıflarıyla birlikte çalışabilmeyi kolaylaştırmak için NestedSets'in bazı yardımcı yöntemleri de vardır. Maalesef, bir NestedSet'in bu yöntemlerden birine yanlışlıkla iletilmesi kopyalama davranışına yol açar ve kare şeklindeki bellek tüketimini yeniden başlatır.