แก้ไขข้อบกพร่อง WebAssembly ได้เร็วขึ้น

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

ที่งาน Chrome Dev Summit 2020 เราได้สาธิตการรองรับการแก้ไขข้อบกพร่องของ Chrome สำหรับแอปพลิเคชัน WebAssembly บนเว็บเป็นครั้งแรก นับแต่นั้นเป็นต้นมา ทีมได้ลงทุนลงแรงอย่างมากในการทำให้ประสบการณ์การใช้งานของนักพัฒนาซอฟต์แวร์มีขนาดใหญ่ขึ้นสำหรับแอปพลิเคชันขนาดใหญ่หรือขนาดใหญ่ ในโพสต์นี้เราจะแสดงปุ่มที่เพิ่มเข้าไป (หรือสร้างขึ้นเอง) ในเครื่องมือต่างๆ รวมถึงวิธีใช้

การแก้ไขข้อบกพร่องที่รองรับการปรับขนาด

มาเริ่มจากจุดที่ฟังค้างไว้ในโพสต์ปี 2020 กัน นี่คือตัวอย่างที่เรามองย้อนกลับไปในตอนนั้น

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

นี่ยังเป็นตัวอย่างที่ค่อนข้างเล็กและคุณอาจไม่เห็นปัญหาใดๆ จริงที่คุณจะเห็นในแอปพลิเคชันขนาดใหญ่ แต่เรายังคงสามารถแสดงให้คุณเห็นว่าคุณลักษณะใหม่มีอะไรบ้าง ติดตั้งและลองใช้ได้รวดเร็วและง่ายดายด้วยตัวคุณเอง!

ในโพสต์ล่าสุด เราได้พูดถึงวิธีคอมไพล์และแก้ไขข้อบกพร่องของตัวอย่างนี้ เรามาลองดูตัวอย่างของ //performance// กัน

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

คำสั่งนี้จะสร้างไบนารี Wasm ขนาด 3MB และอย่างที่คุณอาจคาดหวังคือข้อมูลการแก้ไขข้อบกพร่อง ซึ่งคุณจะยืนยันได้โดยใช้เครื่องมือ llvm-objdump [1] ตัวอย่างเช่น

$ 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

เอาต์พุตนี้แสดงส่วนทั้งหมดที่อยู่ในไฟล์ Wasm ที่สร้างขึ้น โดยส่วนใหญ่จะเป็นส่วน WebAssembly แบบมาตรฐาน แต่ก็ยังมีส่วนที่กำหนดเองอีกหลายรายการซึ่งขึ้นต้นด้วย .debug_ ซึ่งเป็นตำแหน่งที่ไบนารีมีข้อมูลการแก้ไขข้อบกพร่องของเรา หากเรารวมทุกขนาดแล้ว คุณจะเห็นว่าข้อมูลการแก้ไขข้อบกพร่องมีขนาดประมาณ 2.3 MB จากไฟล์ 3 MB ของเรา หากเรา time คำสั่ง emcc ด้วย คุณจะเห็นว่าเครื่องของเราใช้เวลาเรียกใช้ประมาณ 1.5 วินาที ตัวเลขเหล่านี้เป็นเกณฑ์พื้นฐานที่ดีพอสมควร แต่ก็อาจจะไม่มีใครสนใจเรื่องนี้มากนัก แต่ในการใช้งานจริง ไบนารีการแก้ไขข้อบกพร่องจะมีขนาดเป็น GB ได้อย่างง่ายดายและใช้เวลาสร้างไม่กี่นาที

การข้ามไบนารี

เมื่อสร้างแอปพลิเคชัน Wasm ด้วย Emscripten หนึ่งในขั้นตอนการสร้างขั้นสุดท้ายของแอปพลิเคชันคือการเรียกใช้เครื่องมือเพิ่มประสิทธิภาพ Binaryen ไบนารีคือชุดเครื่องมือคอมไพเลอร์ที่ทั้งเพิ่มประสิทธิภาพและทำให้ไบนารีของ WebAssembly (คล้าย) ถูกกฎหมาย การใช้งาน Binaryen ที่เป็นส่วนหนึ่งของบิลด์มีราคาค่อนข้างแพง แต่จำเป็นต้องใช้ภายใต้เงื่อนไขบางอย่างเท่านั้น สำหรับบิลด์การแก้ไขข้อบกพร่อง เราสามารถเร่งเวลาบิลด์ได้อย่างมากหากไม่จำเป็นต้องใช้การส่งแบบไบนารี การใช้ไบนารีที่จำเป็นที่พบบ่อยที่สุดคือการทำให้ลายเซ็นของฟังก์ชันที่เกี่ยวข้องกับค่าที่เป็นจำนวนเต็ม 64 บิตได้อย่างถูกต้อง การเลือกใช้การผสานรวม WebAssembly BigInt โดยใช้ -sWASM_BIGINT ช่วยให้เราหลีกเลี่ยงปัญหานี้ได้

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

เราได้รับแจ้งว่า -sERROR_ON_WASM_CHANGES_AFTER_LINK ไม่เหมาะสม ช่วยตรวจจับเมื่อ Binaryen กำลังทำงานและเขียนไบนารีใหม่โดยไม่คาดคิด วิธีนี้ทำให้เรามั่นใจได้ว่าเราจะดำเนินการได้อย่างรวดเร็ว

แม้ว่าตัวอย่างของเราจะค่อนข้างเล็ก แต่เรายังคงเห็นผลของการข้าม Binaryen อยู่ ตาม time คำสั่งนี้ทำงานไม่ถึง 1 วินาที ทำให้เร็วกว่าเดิมแค่ครึ่งวินาที

การปรับแต่งขั้นสูง

กำลังข้ามการสแกนไฟล์อินพุต

โดยปกติเมื่อลิงก์โปรเจ็กต์ Emscripten emcc จะสแกนไฟล์ออบเจ็กต์และไลบรารีอินพุตทั้งหมด วิธีการนี้เพื่อใช้การอ้างอิงที่แม่นยำระหว่างฟังก์ชันไลบรารี JavaScript และสัญลักษณ์ดั้งเดิมในโปรแกรมของคุณ สำหรับโปรเจ็กต์ขนาดใหญ่ การสแกนไฟล์อินพุตเพิ่มเติม (โดยใช้ llvm-nm) จะช่วยให้เวลาในการลิงก์เพิ่มขึ้นอย่างมาก

คุณจะเรียกใช้ด้วย -sREVERSE_DEPS=all แทนได้ ซึ่งจะบอก emcc ให้รวมทรัพยากร Dependency แบบดั้งเดิมของฟังก์ชัน JavaScript ทั้งหมด ซึ่งมีโอเวอร์เฮดโค้ดขนาดเล็กแต่เพิ่มความเร็วในการลิงก์ได้และมีประโยชน์สําหรับการแก้ไขข้อบกพร่องของบิลด์

สำหรับโปรเจ็กต์ที่มีขนาดเล็กเท่าตัวอย่างของเรา วิธีนี้ไม่แตกต่างกัน แต่หากคุณมีไฟล์ออบเจ็กต์นับร้อยหรือนับพันรายการในโปรเจ็กต์ของคุณ ก็จะช่วยให้เวลาในการลิงก์เพิ่มขึ้นอย่างมาก

นำส่วน "ชื่อ" ออก

ในโปรเจ็กต์ขนาดใหญ่ โดยเฉพาะโปรเจ็กต์ที่ใช้เทมเพลต C++ เป็นจำนวนมาก ส่วน "ชื่อ" ของ WebAssembly อาจมีขนาดใหญ่มาก ในตัวอย่างของเรา เป็นเพียงส่วนเล็กๆ ของขนาดไฟล์โดยรวม (ดูเอาต์พุตของ llvm-objdump ด้านบน) แต่ในบางกรณีอาจมีความสำคัญมาก หากส่วน "ชื่อ" ของแอปพลิเคชันของคุณมีขนาดใหญ่มาก และข้อมูลการแก้ไขข้อบกพร่องของคนแคระนั้นเพียงพอต่อความต้องการในการแก้ไขข้อบกพร่อง การลบส่วน "ชื่อ" ออกจะเป็นประโยชน์ดังนี้

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

การดำเนินการนี้จะตัดส่วน "name" ของ WebAssembly ออกโดยเก็บส่วนการแก้ไขข้อบกพร่อง DWARF ไว้

แก้ไขข้อบกพร่องของฟิชชัน

ไบนารีที่มีข้อมูลการแก้ไขข้อบกพร่องจำนวนมากไม่เพียงเป็นการสร้างแรงกดดันให้กับเวลาบิลด์ แต่ต้องใช้เวลาแก้ไขข้อบกพร่องด้วย โปรแกรมแก้ไขข้อบกพร่องต้องโหลดข้อมูลและต้องสร้างดัชนีเพื่อตอบสนองต่อคำค้นหาอย่างรวดเร็ว เช่น "ตัวแปรภายใน x เป็นประเภทใด"

ฟิชชันสำหรับการแก้ไขข้อบกพร่องช่วยให้เราแบ่งข้อมูลการแก้ไขข้อบกพร่องของไบนารีออกเป็น 2 ส่วน ส่วนแรกอยู่ในไบนารี และอีกส่วนหนึ่งอยู่ในไฟล์ออบเจ็กต์ DWARF (.dwo) แยกต่างหาก คุณจะเปิดใช้ได้โดยส่ง Flag -gsplit-dwarf ไปยัง 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

ด้านล่างนี้เราจะแสดงคำสั่งต่างๆ และไฟล์ที่จะสร้างขึ้นโดยการคอมไพล์โดยไม่มีข้อมูลการแก้ไขข้อบกพร่อง โดยมีข้อมูลการแก้ไขข้อบกพร่อง และสุดท้ายคือทั้งข้อมูลการแก้ไขข้อบกพร่องและฟิชชันการแก้ไขข้อบกพร่อง

คำสั่งต่างๆ และสร้างไฟล์

เมื่อแยกข้อมูล DWARF ข้อมูลการแก้ไขข้อบกพร่องส่วนหนึ่งจะอยู่รวมกับไบนารี ในขณะที่ข้อมูลขนาดใหญ่จะเก็บไว้ในไฟล์ mandelbrot.dwo (ดังที่แสดงด้านบน)

สำหรับ mandelbrot เรามีไฟล์ต้นฉบับเพียง 1 ไฟล์ แต่โดยทั่วไปโปรเจ็กต์จะมีขนาดใหญ่กว่านี้และมีมากกว่า 1 ไฟล์ ฟิชชันสำหรับการแก้ไขข้อบกพร่องจะสร้างไฟล์ .dwo สำหรับไฟล์แต่ละรายการ เพื่อให้โปรแกรมแก้ไขข้อบกพร่องรุ่นเบต้าปัจจุบัน (0.1.6.1615) โหลดข้อมูลการแก้ไขข้อบกพร่องแบบแยกได้ เราต้องรวมรายการเหล่านี้ทั้งหมดเข้าด้วยกันเป็นแพ็กเกจ DWARF (.dwp) ดังตัวอย่างนี้

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

รวมไฟล์ dwo เป็นแพ็กเกจ DWARF

การสร้างแพ็กเกจ DWARF จากออบเจ็กต์เดี่ยวมีข้อดีคือคุณต้องแสดงไฟล์เพิ่มเติมเพียง 1 ไฟล์เท่านั้น ขณะนี้เรากำลังดำเนินการโหลดออบเจ็กต์แต่ละรายการทั้งหมดในรุ่นที่จะออกในอนาคต

DWARF 5 มีอะไรใหม่

คุณอาจสังเกตว่าเราดัก Flag อีกรายการหนึ่งในคำสั่ง emcc ด้านบน -gdwarf-5 การเปิดใช้สัญลักษณ์ DWARF เวอร์ชัน 5 ซึ่งปัจจุบันไม่ใช่ค่าเริ่มต้นเป็นอีกเคล็ดลับหนึ่งที่ช่วยให้เราเริ่มแก้ไขข้อบกพร่องได้เร็วขึ้น ด้วยการทำเช่นนี้ ข้อมูลบางอย่างจะจัดเก็บไว้ในไบนารีหลักที่เวอร์ชัน 4 เริ่มต้นให้ไว้ กล่าวอย่างเจาะจงคือ เราจะระบุไฟล์ต้นฉบับทั้งชุดได้จากไบนารีหลักเท่านั้น วิธีนี้ช่วยให้โปรแกรมแก้ไขข้อบกพร่องดำเนินการพื้นฐานได้ เช่น แสดงแผนผังต้นทางแบบเต็ม และตั้งค่าเบรกพอยท์โดยไม่ต้องโหลดและแยกวิเคราะห์ข้อมูลสัญลักษณ์แบบเต็ม วิธีนี้ช่วยให้แก้ไขข้อบกพร่องด้วยสัญลักษณ์การแยกได้เร็วขึ้นมาก เราจึงใช้แฟล็กบรรทัดคำสั่ง -gsplit-dwarf และ -gdwarf-5 ร่วมกันเสมอ

รูปแบบการแก้ไขข้อบกพร่อง DWARF5 ยังทำให้เราเข้าถึงฟีเจอร์ที่มีประโยชน์ได้ด้วย ระบบจะแนะนำดัชนีชื่อในข้อมูลการแก้ไขข้อบกพร่องซึ่งระบบจะสร้างขึ้นเมื่อมีการส่งแฟล็ก -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

ในระหว่างเซสชันการแก้ไขข้อบกพร่อง การค้นหาสัญลักษณ์มักเกิดขึ้นด้วยการค้นหาเอนทิตีตามชื่อ เช่น เมื่อมองหาตัวแปรหรือประเภท ดัชนีชื่อช่วยเร่งการค้นหานี้โดยชี้ไปที่หน่วยรวบรวมที่กำหนดชื่อนั้นโดยตรง หากไม่มีดัชนีชื่อ จะต้องมีการค้นหาข้อมูลการแก้ไขข้อบกพร่องอย่างละเอียดเพื่อค้นหาหน่วยรวบรวมที่ถูกต้องซึ่งระบุเอนทิตีที่มีชื่อที่เรากำลังมองหา

หากมีข้อสงสัย: ดูข้อมูลการแก้ไขข้อบกพร่อง

คุณใช้ llvm-dwarfdump เพื่อดูข้อมูล DWARF ได้ เรามาลองดูกัน

llvm-dwarfdump mandelbrot.wasm

ซึ่งทำให้เราเห็นภาพรวมเกี่ยวกับ "หน่วยคอมไพล์" (หรือเรียกสั้นๆ ว่าไฟล์ต้นฉบับ) ที่เรามีข้อมูลการแก้ไขข้อบกพร่อง ในตัวอย่างนี้ เรามีข้อมูลการแก้ไขข้อบกพร่องสำหรับ mandelbrot.cc เท่านั้น ข้อมูลทั่วไปจะช่วยให้เราทราบว่าเรามีโครงกระดูก ซึ่งหมายความว่าเรามีข้อมูลไม่สมบูรณ์ในไฟล์นี้ และมีไฟล์ .dwo แยกต่างหากซึ่งมีข้อมูลการแก้ไขข้อบกพร่องที่เหลือ ดังนี้

mandelbrot.wasm และข้อมูลการแก้ไขข้อบกพร่อง

คุณยังสามารถดูตารางอื่นๆ ภายในไฟล์นี้ได้ด้วย เช่น ตารางบรรทัดซึ่งแสดงการแมปไบต์โค้ด Wasm กับบรรทัด C++ (ลองใช้ llvm-dwarfdump -debug-line)

เรายังดูข้อมูลการแก้ไขข้อบกพร่องที่อยู่ในไฟล์ .dwo ที่แยกต่างหากได้อีกด้วย โดยทำดังนี้

llvm-dwarfdump mandelbrot.dwo

mandelbrot.wasm และข้อมูลการแก้ไขข้อบกพร่อง

TL;DR: การใช้การแยกแก้ไขข้อบกพร่องมีข้อดีอย่างไร

การแยกข้อมูลการแก้ไขข้อบกพร่องมีข้อดีหลายอย่างหากทำงานกับแอปพลิเคชันขนาดใหญ่

  1. การลิงก์ที่เร็วขึ้น: Linker ไม่จำเป็นต้องแยกวิเคราะห์ข้อมูลการแก้ไขข้อบกพร่องทั้งหมดอีกต่อไป โดยปกติแล้ว Linker จะต้องแยกวิเคราะห์ข้อมูล DWARF ทั้งหมดที่อยู่ในไบนารี ตัวลิงก์จะจัดการกับไบนารีขนาดเล็กซึ่งส่งผลให้ใช้เวลาในการลิงก์เร็วขึ้น (โดยเฉพาะอย่างยิ่งสำหรับแอปพลิเคชันขนาดใหญ่) โดยการนำข้อมูลการแก้ไขข้อบกพร่องส่วนใหญ่ออกเป็นไฟล์แยกต่างหาก

  2. แก้ไขข้อบกพร่องได้เร็วขึ้น: โปรแกรมแก้ไขข้อบกพร่องจะข้ามการแยกวิเคราะห์สัญลักษณ์เพิ่มเติมในไฟล์ .dwo/.dwp สําหรับการค้นหาสัญลักษณ์บางอย่าง สำหรับการค้นหาบางอย่าง (เช่น คำขอในการแมปบรรทัดของไฟล์ Wasm-to-C++) เราไม่จำเป็นต้องดูข้อมูลการแก้ไขข้อบกพร่องเพิ่มเติม วิธีนี้ช่วยให้เราประหยัดเวลา โดยไม่จำเป็นต้องโหลดและแยกวิเคราะห์ข้อมูลการแก้ไขข้อบกพร่องเพิ่มเติม

1: หากไม่มี llvm-objdump เวอร์ชันล่าสุดในระบบ และคุณใช้ emsdk คุณสามารถค้นหาได้ในไดเรกทอรี emsdk/upstream/bin

ดาวน์โหลดช่องตัวอย่าง

ลองใช้ Chrome Canary, Dev หรือเบต้าเป็นเบราว์เซอร์สำหรับการพัฒนาเริ่มต้น ตัวอย่างช่องทางเหล่านี้จะช่วยให้คุณสามารถเข้าถึงฟีเจอร์ล่าสุดของเครื่องมือสำหรับนักพัฒนาเว็บ ทดสอบ API แพลตฟอร์มเว็บที่ล้ำสมัย และค้นหาปัญหาในเว็บไซต์ก่อนที่ผู้ใช้จะทำงานได้

ติดต่อทีมเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome

ใช้ตัวเลือกต่อไปนี้เพื่อพูดคุยเกี่ยวกับฟีเจอร์ใหม่ๆ และการเปลี่ยนแปลงในโพสต์หรือเรื่องอื่นๆ ที่เกี่ยวข้องกับเครื่องมือสำหรับนักพัฒนาเว็บ

  • ส่งคำแนะนำหรือความคิดเห็นถึงเราผ่าน crbug.com
  • รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บโดยใช้ตัวเลือกเพิ่มเติม   เพิ่มเติม   > ความช่วยเหลือ > รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บในเครื่องมือสำหรับนักพัฒนาเว็บ
  • ทวีตที่ @ChromeDevTools
  • แสดงความคิดเห็นเกี่ยวกับ "มีอะไรใหม่ในวิดีโอ YouTube สำหรับเครื่องมือสำหรับนักพัฒนาเว็บ" หรือเคล็ดลับเครื่องมือสำหรับนักพัฒนาเว็บวิดีโอ YouTube