Performansı Optimize Etme

Sorun bildir Kaynağı görüntüle Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Kural yazarken en sık karşılaşılan performans sorunu, bağımlılıklardan toplanan verilerin geçilmesi veya kopyalanmasıdır. Bu işlemler, tüm derleme üzerinde toplandığında kolayca O(N^2) zaman veya alan alabilir. Bu durumu önlemek için depsets'in nasıl etkili bir şekilde kullanılacağını anlamak çok önemlidir.

Bu işlemi doğru yapmak zor olabilir. Bu nedenle Bazel, hata yapmış olabileceğiniz noktaları bulmanıza yardımcı olan bir bellek profil oluşturucu da sağlar. Uyarı: Verimsiz bir kural yazmanın maliyeti, yaygın olarak kullanılana kadar belirgin olmayabilir.

Depset'leri kullanma

Kural bağımlılıklarından bilgi topladığınız her durumda depsets kullanmalısınız. Yalnızca mevcut kurala özgü bilgileri yayınlamak için düz listeler veya sözlükler kullanın.

Bir veri kümesi, bilgileri paylaşmaya olanak tanıyan iç içe yerleştirilmiş bir grafik olarak gösterir.

Aşağıdaki grafiği inceleyin:

C -> B -> A
D ---^

Her düğüm tek bir dize yayınlar. Bağımlılık kümeleriyle veriler şu şekilde görünür:

a = depset(direct=['a'])
b = depset(direct=['b'], transitive=[a])
c = depset(direct=['c'], transitive=[b])
d = depset(direct=['d'], transitive=[b])

Her öğenin yalnızca bir kez belirtildiğini unutmayın. Listelerle elde edeceğiniz avantajlar:

a = ['a']
b = ['b', 'a']
c = ['c', 'b', 'a']
d = ['d', 'b', 'a']

Bu örnekte 'a' işaretinin dört kez kullanıldığına dikkat edin. Daha büyük grafiklerde bu sorun daha da kötüleşir.

Aşağıda, geçişli bilgileri yayınlamak için bağımlılık kümelerini doğru şekilde kullanan bir kural uygulamasına ilişkin örnek verilmiştir. Bunun O(N^2) olmadığı için, listeleri kullanarak kurala özgü bilgileri yayınlamanızda bir sakınca yoktur.

MyProvider = provider()

def _impl(ctx):
  my_things = ctx.attr.things
  all_things = depset(
      direct=my_things,
      transitive=[dep[MyProvider].all_things for dep in ctx.attr.deps]
  )
  ...
  return [MyProvider(
    my_things=my_things,  # OK, a flat list of rule-local things only
    all_things=all_things,  # OK, a depset containing dependencies
  )]

Daha fazla bilgi için depset genel bakış sayfasına bakın.

depset.to_list() numaralı telefonu aramaktan kaçının

to_list() kullanarak bir depset'i düz bir listeye dönüştürebilirsiniz ancak bu genellikle O(N^2) maliyetine neden olur. Mümkünse hata ayıklama dışında hiçbir amaçla bağımlılık kümelerini düzleştirmeyin.

Yalnızca <xx>_binary kuralı gibi üst düzey hedeflerde düzleştirme yaparsanız maliyet, derleme grafiğinin her düzeyinde birikmeyeceğinden bağımlılık kümelerini serbestçe düzleştirebileceğiniz yaygın bir yanılgıdır. Ancak bu, çakışan bağımlılıklarla bir hedef grubu oluşturduğunuzda yine O(N^2) olur. Bu durum, testlerinizi oluştururken //foo/tests/... veya bir IDE projesini içe aktarırken meydana gelir.

Arama sayısını depset olarak azaltın.

depset işlevini döngü içinde çağırmak genellikle hatalı bir işlemdir. Bu durum, çok derin iç içe yerleştirme içeren ve kötü performans gösteren bağımlılık kümelerine yol açabilir. Örneğin:

x = depset()
for i in inputs:
    # Do not do that.
    x = depset(transitive = [x, i.deps])

Bu kod kolayca değiştirilebilir. İlk olarak, geçişli bağımlılık kümelerini toplayın ve hepsini aynı anda birleştirin:

transitive = []

for i in inputs:
    transitive.append(i.deps)

x = depset(transitive = transitive)

Bu, bazen liste kavrama kullanılarak azaltılabilir:

x = depset(transitive = [i.deps for i in inputs])

Komut satırları için ctx.actions.args() kullanın

Komut satırları oluştururken ctx.actions.args() kullanmalısınız. Bu, tüm bağımlılık kümelerinin genişletilmesini yürütme aşamasına erteler.

Bu yöntem, daha hızlı olmasının yanı sıra kurallarınızın bellek tüketimini de azaltır. Bazen bu oran% 90 veya daha fazla olabilir.

İşte birkaç ipucu:

  • Düzleştirmek yerine bağımlılık kümelerini ve listeleri doğrudan bağımsız değişken olarak iletin. Bu öğeler, sizin için ctx.actions.args() tarafından genişletilir. Depsset içeriklerinde herhangi bir dönüşüm yapmanız gerekiyorsa ctx.actions.args#add işlevine göz atarak uygun bir seçenek olup olmadığını kontrol edin.

  • Bağımsız değişken olarak File#path değerini mi iletiyorsunuz? Gerek yok. Tüm dosyalar, yollarına dönüştürülür ve genişletme zamanına ertelenir.

  • Dizeleri birleştirerek oluşturmaktan kaçının. En iyi dize bağımsız değişkeni, belleği kuralınızın tüm örnekleri arasında paylaşıldığı için sabittir.

  • Args komut satırı için çok uzunsa ctx.actions.args() nesnesi, ctx.actions.args#use_param_file kullanılarak koşullu veya koşulsuz olarak bir param dosyasına yazılabilir. Bu işlem, işlem yürütüldüğünde arka planda yapılır. Parametreler dosyasını açıkça kontrol etmeniz gerekiyorsa ctx.actions.write kullanarak dosyayı manuel olarak yazabilirsiniz.

Örnek:

def _impl(ctx):
  ...
  args = ctx.actions.args()
  file = ctx.declare_file(...)
  files = depset(...)

  # Bad, constructs a full string "-<-foo=file> path" for each rule instance
  args.add("--foo=" + file.path)

  # Good, shares "--foo" among all rule instances, and defers file.path to later
  # I<t will ho>wever pass ["--foo", file path] to the acti<on comman>d line,
  # instead of ["--foo=file_path"]
  args.add(&qu<ot;--foo&>quot;, file)

  <# Use for>mat if you prefer ["--foo=file path"] to ["--foo", file path]
  args.add(format="--foo=%s", value=file)

  # Bad, makes a giant string of a whole depset
  args.add(" ".join(["-I%s" % file.short_path for file in files])

  # Good, only stores a reference to the depset
  args.add_all(files, format_each=";-I%s", map_each=_to_short_path)

# Function passed to map_each above
def _to_short_path(f):
  return f.short_path

Geçişli işlem girişleri, bağımlılık kümeleri olmalıdır.

ctx.actions.run kullanarak bir işlem oluştururken inputs alanının bir depset kabul ettiğini unutmayın. Girişler geçişli olarak bağımlılıklardan toplandığında bunu kullanın.

inputs = depset(...)
ctx.actions.run(
  inputs = inputs,  # Do *not* turn inputs into a list
  ...
)

Asılı

Bazel askıda kalmış gibi görünüyorsa Ctrl-\ tuşlarına basabilir veya SIGQUIT sinyali (kill -3 $(bazel info server_pid)) göndererek $(bazel info output_base)/server/jvm.out dosyasında iş parçacığı dökümü alabilirsiniz.

Bazel askıda kalırsa bazel info komutunu çalıştıramayabilirsiniz. Bu nedenle, output_base dizini genellikle çalışma alanı dizininizdeki bazel-<workspace> sembolik bağlantısının üst dizinidir.

Performans profili oluşturma

Bazel, varsayılan olarak çıkış tabanındaki command.profile.gz konumuna bir JSON profili yazar. Konumu --profile işaretiyle yapılandırabilirsiniz. Örneğin: --profile=/tmp/profile.gz. .gz ile biten konumlar GZIP ile sıkıştırılır.

Sonuçları görmek için bir Chrome tarayıcı sekmesinde chrome://tracing adresini açın, "Yükle"yi tıklayın ve (sıkıştırılmış olabilecek) profil dosyasını seçin. Daha ayrıntılı sonuçlar için sol alt köşedeki kutuları tıklayın.

Gezinmek için aşağıdaki klavye kontrollerini kullanabilirsiniz:

  • "Seç" modu için 1 tuşuna basın. Bu modda, etkinlik ayrıntılarını incelemek için belirli kutuları seçebilirsiniz (sol alt köşeye bakın). Özet ve toplu istatistikler almak için birden fazla etkinlik seçin.
  • "Kaydırma" modu için 2 tuşuna basın. Ardından, görünümü taşımak için fareyi sürükleyin. Sola/sağa gitmek için a/d tuşlarını da kullanabilirsiniz.
  • "Yakınlaştırma" modu için 3 tuşuna basın. Ardından, yakınlaştırmak için fareyi sürükleyin. Yakınlaştırmak veya uzaklaştırmak için w/s tuşlarını da kullanabilirsiniz.
  • İki etkinlik arasındaki mesafeyi ölçebileceğiniz "zamanlama" modu için 4 tuşuna basın.
  • Tüm kontroller hakkında bilgi edinmek için ? düğmesine basın.

Profil bilgileri

Örnek profil:

Örnek profil

1. şekil. Örnek profil.

Bazı özel satırlar vardır:

  • action counters: Kaç eşzamanlı işlemin devam ettiğini gösterir. Gerçek değeri görmek için bu metriği tıklayın. Temiz derlemelerde --jobs değerine kadar çıkmalıdır.
  • cpu counters: Derlemenin her saniyesi için Bazel tarafından kullanılan CPU miktarını gösterir (1 değeri, bir çekirdeğin% 100 meşgul olduğu anlamına gelir).
  • Critical Path: Kritik yoldaki her işlem için bir blok gösterir.
  • grpc-command-1: Bazel'in ana iş parçacığı. Bazel'in ne yaptığına dair üst düzey bir resim elde etmek için yararlıdır. Örneğin, "Launch Bazel", "evaluateTargetPatterns" ve "runAnalysisPhase".
  • Service Thread: Küçük ve büyük çöp toplama (GC) duraklamalarını gösterir.

Diğer satırlar Bazel iş parçacıklarını temsil eder ve bu iş parçacığındaki tüm etkinlikleri gösterir.

Sık karşılaşılan performans sorunları

Performans profillerini analiz ederken şunlara dikkat edin:

  • Beklenenden daha yavaş analiz aşaması (runAnalysisPhase), özellikle de artımlı derlemelerde. Bu durum, kuralın kötü uygulanmasının bir işareti olabilir. Örneğin, depsets'i düzleştiren bir kural. Paket yükleme, aşırı sayıda hedef, karmaşık makro veya yinelemeli glob nedeniyle yavaş olabilir.
  • Tek tek yavaş işlemler, özellikle de kritik yoldaki işlemler. Büyük işlemleri birden fazla küçük işleme bölmek veya bunları hızlandırmak için (geçişli) bağımlılıklar kümesini azaltmak mümkün olabilir. Ayrıca, PROCESS_TIME gibi alışılmadık derecede yüksek bir PROCESS_TIME olup olmadığını da kontrol edin (ör. REMOTE_SETUP veya FETCH).
  • Darboğazlar: Diğer tüm iş parçacıkları boşta dururken / sonucu beklerken az sayıda iş parçacığı meşgul (yukarıdaki ekran görüntüsünde yaklaşık 15-30 saniye arasına bakın). Bunu optimize etmek için büyük olasılıkla daha fazla paralellik sağlamak üzere kural uygulamalarına veya Bazel'e dokunmanız gerekir. Bu durum, olağan dışı miktarda GC olduğunda da yaşanabilir.

Profil dosya biçimi

En üst düzey nesne, meta verileri (otherData) ve gerçek izleme verilerini (traceEvents) içerir. Meta veriler, ek bilgiler (ör. çağırma kimliği ve Bazel çağırma tarihi) içerir.

Örnek:

{
  "otherData": {
    "build_id": "101bff9a-7243-4c1a-8503-9dc6ae4c3b05",
    "date": "Tue Jun 16 08:30:21 CEST 2020",
    "profile_finish_ts": "1677666095162000",
    "output_base": "/usr/local/google/_bazel_johndoe/573d4be77eaa72b91a3dfaa497bf8cd0"
  },
  "traceEvents": [
    {"name":"thread_name","ph":"M","pid":1,"tid":0,"args":{"name":"Critical Path"}},
    {"cat":"build phase marker","name":"Launch Bazel","ph":"X","ts":-1824000,"dur":1824000,"pid":1,"tid":60},
    ...
    {"cat":"general information","name":"NoSpawnCacheModule.beforeCommand","ph":"X","ts":116461,"dur":419,"pid":1,"tid":60},
    ...
    {"cat":"package creation","name":"src";,"ph":"X","ts":279844,"dur":15479,"pid":1,"tid":838},
    ...
    {"name":"thread_name","ph":"M","pid":1,"tid":11,"args":{"name":"Service Thread"}},
    {"cat":"gc notification","name":"minor GC","ph":"X","ts":334626,"dur":13000,"pid":1,"tid":11},

    ...
    {"cat":"action processing","name":"Compiling third_party/grpc/src/core/lib/transport/status_conversion.cc","ph":"X","ts":12630845,"dur":136644,"pid":1,"tid":1546}
 ]
}

İzleme etkinliklerindeki zaman damgaları (ts) ve süreler (dur) mikrosaniye cinsinden verilir. Kategori (cat), ProfilerTask enum değerlerinden biridir. Çok kısa ve birbirine yakın olan bazı etkinliklerin birleştirildiğini unutmayın. Etkinlik birleştirmeyi önlemek istiyorsanız --noslim_json_profile değerini iletin.

Ayrıca Chrome İzleme Etkinliği Biçimi Spesifikasyonu'na da bakın.

analyze-profile

Bu profilleme yöntemi iki adımdan oluşur. İlk olarak, derleme/test işleminizi --profile işaretiyle çalıştırmanız gerekir. Örneğin:

$ bazel build --profile=/tmp/prof //path/to:target

Oluşturulan dosya (bu örnekte /tmp/prof) ikili dosyadır ve analyze-profile komutuyla sonradan işlenip analiz edilebilir:

$ bazel analyze-profile /tmp/prof

Varsayılan olarak, belirtilen profil veri dosyası için özet analiz bilgilerini yazdırır. Bu, her derleme aşamasında farklı görev türleri için kümülatif istatistikleri ve kritik yol analizini içerir.

Varsayılan çıktının ilk bölümünde, farklı derleme aşamalarında harcanan süreye genel bir bakış sunulur:

INFO: Profile created on Tue Jun 16 08:59:40 CEST 2020, build ID: 0589419c-738b-4676-a374-18f7bbc7ac23, output base: /home/johndoe/.cache/bazel/_bazel_johndoe/d8eb7a85967b22409442664d380222c0

=== PHASE SUMMARY INFORMATION ===

Total launch phase time         1.070 s   12.95%
Total init phase time           0.299 s    3.62%
Total loading phase time        0.878 s   10.64%
Total analysis phase time       1.319 s   15.98%
Total preparation phase time    0.047 s    0.57%
Total execution phase time      4.629 s   56.05%
Total finish phase time         0.014 s    0.18%
------------------------------------------------
Total run time                  8.260 s  100.00%

Critical path (4.245 s):
       Time Percentage   Description
    8.85 ms    0.21%   _Ccompiler_Udeps for @local_config_cc// compiler_deps
    3.839 s   90.44%   action 'Compiling external/com_google_protobuf/src/google/protobuf/compiler/php/php_generator.cc [for host]'
     270 ms    6.36%   action 'Linking external/com_google_protobuf/protoc [for host]'
    0.25 ms    0.01%   runfiles for @com_google_protobuf// protoc
     126 ms    2.97%   action 'ProtoCompile external/com_google_protobuf/python/google/protobuf/compiler/plugin_pb2.py'
    0.96 ms    0.02%   runfiles for //tools/aquery_differ aquery_differ

Bellek kullanımının profilini çıkarma

Bazel, kuralınızın bellek kullanımını kontrol etmenize yardımcı olabilecek yerleşik bir bellek profili oluşturucuyla birlikte gelir. Bir sorun varsa soruna neden olan tam kod satırını bulmak için yığını dökebilirsiniz.

Bellek izlemeyi etkinleştirme

Bu iki başlangıç işaretini her Bazel çağrısına iletmeniz gerekir:

  STARTUP_FLAGS=\
  --host_jvm_args=-javaagent:$(BAZEL)/third_party/allocation_instrumenter/java-allocation-instrumenter-3.3.0.jar \
  --host_jvm_args=-DRULE_MEMORY_TRACKER=1

Bu komutlar, sunucuyu bellek izleme modunda başlatır. Bunları tek bir Bazel çağrısı için bile unutursanız sunucu yeniden başlatılır ve baştan başlamanız gerekir.

Bellek İzleyici'yi kullanma

Örneğin, hedef foo öğesine bakıp ne işe yaradığını görün. Yalnızca analizi çalıştırmak ve derleme yürütme aşamasını çalıştırmamak için --nobuild işaretini ekleyin.

$ bazel $(STARTUP_FLAGS) build --nobuild //foo:foo

Ardından, Bazel örneğinin tamamının ne kadar bellek kullandığını görün:

$ bazel $(STARTUP_FLAGS) info used-heap-size-after-gc
> 2594MB

bazel dump --rules kullanarak kural sınıfına göre dökümünü yapın:

$ bazel $(STARTUP_FLAGS) dump --rules
>

RULE                                 COUNT     ACTIONS          BYTES         EACH
genrule                             33,762      33,801    291,538,824        8,635
config_setting                      25,374           0     24,897,336          981
filegroup                           25,369      25,369     97,496,272        3,843
cc_library                           5,372      73,235    182,214,456       33,919
proto_library                        4,140     110,409    186,776,864       45,115
android_library                      2,621      36,921    218,504,848       83,366
java_library                         2,371      12,459     38,841,000       16,381
_gen_source                            719       2,157      9,195,312       12,789
_check_proto_library_deps              719         668      1,835,288        2,552
... (more output)

pprof dosyası oluşturarak belleğin nereye gittiğine bakın.bazel dump --skylark_memory kullanın:

$ bazel $(STARTUP_FLAGS) dump --skylark_memory=$HOME/prof.gz
> Dumping Starlark heap to: /usr/local/google/home/$USER/prof.gz

Yığını incelemek için pprof aracını kullanın. İyi bir başlangıç noktası, pprof -flame $HOME/prof.gz kullanarak alev grafiği elde etmektir.

https://github.com/google/pprof adresinden pprof dosyasını indirin.

En popüler arama sitelerinin satırlarla açıklanmış metin dökümünü alın:

$ pprof -text -lines $HOME/prof.gz
>
      flat  flat%   sum%        cum   cum%
  146.11MB 19.64% 19.64%   146.11MB 19.64%  android_library <native>:-1
  113.02MB 15.19% 34.83%   113.02MB 15.19%  genrule <native>:-1
   74.11MB  9.96% 44.80%    74.11MB  9.96%  glob <native>:-1
   55.98MB  7.53% 52.32%    55.98MB  7.53%  filegroup <native>:-1
   53.44MB  7.18% 59.51%    53.44MB  7.18%  sh_test <native>:-1
   26.55MB  3.57% 63.07%    26.55MB  3.57%  _generate_foo_files /foo/tc/tc.bzl:491
   26.01MB  3.50% 66.57%    26.01MB  3.50%  _build_foo_impl /foo/build_test.bzl:78
   22.01MB  2.96% 69.53%    22.01MB  2.96%  _build_foo_impl /foo/build_test.bzl:73
   ... (more output)