Proses Debug WebAssembly Lebih Cepat

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

Di Chrome Dev Summit 2020, kami mendemonstrasikan dukungan proses debug Chrome untuk aplikasi WebAssembly di web untuk pertama kalinya. Sejak saat itu, tim telah menginvestasikan banyak energi untuk membuat pengalaman developer berskala besar untuk aplikasi yang besar dan bahkan sangat besar. Dalam postingan ini, kami akan menunjukkan tombol yang kami tambahkan (atau berhasil) di berbagai alat dan cara menggunakannya.

Proses debug yang skalabel

Mari kita lanjutkan dari postingan terakhir kita di tahun 2020 ini. Berikut adalah contoh yang kami lihat saat itu:

#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();
}

Ini masih merupakan contoh yang cukup kecil dan Anda mungkin tidak akan melihat masalah sebenarnya yang akan Anda lihat dalam aplikasi yang sangat besar, tetapi kami masih dapat menunjukkan kepada Anda fitur-fitur baru. Penyiapannya cepat, mudah, dan bisa dicoba sendiri.

Dalam postingan terakhir, kita membahas cara mengompilasi dan men-debug contoh ini. Mari kita lakukan lagi, tetapi mari kita lihat //performance//:

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

Perintah ini menghasilkan biner wasm sebesar 3 MB. Sebagian besar dari hal tersebut, seperti yang mungkin Anda duga, merupakan informasi debug. Anda dapat memverifikasinya dengan alat llvm-objdump [1], misalnya:

$ 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

Output ini menunjukkan semua bagian yang ada dalam file wasm yang dihasilkan, sebagian besar adalah bagian WebAssembly standar, tetapi ada juga beberapa bagian kustom yang namanya diawali dengan .debug_. Di situlah biner berisi informasi debug kita. Jika semua ukuran ditambahkan, kita akan melihat bahwa info debug mencapai sekitar 2,3 MB dari file 3 MB. Jika kita juga melakukan time perintah emcc, kita melihat bahwa di komputer kita perlu waktu sekitar 1,5 detik untuk berjalan. Angka-angka ini membuat garis dasar kecil yang bagus, tetapi mereka sangat kecil mungkin tidak ada yang akan memperhatikannya. Namun, dalam aplikasi yang sebenarnya, biner debug dapat dengan mudah mencapai ukuran dalam GB dan membutuhkan waktu beberapa menit untuk di-build.

Melewati Binaryen

Saat mem-build aplikasi wasm dengan Emscripten, salah satu langkah build akhirnya adalah menjalankan pengoptimal Binaryen. Binaryen adalah toolkit compiler yang mengoptimalkan dan melegalkan biner WebAssembly(-seperti). Menjalankan Binaryen sebagai bagian dari build cukup mahal, tetapi hanya diperlukan dalam kondisi tertentu. Untuk build debug, kita dapat mempercepat waktu build secara signifikan jika kita menghindari penerusan Binaryen. Penerusan Biner yang paling umum diperlukan adalah untuk melegalkan tanda tangan fungsi yang melibatkan nilai integer 64 bit. Dengan ikut serta dalam integrasi BigInt WebAssembly menggunakan -sWASM_BIGINT, kita dapat menghindari hal ini.

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

Kami telah menampilkan tanda -sERROR_ON_WASM_CHANGES_AFTER_LINK untuk pengukuran yang baik. Membantu mendeteksi kapan Binaryen berjalan dan menulis ulang biner secara tidak terduga. Dengan cara ini, kita dapat memastikan bahwa kita tetap berada di jalur yang cepat.

Meskipun contoh kita cukup kecil, kita masih bisa melihat efek melewatkan Binaryen! Menurut time, perintah ini berjalan kurang dari 1 detik, jadi setengah detik lebih cepat dari sebelumnya.

Penyesuaian lanjutan

Melewati pemindaian file input

Biasanya, saat menautkan project Emscripten, emcc akan memindai semua file dan library objek input. Hal ini dilakukan untuk mengimplementasikan dependensi yang tepat antara fungsi library JavaScript dan simbol native dalam program Anda. Untuk project yang lebih besar, pemindaian file input tambahan ini (menggunakan llvm-nm) dapat menambah waktu penautan secara signifikan.

Anda dapat menjalankan fungsi tersebut dengan -sREVERSE_DEPS=all yang memberi tahu emcc untuk menyertakan semua kemungkinan dependensi native fungsi JavaScript. Ini memiliki overhead ukuran kode kecil, tetapi dapat mempercepat waktu penautan dan dapat berguna untuk build debug.

Untuk proyek sekecil contoh kami, hal ini tidak ada bedanya, tetapi jika Anda memiliki ratusan atau bahkan ribuan file objek di proyek Anda, hal ini dapat meningkatkan waktu tautan secara signifikan.

Menghapus bagian “nama”

Dalam project besar, terutama yang memiliki banyak penggunaan template C++, bagian "nama" WebAssembly bisa sangat besar. Dalam contoh kita, ini hanya sebagian kecil dari ukuran file keseluruhan (lihat output llvm-objdump di atas), tetapi dalam beberapa kasus bisa menjadi sangat signifikan. Jika bagian "name" dari aplikasi Anda sangat besar, dan informasi debug dwarf cukup untuk kebutuhan proses debug, akan bermanfaat untuk menghapus bagian "name":

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

Tindakan ini akan menghapus bagian “nama” WebAssembly sambil mempertahankan bagian debug DWARF.

Pembagian debug

Biner dengan banyak data debug tidak hanya menekan waktu build tetapi juga pada waktu proses debug. Debugger perlu memuat data dan perlu membangun indeks untuk debugger, agar dapat dengan cepat merespons kueri, seperti "Apa jenis variabel lokal x?".

Pembagian debug memungkinkan kita membagi informasi debug untuk biner menjadi dua bagian: satu, yang tetap berada dalam biner, dan satu lagi, yang terdapat dalam file objek DWARF (.dwo) terpisah. Ini dapat diaktifkan dengan meneruskan flag -gsplit-dwarf ke Emscripten:

$ 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

Di bawah ini, kami menunjukkan berbagai perintah dan file yang dihasilkan dengan mengompilasi tanpa data debug, dengan data debug, dan terakhir dengan data debug dan fisi debug.

perintah yang berbeda dan 
file apa yang dihasilkan

Saat memisahkan data DWARF, sebagian data debug berada bersama biner, sedangkan sebagian besar dimasukkan ke dalam file mandelbrot.dwo (seperti yang diilustrasikan di atas).

Untuk mandelbrot, kami hanya memiliki satu file sumber, tetapi umumnya project berukuran lebih besar dari ini dan menyertakan lebih dari satu file. Bagian debug menghasilkan file .dwo untuk setiap fisi. Agar versi beta debugger (0.1.6.1615) saat ini dapat memuat informasi debug terpisah ini, kita perlu menggabungkan semua hal tersebut menjadi paket DWARF (.dwp) seperti ini:

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

memaketkan file dwo ke dalam paket DWARF

Membangun paket DWARF dari masing-masing objek memiliki keuntungan bahwa Anda hanya perlu menyediakan satu file tambahan. Saat ini kami sedang berupaya memuat semua objek individual dalam rilis mendatang.

Apa yang dimaksud dengan DWARF 5?

Anda mungkin telah mengetahui, kita menyelinapkan flag lain ke dalam perintah emcc di atas, -gdwarf-5. Mengaktifkan simbol DWARF versi 5, yang saat ini bukan merupakan default, adalah trik lain untuk membantu kami memulai proses debug dengan lebih cepat. Dengan itu, informasi tertentu disimpan dalam biner utama yang ditinggalkan oleh versi {i>default<i} 4. Secara khusus, kita dapat menentukan set lengkap {i>file<i} hanya dari biner utama. Hal ini memungkinkan debugger melakukan tindakan dasar seperti menampilkan hierarki sumber lengkap dan menyetel titik henti sementara tanpa memuat dan mengurai data simbol lengkap. Hal ini membuat proses debug dengan simbol terpisah jauh lebih cepat, sehingga kita selalu menggunakan tanda command line -gsplit-dwarf dan -gdwarf-5 secara bersamaan.

Dengan format debug DWARF5, kita juga mendapatkan akses ke fitur lain yang berguna. Tutorial ini memperkenalkan indeks nama dalam data debug yang akan dibuat saat meneruskan flag -gpubnames:

$ 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

Selama sesi proses debug, pencarian simbol sering terjadi dengan menelusuri entity berdasarkan nama, misalnya, saat mencari variabel atau jenis. Indeks nama mempercepat penelusuran ini dengan mengarahkan langsung ke unit kompilasi yang menentukan nama tersebut. Tanpa indeks nama, penelusuran lengkap atas seluruh data debug akan diperlukan untuk menemukan unit kompilasi yang tepat yang mendefinisikan entity bernama yang sedang kita cari.

Bagi yang penasaran: Melihat data debug

Anda dapat menggunakan llvm-dwarfdump untuk melihat sekilas data DWARF. Mari kita coba:

llvm-dwarfdump mandelbrot.wasm

Ini memberi kita ringkasan tentang "Unit kompilasi" (kira-kira, file sumber) yang informasi debugnya kita miliki. Dalam contoh ini, kita hanya memiliki info debug untuk mandelbrot.cc. Info umum akan memberi tahu kita bahwa kita memiliki unit kerangka, yang berarti kita memiliki data yang tidak lengkap pada file ini, dan ada file .dwo terpisah yang berisi info debug yang tersisa:

mandelbrot.wasm dan info debug

Anda juga dapat melihat tabel lain dalam file ini, misalnya di tabel baris yang menunjukkan pemetaan bytecode wasm ke baris C++ (coba gunakan llvm-dwarfdump -debug-line).

Kita juga dapat melihat info debug yang terdapat dalam file .dwo terpisah:

llvm-dwarfdump mandelbrot.dwo

mandelbrot.wasm dan info debug

TL;DR: Apa keuntungan menggunakan pemisahan debug?

Ada beberapa keuntungan dalam memisahkan informasi debug jika seseorang bekerja dengan aplikasi besar:

  1. Penautan yang lebih cepat: Penaut tidak perlu lagi mengurai seluruh informasi debug. Penaut biasanya perlu mengurai seluruh data DWARF yang ada dalam biner. Dengan menghapus sebagian besar informasi debug ke dalam file terpisah, penaut menangani biner yang lebih kecil, yang menghasilkan waktu penautan lebih cepat (terutama berlaku untuk aplikasi besar).

  2. Proses debug yang lebih cepat: Debugger dapat melewati penguraian simbol tambahan dalam file .dwo/.dwp untuk beberapa pencarian simbol. Untuk beberapa pencarian (seperti permintaan pada pemetaan baris file wasm-to-C++), kita tidak perlu melihat data debug tambahan. Ini akan menghemat waktu kita, tanpa perlu memuat dan mengurai data debug tambahan.

1: Jika Anda tidak memiliki versi terbaru llvm-objdump di sistem, dan Anda menggunakan emsdk, Anda dapat menemukannya di direktori emsdk/upstream/bin.

Mendownload saluran pratinjau

Pertimbangkan untuk menggunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, menguji API platform web tercanggih, dan menemukan masalah di situs Anda sebelum pengguna melakukannya.

Menghubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur dan perubahan baru di postingan, atau hal lain yang berkaitan dengan DevTools.

  • Kirim saran atau masukan kepada kami melalui crbug.com.
  • Laporkan masalah DevTools menggunakan Opsi lainnya   Lainnya   > Bantuan > Laporkan masalah DevTools di DevTools.
  • Tweet di @ChromeDevTools.
  • Berikan komentar di video YouTube Apa yang baru di DevTools atau video YouTube Tips DevTools.