WebAssembly Hatalarını Daha Hızlı Ayıklama

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
Eric Leese
Sam Clegg

Chrome Dev Summit 2020'de, Chrome'un WebAssembly uygulamaları için hata ayıklama desteğini web'de ilk kez gösterdik. O zamandan beri ekip, geliştirici deneyimini büyük ve hatta çok büyük uygulamalar için ölçeklendirecek şekilde geliştirmeye çok fazla enerji harcadı. Bu yayında, farklı araçlara eklediğimiz (veya çalışmasını sağladığımız) düğmeleri ve bunların nasıl kullanılacağını göstereceğiz.

Ölçeklenebilir hata ayıklama

2020'deki gönderimizde kaldığımız yerden devam edelim. O zamanlar incelediğimiz örnek:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

Bu örnek hâlâ oldukça küçüktür ve büyük bir uygulamada karşılaşacağınız gerçek sorunlardan hiçbirini görme olasılığınız yoktur. Yine de yeni özelliklerin neler olduğunu size gösterebiliriz. Kurulumu ve denemesi hızlı ve kolaydır.

Son yayında, bu örneğin nasıl derleneceğini ve nasıl hata ayıklayacağınızı ele almıştık. Bunu tekrar yapalım ancak //performance// bölümüne de göz atalım:

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH

Bu komut, 3 MB boyutunda bir wasm ikili dosyası oluşturur. Bunun büyük kısmı, tahmin edebileceğiniz gibi hata ayıklama bilgileridir. Bunu llvm-objdump aracıyla [1] doğrulayabilirsiniz. Örneğin:

$ llvm-objdump -h mandelbrot.wasm

mandelbrot.wasm:        file format wasm

Sections:
Idx Name          Size     VMA      Type
  0 TYPE          0000026f 00000000
  1 IMPORT        00001f03 00000000
  2 FUNCTION      0000043e 00000000
  3 TABLE         00000007 00000000
  4 MEMORY        00000007 00000000
  5 GLOBAL        00000021 00000000
  6 EXPORT        0000014a 00000000
  7 ELEM          00000457 00000000
  8 CODE          0009308a 00000000 TEXT
  9 DATA          0000e4cc 00000000 DATA
 10 name          00007e58 00000000
 11 .debug_info   000bb1c9 00000000
 12 .debug_loc    0009b407 00000000
 13 .debug_ranges 0000ad90 00000000
 14 .debug_abbrev 000136e8 00000000
 15 .debug_line   000bb3ab 00000000
 16 .debug_str    000209bd 00000000

Bu çıkışta, oluşturulan wasm dosyasında bulunan tüm bölümler gösterilir. Bunların çoğu standart WebAssembly bölümleridir ancak adı .debug_ ile başlayan birkaç özel bölüm de vardır. İkili dosya, hata ayıklama bilgilerinizi burada içerir. Tüm boyutları topladığımızda, hata ayıklama bilgilerinin 3 MB'lık dosyamızın yaklaşık 2,3 MB'ını oluşturduğunu görürüz. emcc komutunu da time edersek makinemizde komutun yaklaşık 1, 5 saniye sürdüğünü görürüz. Bu sayılar güzel bir referans noktası oluşturuyor ancak o kadar küçükler ki muhtemelen kimse bunlara dikkat etmez. Ancak gerçek uygulamalarda hata ayıklama ikili dosyası kolayca GB'lara ulaşabilir ve derlenmesi dakikalar sürebilir.

Skipping Binaryen

Emscripten ile bir wasm uygulaması oluştururken son derleme adımlarından biri Binaryen optimizatörünü çalıştırmaktır. Binaryen, hem WebAssembly (benzeri) ikili dosyaları optimize eden hem de yasallaştıran bir derleyici araç paketidir. Derleme kapsamında Binaryen'in çalıştırılması oldukça pahalıdır ancak yalnızca belirli koşullarda gereklidir. Binaryen geçişlerine gerek kalmamasını sağlarsak hata ayıklama derlemeleri için derleme süresini önemli ölçüde hızlandırabiliriz. En yaygın olarak gerekli olan Binaryen geçişi, 64 bit tam sayı değerleri içeren işlev imzalarının yasallaştırılmasıdır. -sWASM_BIGINT kullanarak WebAssembly BigInt entegrasyonunu etkinleştirerek bu sorunu önleyebiliriz.

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Güvenli olması için -sERROR_ON_WASM_CHANGES_AFTER_LINK işaretini de ekledik. Binaryen'in ne zaman çalıştığını ve ikili dosyayı beklenmedik bir şekilde yeniden yazdığını tespit etmenize yardımcı olur. Böylece, hızlı bir şekilde ilerlediğimizden emin olabiliriz.

Örneğimiz oldukça küçük olsa da Binaryen'i atlamanın etkisini görebiliriz. time'ye göre bu komut 1 saniyenin biraz altında sürede çalışıyor. Yani öncekinden yarım saniye daha hızlı.

Gelişmiş ince ayarlar

Giriş dosyası taramasını atlama

Normalde bir Emscripten projesi bağlanırken emcc, tüm giriş nesne dosyalarını ve kitaplıklarını tarar. Bunu, programınızdaki JavaScript kitaplık işlevleri ile yerel semboller arasında hassas bağımlılıklar uygulamak için yapar. Daha büyük projelerde, giriş dosyalarının llvm-nm kullanılarak ek olarak taranması bağlantı süresini önemli ölçüde uzatabilir.

Bunun yerine, emcc'ye JavaScript işlevlerinin tüm yerel bağımlılıkları dahil etmesini söyleyen -sREVERSE_DEPS=all ile çalıştırabilirsiniz. Bu yöntem, küçük bir kod boyutu yükü oluşturur ancak bağlantı sürelerini kısaltabilir ve hata ayıklama derlemeleri için yararlı olabilir.

Örneğimizdeki kadar küçük bir proje için bu durum gerçek bir fark yaratmaz ancak projenizde yüzlerce hatta binlerce nesne dosyanız varsa bağlantı sürelerini önemli ölçüde iyileştirebilir.

"Ad" bölümünü kaldırma

Büyük projelerde (özellikle çok fazla C++ şablonu kullanılan projelerde) WebAssembly "ad" bölümü çok büyük olabilir. Örneğimizde bu değer, toplam dosya boyutunun yalnızca küçük bir kısmıdır (yukarıdaki llvm-objdump çıktısına bakın) ancak bazı durumlarda çok önemli olabilir. Uygulamanızın "ad" bölümü çok büyükse ve hata ayıklama ihtiyaçlarınız için dwarf hata ayıklama bilgileri yeterliyse "ad" bölümünü kaldırmak avantajlı olabilir:

$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm

Bu işlem, DWARF hata ayıklama bölümlerini korurken WebAssembly "ad" bölümünü kaldırır.

Fission'u hata ayıklama

Çok fazla hata ayıklama verisi içeren ikili dosyalar, derleme süresine olduğu kadar hata ayıklama süresine de baskı uygular. Hata ayıklayıcının, "x yerel değişkeninin türü nedir?" gibi sorgulara hızlıca yanıt verebilmek için verileri yüklemesi ve bunlar için bir dizin oluşturması gerekir.

Hata ayıklama bölme, bir ikili dosyanın hata ayıklama bilgilerini iki bölüme ayırmamıza olanak tanır: biri ikili dosyada kalan, diğeri ise ayrı bir DWARF nesnesi (.dwo) dosyasında bulunan bölüm. -gsplit-dwarf işaretçisi Emscripten'e iletilerek etkinleştirilebilir:

$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc  -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Aşağıda, farklı komutları ve hata ayıklama verileri olmadan, hata ayıklama verileri ile ve son olarak hem hata ayıklama verileri hem de hata ayıklama bölme işlemiyle derleme yapılarak hangi dosyaların oluşturulduğunu gösteriyoruz.

farklı komutlar ve oluşturulan dosyalar

DWARF verileri bölündüğünde, hata ayıklama verilerinin bir kısmı ikili programla birlikte bulunurken büyük kısmı mandelbrot.dwo dosyasına yerleştirilir (yukarıda gösterildiği gibi).

mandelbrot için yalnızca bir kaynak dosyamız var ancak genellikle projeler bundan daha büyüktür ve birden fazla dosya içerir. Bölme işlemini hata ayıklama işlemi, her biri için bir .dwo dosyası oluşturur. Hata ayıklayıcının mevcut beta sürümünün (0.1.6.1615) bu bölünmüş hata ayıklama bilgilerini yükleyebilmesi için tüm bunları aşağıdaki gibi bir DWARF paketinde (.dwp) toplamamız gerekir:

$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp

dwo dosyalarını DWARF paketi halinde toplama

DWARF paketini tek tek nesnelerden oluşturmanın avantajı, yalnızca bir tane ek dosya yayınlamanız gerekmesidir. Şu anda gelecekteki bir sürümde tüm nesneleri tek tek yüklemek için çalışıyoruz.

DWARF 5 ile ilgili sorun nedir?

Yukarıdaki emcc komutuna -gdwarf-5 başka bir işaret eklediğimizi fark etmiş olabilirsiniz. DWARF sembollerinin şu anda varsayılan olarak kullanılmayan 5. sürümünü etkinleştirmek, hata ayıklama işlemine daha hızlı başlamamıza yardımcı olacak başka bir yöntemdir. Bu sayede, ana ikili dosyada varsayılan 4. sürümün atladığı belirli bilgiler saklanır. Daha açık belirtmek gerekirse, kaynak dosyaların tamamını yalnızca ana ikili dosyadan belirleyebiliriz. Bu sayede hata ayıklayıcı, tam simge verilerini yükleyip ayrıştırmadan kaynak ağacının tamamını gösterme ve kesme noktaları ayarlama gibi temel işlemleri yapabilir. Bu, bölünmüş sembollerle hata ayıklama işlemini çok daha hızlı hale getirir. Bu nedenle, -gsplit-dwarf ve -gdwarf-5 komut satırı işaretlerini her zaman birlikte kullanırız.

DWARF5 hata ayıklama biçimiyle başka bir kullanışlı özelliğe de erişebiliriz. -gpubnames bayrağı iletilirken oluşturulacak hata ayıklama verilerine bir ad dizini ekler:

$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Hata ayıklama oturumunda, sembol aramaları genellikle bir öğeyi ada göre aramak (ör. bir değişken veya tür ararken) şeklinde gerçekleşir. Ad dizini, doğrudan adı tanımlayan derleme birimini işaret ederek bu aramayı hızlandırır. Ad dizini olmadan, aradığımız adlandırılmış öğeyi tanımlayan doğru derleme birimini bulmak için tüm hata ayıklama verilerinin kapsamlı bir şekilde aranması gerekir.

Merak edenler için: Hata ayıklama verilerine bakma

DWARF verilerine göz atmak için llvm-dwarfdump'ü kullanabilirsiniz. Bunu deneyelim:

llvm-dwarfdump mandelbrot.wasm

Bu sayede, hata ayıklama bilgilerine sahip olduğumuz "Derleme birimleri"ne (genel olarak kaynak dosyalar) genel bir bakış elde edebiliriz. Bu örnekte yalnızca mandelbrot.cc için hata ayıklama bilgileri mevcuttur. Genel bilgiler, iskelet birimi olduğunu gösterir. Bu, bu dosyayla ilgili verilerin eksik olduğu ve kalan hata ayıklama bilgilerini içeren ayrı bir .dwo dosyası olduğu anlamına gelir:

mandelbrot.wasm ve hata ayıklama bilgileri

Bu dosyadaki diğer tablolara da göz atabilirsiniz. Örneğin, wasm bayt kodunun C++ satırlarıyla eşlemesini gösteren satır tablosuna (llvm-dwarfdump -debug-line kullanmayı deneyin) bakabilirsiniz.

Ayrı .dwo dosyasında bulunan hata ayıklama bilgilerine de bakabiliriz:

llvm-dwarfdump mandelbrot.dwo

mandelbrot.wasm ve hata ayıklama bilgileri

Özet: Hata ayıklama için bölme kullanmanın avantajı nedir?

Büyük uygulamalarla çalışıyorsanız hata ayıklama bilgilerini bölmenin çeşitli avantajları vardır:

  1. Daha hızlı bağlantı: Bağlantılayıcının artık hata ayıklama bilgilerinin tamamını ayrıştırması gerekmez. Bağlantı oluşturucuların genellikle ikili dosyada bulunan DWARF verilerinin tamamını ayrıştırması gerekir. Hata ayıklama bilgilerinin büyük bir kısmını ayrı dosyalara ayırarak bağlayıcılar daha küçük ikili dosyalarla çalışır. Bu da daha hızlı bağlantı sürelerine (özellikle büyük uygulamalar için) neden olur.

  2. Daha hızlı hata ayıklama: Hata ayıklayıcı, bazı simge aramaları için .dwo/.dwp dosyalarındaki ek simgeleri ayrıştırmayı atlayabilir. Bazı aramalar için (ör. wasm'den C++ dosyalarına satır eşleme istekleri) ek hata ayıklama verilerini incelememiz gerekmez. Bu sayede ek hata ayıklama verilerini yükleyip ayrıştırmak zorunda kalmadığımız için zamandan tasarruf ederiz.

1: Sisteminizde llvm-objdump'nin güncel bir sürümü yoksa ve emsdk kullanıyorsanız emsdk/upstream/bin dizininde bulabilirsiniz.

Önizleme kanallarını indirme

Varsayılan geliştirme tarayıcınız olarak Chrome Canary, Yeni Geliştirilenler veya Beta sürümünü kullanabilirsiniz. Bu önizleme kanalları, en son DevTools özelliklerine erişmenize, en yeni web platformu API'lerini test etmenize ve sitenizdeki sorunları kullanıcılarınızdan önce bulmanıza yardımcı olur.

Chrome Geliştirici Araçları Ekibi ile iletişime geçme

Yeni özellikler, güncellemeler veya Geliştirici Araçları ile ilgili başka herhangi bir konu hakkında konuşmak için aşağıdaki seçenekleri kullanın.