Yazma Kurallarının Zorlukları

Sorun bildirin Kaynağı göster

Bu sayfada belirli sorunlar ve etkili Bazel kuralları yazmanın zorlukları hakkında üst düzey bir genel bakış sunulmaktadır.

Özet Şartlar

  • Varsayım: Düzeltme, İşleme Hızı, Kullanım Kolaylığı ve Gecikme Hedefi
  • 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 Kesin Ayrım Güncel değil 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şim Bilgilerinin Kullanılması Sıra Dışı Kodlama Kalıpları Gerektiriyor
  • İçsel: İkinci dereceden zamandan ve bellek tüketiminden kaçınmak Zor

Varsayımlar

Derleme sistemi hakkında doğru olma, kullanım kolaylığı, işleme hızı ve büyük ölçekli depolar gibi bazı varsayımları burada bulabilirsiniz. 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 konusunda en başta doğru ve en doğru şekilde hizmet vermesi gerektiğini varsayarız. Belirli bir kaynak ağaçta, çıkış ağacının nasıl göründüğüne bakılmaksızın aynı derlemenin çıkışı her zaman aynı olmalıdır. İlk tahminde bu, Bazel'in belirli bir derleme adımına giren her bir girişi bilmesi gerektiği anlamına gelir. Böylece, girişlerden herhangi biri değişirse bu adımı tekrar çalıştırabilir. Bazel'in doğru şekilde üretilmesiyle ilgili sınırlamalar vardır. Çünkü derleme tarihi / saati gibi bazı bilgileri sızdırır ve dosya özelliklerinde yapılan değişiklikler gibi belirli değişiklik türlerini yok sayar. Korumalı alana alma, 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 daha vardır. Bunların çoğu Dosya Grubu ya da C++ kurallarıyla ilgilidir ve her ikisi de zorlu problemlerdir. Bu sorunları düzeltmek için uzun vadeli çalışmalar yapmaktayız.

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

Kullanım kolaylığı geliyor. Uzaktan yürütme hizmetiyle aynı (veya benzer) ayak izi bulunan 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üğü ya da BUILD dosyasında yazım hatası olduğunu bildiren bir hata mesajı olabilir.

Bu hedeflerin genellikle çakıştığını unutmayın. Gecikme, kullanım kolaylığı açısından doğruluk 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çeğe bağlı olarak tek bir sabit diske sığmayacağı anlamına geldiği ve büyük çaplı 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. Tüm BUILD dosyalarını tek bir makinede okumak teorik olarak mümkün olsa da bunu henüz makul bir zaman ve bellek dahilinde yapamadık. Bu nedenle, BUILD dosyalarının bağımsız olarak yüklenip ayrıştırılabilmesi son derece önemlidir.

DERLEME benzeri bir açıklama dili

Bu bağlamda, kitaplık ve ikili program kuralları ile bunların karşılıklı bağımlılıklarını belirten BUILD dosyalarına kabaca benzeyen bir yapılandırma dili olduğunu varsayarız. BUILD dosyaları bağımsız olarak okunup ayrıştırılabilir. Ayrıca, mümkün olduğunda (varlık hariç) kaynak dosyalara bakmaktan bile kaçınırız.

Tarihi

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

Yükleme, analiz ve yürütme arasındaki kesin ayrım eski olsa da API'yi etkilemeye devam eder

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, daha sonra bir yapılandırma kullanılarak kuralların analiz edilmesi (esasen komut satırı işaretleri) ve ancak tüm işlemlerin çalıştırılması için katı şekilde ayrılıyordu. Bazel'in merkezinde artık bu ayrım gerekli olmasa da bu ayrım, kurallar API'sinin bir parçasıdır (daha fazla bilgiyi aşağıda bulabilirsiniz).

Bu, kurallar API'sinin kural arayüzünü (hangi özelliklere sahip olduğunu, özellik türlerini) bildirim temelli 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 kod çalıştırılmasına izin verdiği bazı istisnalar vardır. Örneğin, "foo" adlı bir java_library kuralı, dolaylı olarak "libfoo.jar" adında bir çıkış oluşturur. Bu çıkış, derleme grafiğindeki diğer kurallardan referans alınabilir.

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ış dosya adlarının kısmi yönlendirilmiş bir iki taraflı grafiğini oluşturması gerekir.

Yerleşik

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

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

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 iyileştirir. Ancak, performans göstermesi gereken ölçek baş döndürücüdür: Google'ın uzaktan yürütme hizmeti, saniyede çok sayıda isteği işleyecek şekilde tasarlanmıştır. Protokol, hizmet tarafında gereksiz çalışmaların yanı sıra gereksiz gidişatları da dikkatli bir şekilde önler.

Şu anda protokol, derleme sisteminin belirli bir işlem için yapılan tüm girişleri önceden bilmesini gerektirir. Daha sonra derleme sistemi benzersiz bir işlem parmak izini hesaplar ve planlayıcıdan önbellek isabeti ister. Önbellek isabeti bulunursa planlayıcı, çıkış dosyalarının özetleriyle yanıt verir. Dosyalar daha sonra özetle 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, olağan dışı kodlama kalıpları gerektirir.

Yukarıda, doğru olması için Bazel'in, derleme adımının hâlâ güncel olup olmadığını belirlemek üzere derleme adımına giden tüm giriş dosyalarını bilmesi gerektiğini savunduk. Aynı durum paket yükleme ve kural analizi için de geçerlidir. Skyframe'i genel olarak bunu işleyecek şekilde tasarladık. Skyframe, bir hedef düğümünü ("bu seçeneklerle //foo oluştur" gibi) alıp onu bileşenlerine ayıran bir grafik kitaplığı ve değerlendirme çerçevesidir. Bu bileşenler daha sonra değerlendirilip bu sonucu elde etmek için birleştirilir. Skyframe bu sürecin bir parçası olarak paketleri okur, kuralları analiz eder ve eylemleri yürütür.

Skyframe, her düğümde kendi çıkışını hesaplamak için belirli bir düğümün tam olarak hangi düğümleri kullandığını, hedef düğümünden giriş dosyalarına (aynı zamanda Skyframe düğümleridir) kadar izler. Bu grafiğin bellekte açık bir şekilde temsil edilmesi, derleme sisteminin giriş dosyasında yapılan belirli bir değişiklikten (giriş dosyasının oluşturulması veya silinmesi dahil) tam olarak hangi düğümlerin etkileneceğini belirlemesini sağlar. Böylece, çıkış ağacını amaçlanan durumuna geri yüklemek için gereken minimum miktarda işi halleder.

Bunun bir parçası olarak her düğüm, bir bağımlılık keşfi süreci yürütür. Her düğüm bağımlılıkları tanımlayabilir ve ardından daha da fazla bağımlılıkları bildirmek için bu bağımlılıkların içeriğini kullanabilir. Prensip olarak, bu işlem düğüm başına iş parçacığı modeliyle uyumludur. Bununla birlikte, orta boyutlu derlemeler yüz binlerce Skyframe düğümü içerir ve bu, mevcut Java teknolojisiyle kolayca mümkün olmaz (ve tarihsel nedenlerle şu anda Java'yı kullanmak zorundayız, bu yüzden basit iş parçacıkları ve devamlılık yok).

Bazel bunun yerine sabit boyutlu bir iş parçacığı havuzu kullanır. Ancak bu, bir düğümün henüz mevcut olmayan bir bağımlılığı bildirmesi durumunda, bu değerlendirmeyi iptal edip bağımlılık mevcut olduğunda yeniden başlatmamız (muhtemelen başka bir iş parçacığında) gerekebileceği anlamına gelir. Diğer bir deyişle, bu da düğümlerin bunu çok fazla yapmaması gerektiği anlamına gelir. Seri olarak N bağımlılığı bildiren bir düğüm, N kez yeniden başlatılabilir. Bu durumda O(N^2) süresine mal olur. Bunun yerine, bağımlılıkların önceden toplu olarak bildirilmesini amaçlarız. Bu işlemler için bazen kodun yeniden düzenlenmesi, hatta yeniden başlatma sayısını sınırlamak için bir düğümün birden fazla düğüme bölünmesi gerekir.

Bu teknolojinin şu anda rules API'sinde kullanılamadığını unutmayın. Bunun yerine, rules API'si hâlâ eski yükleme, analiz ve yürütme aşamaları kavramları kullanılarak tanımlanmaktadır. Ancak temel kısıtlamalardan biri, diğer düğümlere yapılan tüm erişimlerin, karşılık gelen bağımlılıkları izleyebilmesi için çerçeveden geçmesidir. Derleme sisteminin uygulandığı dilden veya kuralların yazıldığı dilden bağımsız olarak (aynı olması gerekmez) 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 her türlü düşünme biçimi ve bunu yapan kitaplıklardan kaçınmak anlamına gelir. Bu düşük seviye arayüzlere bağımlılık ekleme özelliğini destekleyen kitaplıkların da Skyframe için doğru şekilde ayarlanması gerekir.

Bu, kural yazarlarının en baştan tam bir dil çalışma zamanına maruz kalmasını önlemenizi önemle tavsiye eder. Bu tür API'lerin yanlışlıkla kullanılma riski çok büyüktür. Geçmişte Bazel ekibi veya diğer alan uzmanları tarafından yazılmış olmasına rağmen, güvenli olmayan API'leri kullanan kurallardan kaynaklanan bazı Bazel hataları oluşmuştur.

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

Skyframe'in getirdiği gereksinimler, Java'yı kullanmanın geçmiş kısıtlamaları ve kuralları API'sinin güncelliğini yitirmiş olması dışında, kitaplık ve ikili program kurallarına dayalı tüm derleme sistemlerinde yanlışlıkla ikinci dereceden zaman veya bellek tüketiminin sağlanması sorunu daha da kötü hale getiriyor. İ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 ve C'ye bağımlı olduğu gibi bir kitaplık kuralları zincirinin durumunu düşünün. Ardından, bu kuralların geçişli kapanışı üzerinden bazı özellikleri (ör. Java çalışma zamanı sınıf yolu veya her kitaplık için C++ bağlayıcı komutu) hesaplamak istiyoruz. Normalde standart bir liste uygulaması kabul edebiliriz. Ancak, bu halihazırda ikinci dereceden bellek tüketimini de beraberinde getirmektedir: İlk kitaplık sınıf yolunda bir giriş, ikincisi, üçüncü üçü ve dolayısıyla da toplam 1+2+3+...+N = O(N^2) girişi olmak üzere toplamda bir giriş içerir.

  2. Aynı Kitaplık Kurallarına Bağlı İkili Program Kurallar: Aynı kitaplık kurallarına bağlı bir ikili program kümesinin (örneğin, aynı kitaplık kodunu test eden çok sayıda test kuralınız olması) geçerli olduğu durumu göz önünde bulundurun. N kuraldan yarısının ikili kurallar, diğer yarısının ise kitaplık kuralları olduğunu varsayalım. Şimdi her ikili programın, kitaplık kurallarının geçişli kapatılmasıyla hesaplanan bir özelliğin (ör. Java çalışma zamanı sınıf yolu veya C++ bağlayıcı komut satırı) kopyasını oluşturduğunu düşünün. Örneğin, C++ bağlantısı işleminin komut satırı dizesi gösterimini genişletebilir. N/2 elemanının N/2 kopyası O(N^2) bellektir.

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

Bazel, bu senaryoların ikisinden de yoğun bir şekilde etkilenmektedir. Bu nedenle, her adımda kopya oluşturmayarak bilgileri bellekte etkili bir şekilde sıkıştıran bir dizi özel koleksiyon sınıfını kullanıma sunduk. Bu veri yapılarının neredeyse tamamı belirli anlamlara sahip olduğundan depset (dahili uygulamada NestedSet olarak da bilinir) olarak adlandırıldı. Geçtiğimiz birkaç yıl içinde Bazel'ın bellek tüketimini azaltmaya yönelik olarak çoğu değişiklik, daha önce kullanılanların yerine depsetsin kullanımına yönelik değişikliklerdi.

Maalesef derinliklerin kullanılması tüm sorunları otomatik olarak çözmez. Özellikle, her kuralda yalnızca bir düşüş üzerinde iterasyon yapmak bile ikinci dereceden zaman tüketimine yol açar. NestedSets'in dahili olarak, normal koleksiyon sınıflarıyla birlikte çalışabilirliği kolaylaştıran bazı yardımcı yöntemleri de vardır. Maalesef NestedSet'in bu yöntemlerden birine yanlışlıkla geçirilmesi, davranışı kopyalamaya yol açar ve ikinci dereceden bellek tüketimini tekrar başlatır.