[前端面试题]WebAssembly (Wasm) 的使用场景和优缺点

问题描述: 请解释 WebAssembly(Wasm)是什么,它的主要使用场景有哪些?并讨论 WebAssembly 相对于传统 JavaScript 的优缺点。

我进行了一下非常消耗算力的“高斯模糊计算”,处理图片文件44.8mb,应该是非常巨大的像素处理量了。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Gaussian Blur with JavaScript</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script>
      function gaussianBlur(imageData, radius) {
        const width = imageData.width;
        const height = imageData.height;
        const data = imageData.data;
        const kernel = getGaussianKernel(radius);

        const tempData = new Uint8ClampedArray(data);

        // Horizontal blur
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            applyKernel(tempData, data, kernel, x, y, width, height, true);
          }
        }

        // Vertical blur
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            applyKernel(tempData, data, kernel, x, y, width, height, false);
          }
        }

        return new ImageData(data, width, height);
      }

      function getGaussianKernel(radius) {
        const kernelSize = 2 * radius + 1;
        const kernel = new Float32Array(kernelSize);
        const sigma = radius / 3;
        const norm = 1 / (Math.sqrt(2 * Math.PI) * sigma);
        const coeff = 2 * sigma * sigma;
        let sum = 0;

        for (let i = 0; i < kernelSize; i++) {
          const x = i - radius;
          kernel[i] = norm * Math.exp((-x * x) / coeff);
          sum += kernel[i];
        }

        for (let i = 0; i < kernelSize; i++) {
          kernel[i] /= sum;
        }

        return kernel;
      }

      function applyKernel(
        source,
        dest,
        kernel,
        x,
        y,
        width,
        height,
        horizontal
      ) {
        const radius = Math.floor(kernel.length / 2);
        let r = 0,
          g = 0,
          b = 0,
          a = 0;

        for (let i = -radius; i <= radius; i++) {
          const offset = horizontal ? x + i : y + i;
          const idx = horizontal
            ? (y * width + offset) * 4
            : (offset * width + x) * 4;

          if (offset >= 0 && offset < (horizontal ? width : height)) {
            r += source[idx] * kernel[i + radius];
            g += source[idx + 1] * kernel[i + radius];
            b += source[idx + 2] * kernel[i + radius];
            a += source[idx + 3] * kernel[i + radius];
          }
        }

        const destIdx = (y * width + x) * 4;
        dest[destIdx] = r;
        dest[destIdx + 1] = g;
        dest[destIdx + 2] = b;
        dest[destIdx + 3] = a;
      }

      window.onload = function () {
        const canvas = document.getElementById("canvas");
        const ctx = canvas.getContext("2d");
        const img = new Image();
        img.src = "http://alaya.zone:50001/resource/images/世界地图.jpg";
        img.setAttribute("crossOrigin", "");
        img.onload = function () {
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.drawImage(img, 0, 0);
          const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
          const startTime = performance.now();

          const blurredData = gaussianBlur(imageData, 5);

          // End timing
          const endTime = performance.now();
          console.log(`Gaussian blur took ${endTime - startTime} milliseconds`);

          ctx.putImageData(blurredData, 0, 0);
        };
      };
    </script>
  </body>
</html>

耗时:11.5秒左右

wasm

#include <iostream>
#include <vector>
#include <cmath>
#include <cstdint>
#include <chrono>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

// 计算高斯核
std::vector<float> getGaussianKernel(int radius) {
    int kernelSize = 2 * radius + 1;
    std::vector<float> kernel(kernelSize);
    float sigma = radius / 3.0;
    float norm = 1.0 / (sqrt(2 * M_PI) * sigma);
    float coeff = 2 * sigma * sigma;
    float sum = 0.0;

    for (int i = 0; i < kernelSize; ++i) {
        int x = i - radius;
        kernel[i] = norm * exp(-x * x / coeff);
        sum += kernel[i];
    }

    for (int i = 0; i < kernelSize; ++i) {
        kernel[i] /= sum;
    }

    return kernel;
}

// 应用核
void applyKernel(const std::vector<uint8_t>& source, std::vector<uint8_t>& dest, const std::vector<float>& kernel, int width, int height, int radius, bool horizontal) {
    int channelCount = 4; // 假设图像数据格式为 RGBA
    std::vector<uint8_t> tempData(source.size());
    std::copy(source.begin(), source.end(), tempData.begin());

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            float r = 0, g = 0, b = 0, a = 0;

            for (int i = -radius; i <= radius; ++i) {
                int offset = horizontal ? (x + i) : (y + i);
                int idx = horizontal
                    ? ((y * width + offset) * channelCount)
                    : ((offset * width + x) * channelCount);

                if (offset >= 0 && offset < (horizontal ? width : height)) {
                    r += tempData[idx] * kernel[i + radius];
                    g += tempData[idx + 1] * kernel[i + radius];
                    b += tempData[idx + 2] * kernel[i + radius];
                    a += tempData[idx + 3] * kernel[i + radius];
                }
            }

            int destIdx = (y * width + x) * channelCount;
            dest[destIdx] = static_cast<uint8_t>(r);
            dest[destIdx + 1] = static_cast<uint8_t>(g);
            dest[destIdx + 2] = static_cast<uint8_t>(b);
            dest[destIdx + 3] = static_cast<uint8_t>(a);
        }
    }
}

// 高斯模糊函数
void gaussianBlur(std::vector<uint8_t>& imageData, int width, int height, int radius) {
    std::vector<float> kernel = getGaussianKernel(radius);

    // 水平模糊
    applyKernel(imageData, imageData, kernel, width, height, radius, true);
    // 垂直模糊
    applyKernel(imageData, imageData, kernel, width, height, radius, false);
}

int main() {
    int width, height, channels;
    uint8_t* img = stbi_load("./input.jpg", &width, &height, &channels, 4); // 强制加载为 RGBA
    if (img == nullptr) {
        std::cerr << "Failed to load image" << std::endl;
        return -1;
    }

    std::vector<uint8_t> imageData(img, img + (width * height * 4));

    int radius = 5; // 示例半径

    // 记录开始时间
    auto start = std::chrono::high_resolution_clock::now();

    gaussianBlur(imageData, width, height, radius);

    // 记录结束时间
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> duration = end - start;

    std::cout << "Gaussian blur took " << duration.count() << " seconds." << std::endl;

    stbi_write_png("output.png", width, height, 4, imageData.data(), width * 4);

    stbi_image_free(img);
    return 0;
}

耗时:77秒

这个结果着实令人惊讶,由此看来,WebAssembly 在图像处理方面似乎失去了优势,或许是 Chrome 团队进行了相应的优化。目前,WebAssembly 仅在安全性以及调用现有的 C/C++库方面具备优势,然而,这一优势在调试和封装所面临的困难面前,似乎显得有些微不足道。所以得出的结论是,就当下而言,WebAssembly 暂时缺乏实用价值。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部