WebAssembly可以使您的网络应用程序更快吗?
#javascript #c #webassembly

什么是WebAssembly?

集会?

众所周知,机器读取二进制代码(零和元素),因为它对它们非常方便,但对人类来说不是。因此,工程师基本上创建了二进制代码的映射,并将映射版本称为“ assembly”。

Assembly and binary code

WebAssembly?

WebAssembly是一种类似“汇编”的语言。它的昵称是 wasm 。基本上WebAssembly是现代浏览器的汇编语言。

人们通常不会直接编写汇编代码,因为它可能很乏味。这就是为什么有许多装配编译器只需从C/C++,C#,Rust,Java创建汇编代码...因此,基本上您可以在浏览器内运行现有的C/C ++或Rust应用程序。

WebAssembly代码可以直接注入JavaScript或Node.js环境中。这可能非常有用,因为您可以将WebAssembly与JavaScript结合在一起。

WebAssembly几乎提供本地性能。 JavaScript已编译(JIT)。但是WebAssembly是预编译的。因此,这就是为什么WASM可以比JavaScript代码更快地执行WASM。

弄脏你的手

首先,本文中使用的所有源代码在GitHub中可用。我脑海中最终的问题是“我们可以用websembly更快地对整数排序?” 。由于我们想更快地执行,因此我认为我应该使用圣编程语言 c
为了编译WebAssembly,我使​​用了emscripten我编写了C代码并将C代码编译为使用Emscripten的WebAssembly。我只是在MDNemscripten

上遵循指示。

你好世界!

通过遵循其文档下载并安装Emscripten后,您应该有一个emsdk文件夹。进入该文件夹并运行source ./emsdk_env.sh。现在,您可以将C代码编译为WebAssembly。为了编译Hello World WebAssembly代码,我使用了Command emcc -o hello3.html hello3.c --shell-file html_template/shell_minimal2.html -s NO_EXIT_RUNTIME=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']"。在这里,emcc是Emscripten编译器。 -o hello3.html hello3.c表示编译“ Hello3.3c”,输出HTML文件'Hello3.html'。我的“ hello3.3c”就像下面

#include <stdio.h>
#include <emscripten/emscripten.h>

int main() {
    printf("Hello World\n");
    return 0;
}

#ifdef __cplusplus
#define EXTERN extern "C"
#else
#define EXTERN
#endif

EXTERN EMSCRIPTEN_KEEPALIVE void myFunction(int argc, char ** argv) {
    printf("MyFunction Called\n");
}

加载网页时,它只是将“ Hello World”打印到开发人员控制台。并在调用C函数时也打印“称为“称为”的myfunction。

该脚本的

--shell-file html_template/shell_minimal2.html基本上使用模板html文件来生成输出“ hello3.html”文件。我的“ shell_minimal2.html”如下。

<html>
  <body>
    <button id="mybutton">Call the C function</button>
  </body>
</html>
{{{ SCRIPT }}}
<script>
  document.getElementById("mybutton").addEventListener("click", () => {
    alert("check console");
    const result = Module.ccall(
      "myFunction", // name of C function
      null, // return type
      null, // argument types
      null // arguments
    );
  });
</script>

使用某种称为ccal的特殊方法调用该函数的其余部分是必要的。如果执行命令,它将生成“ Hello3.html”,“ Hello3.js”和“ Hello3.wasm”文件。如果您查看“ Hello3.html”,您将看到

<html>
  <body>
    <button id="mybutton">Call the C function</button>
  </body>
</html>
<script async type="text/javascript" src="hello3.js"></script>
<script>
  document.getElementById("mybutton").addEventListener("click", () => {
    alert("check console");
    const result = Module.ccall(
      "myFunction", // name of C function
      null, // return type
      null, // argument types
      null // arguments
    );
  });
</script>

模板文件'shell_minimal2.html'和'hello3.html'之间的唯一区别是{{{ SCRIPT }}}部分。在“ hello3.html”中,该部分被<script async type="text/javascript" src="hello3.js">'hello3.js'替换为一个JavaScript文件,它提供了一个名为Module的全局变量,还导入了hello3.wasmhello3.wasm是一个二进制文件。您不能轻松地用肉眼阅读它。启动HTTP服务器并在现代浏览器中打开“ Hello3.html”。我正在使用VSCODE进行代码编辑,并且它具有简单HTTP服务器的a nice extension。然后,您实际上可以看到WASM文件已转换为浏览器中的WebAssembly文本(.wat)格式。
WebAssembly Text representation in browser
在开发人员控制台中,您可以看到printf C码的语句实际上是打印的。这是WebAssembly的Hello World!另外,如果您单击该按钮,您将看到它调用C函数!太棒了!从JavaScript我们可以调用C函数!
Image description

斐波那契

现在,让我们看看C代码是否可以比普通JavaScript代码更快执行。为了进行比较,我在C和JavaScript中以非常低效的方式实现了斐波那契算法,并并排执行。

我执行命令,就像Hello World示例emcc -o hello4.html fib.c --shell-file html_template/simple_compare.html -sEXPORTED_FUNCTIONS=_fib -sEXPORTED_RUNTIME_METHODS=cwrap一样。在这里调用C函数,我们使用其他一些特殊方法cwrap
以下是我的C代码文件'fib.c'

int fib(int n)
{
    if (n < 2)
        return n;
    return fib(n - 1) + fib(n - 2);
}

这是我的'simple_compare.html'文件

{{{ SCRIPT }}}
<input type="number" id="fibNum" value="35" />
<button id="mybutton">Compare JS vs C on Fibonacci</button>
<script>
  document.getElementById("mybutton").addEventListener("click", () => {
    const fibIndex = Number(document.getElementById("fibNum").value);
    const t1 = executeFibonacciOnC(fibIndex);
    const t2 = executeFibonacciOnJS(fibIndex);
    console.log("C time:", t1, "JS time:", t2);
  });

  // finds the 'n'th fibonacci number in C using the worst implementation and returns the execution time
  function executeFibonacciOnC(n) {
    fibC = Module.cwrap("fib", "number", ["number"]);
    const t1 = performance.now();
    const res = fibC(n);
    const t2 = performance.now();
    console.log("c result: ", res);
    return t2 - t1;
  }

  function executeFibonacciOnJS(n) {
    const t1 = performance.now();
    const res = fib(n);
    const t2 = performance.now();
    console.log("JS result: ", res);
    return t2 - t1;
  }

  function fib(n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
  }
</script>

在这里,您可以看到JavaScript比C代码快很多。我觉得自己一直浪费了时间,WebAssembly只是一个气球。
C vs JS on Fibonacci calculation without any compiler optimizations

然后我意识到Emscripten中有一些编译器优化标志。让我们使用它们emcc -o hello4.html fib.c --shell-file html_template/simple_compare.html -sEXPORTED_FUNCTIONS=_fib -sEXPORTED_RUNTIME_METHODS=cwrap -O2我刚刚在我的命令中添加了一个-O2标志,然后再做一次。

现在您可以看到一些魔术!
C vs JS on Fibonacci calculation with O2 compiler optimization flag
这次,C代码执行更快!在某些情况下,它甚至快2倍!

排序数字

现在让我们尝试我们的最终目标。我们可以比JavaScript更快地对数字进行排序吗?让我们尝试使用C标准库功能qsort我认为应该更快,C代码通常很快。与以前的命令类似,我执行了命令emcc -o qsort.html arraySorter.c --shell-file html_template/simple_compare_array.html -sEXPORTED_FUNCTIONS=_arraySorter,_malloc,_free -sEXPORTED_RUNTIME_METHODS=cwrap我的“ arraysorter.c”非常简单。它只需使用比较功能调用qsort函数。

#include <stdlib.h>

int compareFn(const void *a, const void *b)
{
    return (*(int *)a - *(int *)b);
}

void arraySorter(int *arr, int size)
{
    qsort(arr, size, sizeof(int), compareFn);
}

我的模板文件'simple_compare_array.html'如下所示。在这里传递和数组作为参数很难。我们需要使用mallocfree函数来创建和数组并将其传递给C。

{{{ SCRIPT }}}
<input type="number" id="arrSize" value="1000000" />
<button id="mybutton">Compare JS vs C on sorting integer array</button>
<script>
  function getArray() {
    const l = Number(document.getElementById("arrSize").value);
    return Array.from({ length: l }, () => Math.floor(Math.random() * l));
  }
  document.getElementById("mybutton").addEventListener("click", () => {
    const arr1 = getArray();
    const arr2 = Array.from(arr1);
    const t1 = arrayOperationsOnC(arr1);
    const t2 = arrayOperationsOnJS(arr2);
    console.log("C time:", t1, "JS time:", t2);
  });

  function arrayOperationsOnC(n) {
    const BYTE_SIZE_OF_INT = 4;
    const t1 = performance.now();
    const arraySize = n.length;
    const arrayPointer = Module._malloc(arraySize * BYTE_SIZE_OF_INT);
    Module.HEAP32.set(new Int32Array(n), arrayPointer / BYTE_SIZE_OF_INT);
    const cFunc = Module.cwrap("arraySorter", null, ["number", "number"]);
    cFunc(arrayPointer, arraySize);
    const resultArray = Array.from(
      Module.HEAP32.subarray(
        arrayPointer / BYTE_SIZE_OF_INT,
        arrayPointer / BYTE_SIZE_OF_INT + arraySize
      )
    );
    console.log(resultArray);
    Module._free(arrayPointer);
    const t2 = performance.now();
    return t2 - t1;
  }

  function arrayOperationsOnJS(n) {
    const t1 = performance.now();
    const res = arraySorter(n);
    console.log(res);
    const t2 = performance.now();
    return t2 - t1;
  }

  function arraySorter(arr) {
    return arr.sort((a, b) => a - b);
  }
</script>

如果我执行此操作,我会看到我的C代码速度慢了4倍!
到底有什么问题?

Image description

好吧,我发现我没有使用编译器优化标志。让我们尝试-O2标志,然后再做一次。现在它更快,但仍然比JavaScript慢。

c时间:716.1 JS时间:269.5

即使我使用-O3标志,我也发现它仍然较慢。没有“ -o4”这是最优化的。

c时间:711.9 JS时间:270.6

现在,我们确定使用C快速排序无法通过普通的JavaScript。但是我们使用了C标准库功能qsort。我们可以尝试简单的C代码以进行快速排序吗?咱们试试吧。我在这里使用了命令emcc -o faster_sorter.html arraySorter2.c --shell-file html_template/simple_compare_array.html -sEXPORTED_FUNCTIONS=_arraySorter,_malloc,_free -sEXPORTED_RUNTIME_METHODS=cwrap -O2,我使用了相同的HTML模板,但是这次我用普通的C代码实现了快速排序算法。下面在我的arraySorter2.c文件中。

// Quick sort in C

// function to swap elements
void swap(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

// function to find the partition position
int partition(int array[], int low, int high)
{

    // select the rightmost element as pivot
    int pivot = array[high];

    // pointer for greater element
    int i = (low - 1);

    // traverse each element of the array
    // compare them with the pivot
    for (int j = low; j < high; j++)
    {
        if (array[j] <= pivot)
        {

            // if element smaller than pivot is found
            // swap it with the greater element pointed by i
            i++;

            // swap element at i with element at j
            swap(&array[i], &array[j]);
        }
    }

    // swap the pivot element with the greater element at i
    swap(&array[i + 1], &array[high]);

    // return the partition point
    return (i + 1);
}

void quickSort(int array[], int low, int high)
{
    if (low < high)
    {

        // find the pivot element such that
        // elements smaller than pivot are on left of pivot
        // elements greater than pivot are on right of pivot
        int pi = partition(array, low, high);

        // recursive call on the left of pivot
        quickSort(array, low, pi - 1);

        // recursive call on the right of pivot
        quickSort(array, pi + 1, high);
    }
}

void arraySorter(int *arr, int size)
{
    quickSort(arr, 0, size - 1);
}

现在,使用-O2标志,我们可以比普通的javascript更快地对整数排序!现在,C代码更快的速度大约2倍。太神奇了!

c时间:156.8 JS时间:271.5

WASM在您的浏览器中更快吗? Try and see现在。所有source codes均可获得MIT许可。