随着 Web 应用的不断发展和日益复杂化,性能优化成为了开发过程中一个不可忽视的重点。WebAssembly (Wasm) 提供了一种新的方法,通过允许在浏览器中运行编译型语言(如 C/C++)来加速 JavaScript 应用,尤其是在处理计算密集型任务时。本文将介绍如何使用 C/C++ 开发斐波那契数列计算功能,编译为 WebAssembly,并在前端 JavaScript 中进行调用和性能对比。
什么是 WebAssembly?
WebAssembly 是一种新的代码格式,旨在为现代网络应用提供新的编码方式,使得代码可以以接近原生的速度执行。它是一种低级语言,既可以作为目标语言直接手动编写,也可以作为编译目标,使得你可以使用如 C、C++ 等高级语言编写应用逻辑。
环境准备
在开始之前,确保你的开发环境中已安装以下工具:
- Emscripten: 一个编译器工具链,可以将 C/C++ 代码编译成 WebAssembly。
- Node.js: 用于运行 JavaScript 代码。
配置emcc全局环境
如果不配置全局环境变量,每次新开命令行窗口使用 emsdk 工具时,都需要执行一次 source ./emsdk_env.sh
。配置环境变量后在任何窗口都可以使用,比较方便。
打开环境变量配置文件:
vim ~/.zshrc
在后面加入如下信息,即 emsdk 和 emscripten 的安装目录加到 path 中。
export EMSDK_PATH="/Users/path/to/emsdk"
export EMSCRIPTEN_PATH="/Users/path/to/emsdk/upstream/emscripten"
export PATH=$PATH:${EMSDK_PATH}
export PATH=$PATH:${EMSCRIPTEN_PATH}
步骤 1: 编写 C/C++ 代码
首先,我们创建一个 C++ 程序来计算斐波那契数列:
// fibonacci.cpp
extern "C"
{
int fibonacci(int n)
{
if (n <= 1)
return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
步骤 2: 编译为 WebAssembly
使用 Emscripten 编译器将上述 C++ 代码编译为 WebAssembly:
emcc fibonacci.cpp -s WASM=1 -o fibonacci.js
# emcc -o fibonacci.js fibonacci.cpp -s EXPORTED_FUNCTIONS='["_fibonacci"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]'
这条命令将生成两个文件:fibonacci.js
(加载和初始化 WebAssembly 模块的 "胶水" 代码)和 fibonacci.wasm
(包含编译后的 WebAssembly 代码)。
步骤 3: JavaScript 实现对比
为了对比性能,我们同样实现 JavaScript 版本的斐波那契数列计算:
function fibonacciJS(n) {
if (n <= 1) return n;
return fibonacciJS(n - 1) + fibonacciJS(n - 2);
}
步骤 4: 在网页中使用 WebAssembly
创建一个简单的 HTML 页面来加载我们的 JavaScript 代码,并测试两种实现的性能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fibonacci Calculator</title>
</head>
<body>
<script src="fibonacci.js"></script>
<script>
// 当 WebAssembly 模块加载完成
Module.onRuntimeInitialized = async function() {
const n = 40; // 斐波那契数列计算项数
console.time('Fibonacci JavaScript');
console.log('Fibonacci JS Result:', fibonacciJS(n));
console.timeEnd('Fibonacci JavaScript');
console.time('Fibonacci WebAssembly');
console.log('Fibonacci WASM Result:', Module._fibonacci(n));
console.timeEnd('Fibonacci WebAssembly');
};
</script>
</body>
</html>
性能对比与总结
打开这个 HTML 文件后,你可以在浏览器的控制台看到两种方法执行的时间。通过比较,你将发现在执行复杂的递归计算任务时,WebAssembly 版本通常比 JavaScript 版本有更优的性能表现。
通过这个例子,我们可以看到 WebAssembly 在前端开发中的潜力,尤其是在处理那些计算密集型的任务时。这为前端应用带来了新的可能性,开发者可以利用现有的 C/C++ 代码库,提升应用性能,优化用户体验。
Demo
extern "C"
{
int fibonacci(int n)
{
// 循环求自然数从0到n的和
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
return sum;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fibonacci Calculator</title>
</head>
<body>
<script>
// 异步加载 WebAssembly 模块并执行
async function loadAndExecuteWasm() {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.instantiate(buffer);
const result = module.instance.exports.fibonacci(9999999999); // Wasm 模块有一个名为 fibonacci 的函数 (与cpp中的命名相同)
}
// 循环求自然数从0到n的和
function sumOfNaturalNumbersJS(n) {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
// 页面加载后执行 JavaScript 和 WebAssembly 比较
window.onload = async function() {
const n = 9999999999;
console.time('JavaScript');
sumOfNaturalNumbersJS(n)
console.timeEnd('JavaScript');
console.time('WebAssembly');
await loadAndExecuteWasm();
console.timeEnd('WebAssembly');
};
</script>
</body>
</html>
# e.g. output:
JavaScript: 9946.489013671875 ms
WebAssembly: 4198.52587890625 ms
Comments NOTHING