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
  # It will however pass ["--foo", <file path>] to the action command line,
  # instead of ["--foo=<file_path>"]
  args.add("--foo", file)

  # Use format 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

JSON izleme profili, Bazel'in çağırma sırasında ne kadar zaman harcadığını hızlıca anlamak için çok faydalı olabilir.

--experimental_command_profile işareti, çeşitli türlerdeki Java Flight Recorder profillerini (CPU süresi, gerçek süre, bellek ayırmaları ve kilit çekişmesi) yakalamak için kullanılabilir.

--starlark_cpu_profile işareti, tüm Starlark iş parçacıkları tarafından CPU kullanımının pprof profilini yazmak için kullanılabilir.

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 profil 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:<path to java-allocation-instrumenter-3.3.4.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)