Yazma Kurallarının Zorlukları

Sorun bildir Kaynağı göster

Bu sayfada, etkili Bazel kuralları yazmayla ilgili belirli sorunlara ve zorluklara üst düzey bir genel bakış sunulmaktadır.

Özet Gereksinimler

  • Varsayım: Doğruluk, İşleme Hızı, Kullanım Kolaylığı ve Gecikmeyi Hedefleyin
  • Varsayım: Büyük Ölçekli Depolar
  • Varsayım: DERLEME benzeri Açıklama Dili
  • Geçmiş: Yükleme, Analiz ve Yürütme arasındaki Mutlak Ayrım Güncel Değil ancak API'yi etkilemeye devam ediyor
  • Yerleşik: Uzaktan Yürütme ve Önbelleğe Alma Zor
  • Intrinsic: Değişim Bilgilerini Doğru ve Hızlı Artımlı Derlemeler için Kullanma Olağandışı Kodlama Kalıpları gerektirir
  • Yerleşik: İkinci Dereceden Süre ve Bellek Tüketiminden Kaçınmak Zor

Varsayımlar

Derleme sistemi hakkında yapılan bazı varsayımları (doğruluk ihtiyacı, kullanım kolaylığı, işleme hızı ve büyük ölçekli depolar) aşağıda bulabilirsiniz. Aşağıdaki bölümlerde bu varsayımlar ele alınmış 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 artımlı derlemeler açısından ilk ve en doğru şekilde 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 çıktısı her zaman aynı olmalıdır. İlk yaklaşımda, Bazel'in belirli bir derleme adımına giren her bir girdiyi bilmesi gerekir. Böylece, girişlerden herhangi biri değişirse o adımı tekrar çalıştırabilir. Bazel derleme tarihi / saati gibi bazı bilgileri sızdırdığından ve dosya özniteliklerinde yapılan değişiklikler gibi belirli değişiklik türlerini yok saydığından, Bazel'in bunu nasıl belirleyebileceğine dair sınırlar vardır. Korumalı alan, bildirilmemiş giriş dosyalarında okuma yapılmasını önleyerek doğruluğun sağlanmasına 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 Dosya Kümesi veya C++ kurallarıyla ilgilidir. Bunlar her ikisi de zorlu bir sorundur. Bu sorunları düzeltmek için uzun vadeli çalışmalar yapıyoruz.

Derleme sisteminin ikinci hedefi, yüksek işleme hızına sahip olmaktır. Bir uzaktan yürütme hizmeti için mevcut makine ayırma dahilinde yapılabileceklerin sınırlarını kalıcı olarak zorlarız. Uzaktan yürütme hizmeti aşırı yüklenirse hiç kimse iş yapamaz.

Kullanım kolaylığı da sizi bekliyor. Uzaktan yürütme hizmetiyle 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 ifade eder. Bu süre, geçen veya başarısız bir testten alınan test günlüğü ya da BUILD dosyasında yazım hatası olduğunu belirten bir hata mesajı olabilir.

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

Büyük ölçekli depolar

Derleme sisteminin, büyük ölçekli depoların tek bir sabit diske sığamayacağı anlamına geldiği büyük ölçekli depolarda çalışması gerekir. Bu nedenle, neredeyse tüm geliştirici makinelerinde tam ödeme yapmak imkansızdır. Orta boyutlu bir derlemenin on binlerce BUILD dosyasını okuyup ayrıştırması ve yüz binlerce glob'u değerlendirmesi gerekir. Teorik olarak tüm BUILD dosyalarını tek bir makinede okumak mümkün olsa da bu işlemi henüz makul bir süre ve bellek içinde yapamadık. Bu nedenle, BUILD dosyalarının bağımsız olarak yüklenip ayrıştırılabilmesi çok önemlidir.

DERLEME benzeri açıklama dili

Bu bağlamda, kitaplık ve ikili kurallar bildirimi ve bunların bağımlılıkları açısından BUILD dosyalarına genel olarak benzeyen bir yapılandırma dili olduğunu varsayıyoruz. BUILD dosyaları bağımsız olarak okunup ayrıştırılabilir. Ayrıca, mümkün olduğunda kaynak dosyalara bakmaktan bile kaçınır (varlık hariç).

Tarihi

Zorluklara neden olan Bazel sürümleri arasında 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ındaki kesin ayrım artık güncel değil ancak API'yi etkilemeye devam ediyor

Teknik olarak, bir kuralın, işlem uzaktan yürütmeye gönderilmeden hemen önce işlemin giriş ve çıkış dosyalarını bilmesi yeterlidir. Bununla birlikte, orijinal Bazel kod tabanında paketlerin yüklenmesi, ardından bir yapılandırma (esasen komut satırı işaretleri) kullanılarak kuralların analiz edilmesi ve ancak bu işlemlerin gerçekleştirilmesi tamamen birbirinden ayrıdır. Bazel'in temel özelliği artık gerekli olmasa da bu ayrım bugünkü kurallar API'sinin bir parçasıdır (aşağıda ayrıntılı bilgi verilmiştir).

Bu, kurallar API'si için kural arayüzünün bildirim temelli bir açıklamasını (sahip olduğu özellikleri, özellik türleri) gerektirdiği anlamına gelir. API'nin, çıkış dosyalarının ve özelliklerin örtülü 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ı, dolaylı yoldan "libfoo.jar" adlı bir çıkış oluşturur. Bu çıkışa, derleme grafiğindeki diğer kurallardan referans verilebilir.

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

Doğal

Yazma kurallarını zorlaştıran bazı yerleşik özellikler vardır ve en yaygın olanların bazıları aşağıdaki bölümlerde açıklanmıştır.

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

Uzaktan yürütme ve önbelleğe alma, derlemeyi tek bir makinede çalıştırmaya kıyasla yaklaşık iki büyüklük sırasına göre büyük depolardaki derleme sürelerini iyileştirir. Ancak gerçekleştirilmesi gereken ölçek şaşırtıcı: Google'ın uzaktan yürütme hizmeti, saniye başına çok sayıda isteği işleyebilecek şekilde tasarlanmıştır. Protokol, gereksiz gidiş dönüşlerin yanı sıra hizmet tarafında yapılan gereksiz işleri de dikkatli bir şekilde önler.

Şu anda protokol için derleme sistemi, belirli bir işleme yönelik tüm girişleri önceden bilmesini gerektirmektedir. Ardından derleme sistemi benzersiz bir işlem parmak izini hesaplar ve planlayıcıdan bir önbellek isabeti ister. Bir önbellek isabeti bulunursa planlayıcı, çıkış dosyalarının özetleriyle yanıt verir; dosyaların kendisi daha sonra özetle ele alınır. Ancak bu durum, 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, olağan dışı kodlama kalıpları gerektirir

Yukarıda, hatanın doğru olması için Bazel'ın bu derleme adımının hâlâ güncel olup olmadığını algılamak için derleme adımına giden tüm giriş dosyalarını bilmesi gerektiğini tartıştık. Aynı durum paket yükleme ve kural analizi için de geçerlidir. Skyframe'i genel olarak bunu işleyecek şekilde tasarladık. Skyframe, hedef düğümünü ("bu seçeneklerle //foo'yu oluştur" gibi) ele alan ve bunu bileşenlerine ayıran ve daha sonra, bu sonucu elde etmek için değerlendirilen ve birleştirilen bir grafik kitaplığı ve değerlendirme çerçevesidir. Skyframe bu işlemin bir parçası olarak paketleri okur, kuralları analiz eder ve işlemleri yürütür.

Skyframe, hedef düğümden giriş dosyalarına (aynı zamanda Skyframe düğümleri) kadar her düğümde belirli bir düğümün kendi çıkışını hesaplamak için tam olarak hangi düğümleri kullandığını izlemektedir. Bu grafiğin bellekte açıkça gösterilmesi, derleme sisteminin bir giriş dosyasındaki belirli bir değişiklikten (giriş dosyasının oluşturulması veya silinmesi dahil) hangi düğümlerin etkilendiğini tam olarak tanımlamasını ve çıkış ağacını istenen durumuna geri yüklemek için minimum miktarda iş yapmasını sağlar.

Bunun bir parçası olarak, her düğüm bir bağımlılık keşfi işlemi gerçekleştirir. Her düğüm bağımlılıkları beyan edebilir ve ardından bu bağımlılıkların içeriklerini daha fazla bağımlılık bildirmek için kullanabilir. Prensipte bu, düğüm başına iş parçacığı modeline uygundur. Bununla birlikte, orta boyutlu derlemeler yüz binlerce Skyframe düğümü içerir ve bu, mevcut Java teknolojisiyle yapılması kolay değildir (ve tarihsel nedenlerden dolayı, şu anda Java kullanımına bağlı olduğumuzdan hafif iş parçacıkları ve devamlar yoktur).

Bunun yerine Bazel, sabit boyutlu bir iş parçacığı havuzu kullanır. Ancak bu, bir düğümün henüz kullanılamayan bir bağımlılığı bildirmesi durumunda, bağımlılık kullanılabilir olduğunda bu değerlendirmeyi iptal edip (muhtemelen başka bir iş parçacığında) yeniden başlatmamız gerekebilir. Bu da düğümlerin bunu çok fazla yapmaması gerektiği anlamına gelir. N bağımlılığı sırayla bildiren bir düğüm, potansiyel olarak N kez yeniden başlatılabilir ve bu da O(N^2) süresine mal olur. Bunun yerine bağımlılıkları önceden toplu olarak tanımlamayı amaçlarız. Bu işlem bazen kodun yeniden düzenlenmesini, hatta yeniden başlatma sayısını sınırlandırmak için bir düğümü birden fazla düğüme bölmeyi gerektirir.

Bu teknolojinin şu anda rules API'sinde bulunmadığını unutmayın. Bunun yerine, rules API'si hâlâ yükleme, analiz ve yürütme aşamaları gibi eski kavramlar kullanılarak tanımlanmıştır. Bununla birlikte, temel kısıtlamalardan biri, karşılık gelen bağımlılıkları izleyebilmek için diğer düğümlere olan tüm erişimin çerçeveden geçmesidir. Derleme sisteminin uygulandığı veya kuralların yazıldığı dilden (aynı olması gerekmez) bağımsız olarak, kural yazarları Skyframe'i atlayan standart kitaplık veya kalıplar kullanmamalıdır. Java için bu, java.io.File'ın yanı sıra herhangi bir yansıtma biçiminden ve bunu yapan kitaplıklardan kaçınmak anlamına gelir. Bu alt düzey arayüzlerin bağımlılık yerleştirmesini destekleyen kitaplıkların Skyframe için doğru şekilde ayarlanması gerekir.

Bu, kural yazarlarının her şeyden önce tam dil çalışma zamanına maruz kalmaması önemle tavsiye edilir. Bu tür API'lerin yanlışlıkla kullanılmasının 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'leri kullanan kurallardan kaynaklanan birkaç Bazel hatası meydana geliyordu.

İkinci dereceden zaman ve bellek tüketiminden kaçınmak zordur

Daha da kötüsü, Skyframe'in getirdiği gereksinimler, Java kullanımıyla ilgili geçmiş kısıtlamalar ve kural API'sinin eskiliği dışında, ikinci dereceden zaman veya bellek tüketiminin yanlışlıkla girilmesi, kitaplık ve ikili program kurallarına dayalı herhangi bir derleme sisteminde temel bir sorundur. İkinci dereceden bellek tüketimine (ve dolayısıyla ikinci dereceden zaman tüketimine) yol açan çok yaygın iki kalıp vardır.

  1. Kitaplık Kuralları Zincirleri - A'nın B'ye, C'ye bağlı olduğu kitaplık kuralları zincirinin durumunu düşünün. Ardından, bu kuralların geçişli olarak kapatılmasındaki bazı özellikleri (ör. Java çalışma zamanı sınıf yolu veya her kitaplık için C++ bağlayıcı komutu) hesaplamak isteriz. Kısacası, standart bir liste uygulamasını ele alalım. Ancak, bu zaten ikinci dereceden bellek tüketimine neden olmaktadır: İlk kitaplıkta sınıf yolunda bir giriş, ikinci iki, üçüncü üç giriş ve toplamda 1+2+3+...+N = O(N^2) girişi bulunur.

  2. Aynı Kitaplık Kurallarına Bağlı İkili Program Kuralları: Aynı kitaplık kurallarına bağlı olan bir ikili program kümesinin kullanıldığı bir durumu düşünün (örneğin, aynı kitaplık kodunu test eden çok sayıda test kuralınız varsa). N kuraldan yarısının ikili kurallar, diğer yarısı ise kitaplık kuralları olduğunu varsayalım. Şimdi, her bir ikili programı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 şekilde kapanması üzerinden hesaplanan bir özelliğin kopyasını oluşturduğunu düşünün. Örneğin, C++ bağlantı işleminin komut satırı dizesi temsilini genişletebilir. N/2 öğelerinin N/2 kopyası, O(N^2) bellektir.

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

Bazel bu iki senaryodan da yoğun şekilde etkilenir. Bu nedenle, her adımda kopyadan kaçınarak bellekteki bilgileri etkili bir şekilde sıkıştıran bir dizi özel toplama sınıfı sunduk. Bu veri yapılarının neredeyse tamamı anlamlara sahip olduğundan buna depset (dahili uygulamada NestedSet olarak da bilinir) adını verdik. Geçtiğimiz birkaç yılda Bazel'in bellek tüketimini azaltmaya yönelik değişikliklerin çoğu, daha önce kullanılanlar yerine derin delikleri kullanma şeklinde yapılan değişikliklerdi.

Ne yazık ki, veri kaybının kullanılması tüm sorunları otomatik olarak çözmez. Özellikle de her kuralda bir düşüşün tekrarlanması bile ikinci dereceden zaman tüketimine neden olur. NestedSets, dahili olarak normal koleksiyon sınıflarıyla birlikte çalışabilirliği kolaylaştırmak için bazı yardımcı yöntemlere de sahiptir. Ne yazık ki NestedSet'in bu yöntemlerden birine yanlışlıkla geçirilmesi, kopyalama davranışına neden olur ve ikinci dereceden bellek tüketimine yol açar.