问题描述: 请解释 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 暂时缺乏实用价值。