WebAssembly与Go实践

1 WebAssembly简介

WebAssembly(简称Wasm)是一种可移植、高性能的二进制代码格式,可以在现代Web浏览器中运行。Wasm是一种开放的标准,由WebAssembly组织推广和维护。它旨在通过在浏览器中提供一种比JavaScript更高效和更快的运行时环境,来改进Web应用程序的性能和功能。
Wasm的设计目标是在Web浏览器中提供与本机代码相同的性能和安全性,并且它可以使用多种编程语言进行编写。Wasm可以直接从Web浏览器的内存中访问,可以与JavaScript和其他Web技术集成,可以通过WebAssembly API从JavaScript中加载和运行。此外,Wasm还支持多线程执行,这使得它在某些计算密集型应用程序中的性能优势更为明显。
Wasm可以用于加速Web应用程序、游戏、多媒体和数据处理等方面的性能,也可以作为在Web上运行的新型应用程序的构建基石。

2 JavaScript在V8引擎的运行流程

机器是不能直接理解代码的。所以,在执行程序之前,需要将代码翻译成机器能读懂的机器语言。按语言的执行流程,可以把计算机语言划分为编译型语言和解释型语言:
• 编译型语言:在代码运行前编译器直接将对应的代码转换成机器码,运行时不需要再重新翻译,直接可以使用编译后的结果;
• 解释型语言:需要将代码转换成机器码,和编译型语言的区别在于运行时需要转换。解释型语言的执行速度要慢于编译型语言,因为解释型语言每次执行都需要把源码转换一次才能执行。
Java 和 C++ 等语言都是编译型语言,而 JavaScript 是解释性语言,它整体的执行速度会略慢于编译型的语言。V8 是众多浏览器的 JS 引擎中性能表现最好的一个,并且它是 Chrome 的内核,Node.js 也是基于 V8 引擎研发的。
在V8引擎中,JavaScript代码的执行过程通常包括三个阶段:解析(Parsing)、解释(Interpretation)和编译(Compilation)。 下面分别介绍这三个阶段的执行原理。
1.解析阶段:V8引擎首先会对JavaScript代码进行语法解析,将其转换为抽象语法树(Abstract Syntax Tree,AST)。这个过程由解析器(Parser)负责完成,解析器会遍历JavaScript代码,识别语法规则,并生成对应的AST节点。解析器会根据ECMAScript标准规范来解析代码,如果代码有语法错误,解析器会抛出语法错误的异常。
2. 解释阶段:V8引擎在解析完JavaScript代码后,会使用解释器(Ignition)对生成的抽象语法树进行解释执行。解释器会按照语句的顺序依次执行,将代码转化为字节码(Bytecode)。字节码是一种类似于汇编语言的中间代码,比JavaScript代码更加低级,可以更快地执行。但是由于解释器在执行字节码时需要逐条解释执行,因此执行效率较低,适合执行一些简单的代码。
3. 编译阶段:为了提高JavaScript代码的执行效率,V8引擎会将解释执行的字节码编译为机器码(Machine Code),这个过程由优化编译器(TurboFan)负责完成。优化编译器会对字节码进行静态分析和优化,并生成机器码,机器码可以直接在CPU上执行,因此执行效率更高。优化编译器还会根据代码的运行情况动态地调整编译策略,以提高代码的执行效率。
值得注意的是,V8还有一个非常重要的特性是垃圾回收器(Garbage Collector,GC),它会定期扫描内存中的对象,回收不再使用的内存,以减少内存泄漏和性能问题。垃圾回收器是V8引擎的重要组成部分,也是JavaScript代码能够高效运行的关键之一。
总的来说,V8引擎使用解析器将JavaScript代码解析成抽象语法树和字节码,再通过编译器将字节码转换成本机机器码,最终由执行器执行本机机器码,完成JavaScript代码的运行。
而wasm并不需要以上的全部步骤,wasm 在编译阶段就已经通过了代码优化,解析工作也不需要了,可以直接执行优化过后的二进制代码。

3 wasm的优势

  • 高性能:Wasm是一种经过优化的二进制格式,可以在现代Web浏览器中快速加载和执行,具有比JavaScript更快的运行速度。
  • 跨平台支持:Wasm可以在各种操作系统和设备上运行,包括桌面、移动设备和服务器等,无需针对每个平台进行编译或打包。
  • 多语言支持:Wasm支持多种编程语言,包括C/C++、Rust、Go、Python、Java等,开发者可以使用自己最熟悉的语言来编写Wasm应用程序。
  • 安全性:由于Wasm在Web浏览器中运行,因此它可以受到Web平台的安全限制和隔离,同时也不需要信任用户计算机上的软件环境。
  • 与Web技术的集成:Wasm可以与JavaScript和其他Web技术集成,可以通过WebAssembly API从JavaScript中加载和运行,还可以访问浏览器提供的Web API。
  • 可重用性:Wasm可以在多个Web应用程序之间共享和重用,这样可以减少代码的复制和维护成本。
  • 开放标准:Wasm是由WebAssembly组织制定和推广的开放标准,具有透明度、互操作性和可移植性等优势。
    Wasm的性能更高主要有以下原因
  • 二进制格式:Wasm使用二进制格式,这种格式相比于文本格式的JavaScript代码更加紧凑,可以更快地进行加载和解析。
  • 直接编译成机器码:Wasm可以直接编译成机器码,而JavaScript需要在浏览器中进行解释执行。机器码是计算机可以直接执行的代码,而解释执行需要解析和执行JavaScript代码,这两者的效率差异较大。
  • 更优化的代码:Wasm的代码生成器可以针对特定的架构和平台生成更优化的机器码,而JavaScript引擎只能在运行时根据代码执行情况进行优化。
  • 预编译:Wasm可以预编译,这意味着代码可以在Web应用程序加载之前编译,从而减少了启动时间和加载时间。
  • 线程支持:Wasm支持多线程执行,这使得它在某些计算密集型应用程序中的性能优势更为明显。
    💡 这些优势的本质原因在于WebAssembly的设计初衷是为了在Web浏览器中提供更高效、更安全、更灵活的编程能力。相比之下,JavaScript是一种高级编程语言,它的设计目的是为了简化Web应用程序的开发,但在性能和安全方面存在一些局限性。WebAssembly的出现,弥补了JavaScript的一些不足,为Web应用程序提供了更广阔的发展空间。

4 wasm的缺陷

虽然 WebAssembly(wasm)具有许多优点,例如比 JavaScript 更快的执行速度,更小的文件大小等等,但它仍然有一些缺陷:

1 无法直接访问 DOM:WebAssembly 并不直接支持访问 DOM,它只是一个类似于汇编语言的底层字节码,无法直接与浏览器交互。因此,如果需要从 WebAssembly 中访问浏览器的 DOM API,需要通过 JavaScript 提供的特定接口来实现。
2. 缺乏标准库:WebAssembly 并没有自己的标准库,而是依赖于宿主语言(如 JavaScript 或 Rust)提供的标准库。这就导致了在不同的宿主语言中使用 WebAssembly 时,需要自行处理一些细节问题。
3. 调试困难:由于 WebAssembly 是一种二进制格式的字节码,与 JavaScript 相比,其调试起来更加困难。因为 JavaScript 的源代码可以很方便地被浏览器中的开发者工具调试,而 WebAssembly 的字节码需要通过特定的工具进行解析和调试。
4. 无法直接处理字符串:WebAssembly 并不直接支持字符串类型,而是使用了自己的线性内存(Linear Memory)来表示字符串。因此,在 WebAssembly 中处理字符串需要进行一定的手动操作,使得开发过程更加繁琐。
5. 无法动态创建函数:WebAssembly 并不支持动态创建函数,这就限制了其在某些场景下的灵活性。因为在一些应用中,动态创建函数是非常常见的操作,这就导致了 WebAssembly 在这些场景下的应用受到一定的限制。

5 go实践

官方wiki指南:https://github.com/golang/go/wiki/WebAssembly
Go编译器官方已支持将代码编译为wasm模块,无需借助第三方编译工具。

5.1 入门demo

根据官方wiki指南,复现一个最简单的入门demo。
以下demo是在window是执行的:
初始化项目

go mod init myapp 新建一个项目
首先完成一个go函数,打印Hello, WebAssembly!,新建一个hello

package main

import "fmt"

func main() {
    fmt.Println("Hello, WebAssembly!")
}

配置环境变量,并将编写好的go程序编译成wasm格式
go env -w GOOS=js
go env -w GOARCH=wasm
go build -o main.wasm获取go官方准备的js胶水文件:
直接下载(https://raw.githubusercontent.com/golang/go/master/misc/wasm/wasm_exec.js),将文件复制到您的项目目录中,然后创建一个html文件,引入wasm
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>My WebAssembly App</title>
    <script src="./wasm_exec.js"></script>
    <script>
      const go = new Go()
      const importObject = go.importObject
      WebAssembly.instantiateStreaming(fetch('http://127.0.0.1:5500/main.wasm'), importObject).then(
        result => {
          go.run(result.instance)
        }
      )
    </script>
  </head>
  <body></body>
</html>

fetch(‘./main.wasm’)会报错跨域,所以转了一层,fetch(‘http://127.0.0.1:5500/main.wasm’) 本地启动一个live sever。

5.2 实践:n的阶乘

要在 WebAssembly 模块中对外暴露 Go 函数,可以使用 Go 的 syscall/js 包提供的 js.Global().Set() 方法。该方法允许在 JavaScript 中创建一个函数对象,并将其绑定到一个特定的名称上,以便在 JavaScript 中调用。
以下是一个示例代码,演示如何将 factorial 函数绑定到 WebAssembly 模块中的 factorial 全局变量上:
package main

import (
    "syscall/js"
)

func factorial(n int) int {
    if n == 0 {
        return 1
    }
    return n * factorial(n-1)
}

func main() {
    js.Global().Set("factorial", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        n := args[0].Int()
        return factorial(n)
    }))

    select {}
}

在此示例中,使用了 js.FuncOf() 方法来创建一个新的 JavaScript 函数对象,并将其绑定到全局变量 factorial 上。该函数接受一个整数参数 n,并返回其阶乘。
注意,在 main() 函数中使用了 select {},这是因为 WebAssembly 程序需要保持运行状态,以便可以被 JavaScript 调用。如果在主函数中没有这个代码,WebAssembly 将在调用后立即终止。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>My WebAssembly App</title>
    <script src="./wasm_exec.js"></script>
    <script>
      function factorial(n) {
        if (n === 0) {
          return 1
        }
        return n * factorial(n - 1)
      }
      const main = async () => {
        const go = new Go()
        const importObject = go.importObject
        const { instance } = await WebAssembly.instantiateStreaming(
          fetch('http://127.0.0.1:5500/main.wasm'),
          importObject
        )
        go.run(instance)
        const wasmFactorial = new Function('n', 'return factorial(n);')
        const t0 = performance.now()
        let wasmResult = 0
        for (let i = 0; i < 100000; i++) {
          wasmResult += wasmFactorial(30)
        }
        const t1 = performance.now()
        console.log(`WebAssembly Result: ${wasmResult}. Time taken: ${t1 - t0} milliseconds.`)
        let jsResult = 0
        const t2 = performance.now()
        for (let i = 0; i < 100000; i++) {
          jsResult += factorial(30)
        }
        const t3 = performance.now()
        console.log(`JavaScript Result: ${jsResult}. Time taken: ${t3 - t2} milliseconds.`)
      }
      main()
    </script>
  </head>
  <body></body>
</html>

用了 new Function() 构造函数来创建一个在 WebAssembly 实例中调用 factorial() 函数的 JavaScript 函数 wasmFactorial()。然后,在代码的结尾,分别计算了在 JavaScript 中和在 WebAssembly 实例中调用 factorial() 函数的结果,并将结果打印到控制台上。
WebAssembly 的性能应该是比 JavaScript 更好的
,但每次运行的结果不一样,有时候WebAssembly性能更差。可能是因为一些其他因素导致 WebAssembly 的性能变差了。
有几个可能的原因:
• 浏览器中 WebAssembly 运行缓慢:不同浏览器的 WebAssembly 性能有所不同,因此您可以尝试在其他浏览器中运行此代码。
• 浏览器中 WebAssembly 的编译时间:第一次编译 WebAssembly 模块可能需要更长时间,但是在接下来的运行中性能会得到改善。
• 额外的 WebAssembly 模块加载时间:如果模块非常大,则可能需要更长的时间才能加载模块。如果模块非常小,则 JavaScript 可能会更快。
• JavaScript 代码可能被 JIT 编译:对于小型函数,JavaScript 引擎可能会对其进行 Just-In-Time(JIT)编译,这意味着它会被直接编译为机器代码,并且速度会很快。

5.3 实践:矩阵乘法运算

考虑使用涉及到大量计算的复杂函数进行比较,这样可以更好地展示 WebAssembly 的性能优势,尤其是在数据密集型应用中
。尝试比较在 JS 和 WebAssembly 中进行相同数量的矩阵乘法运算所需的时间,因为矩阵乘法是一种计算密集型的操作。

package main

import (
    "syscall/js"
)

func multiplyMatrices(this js.Value, args []js.Value) interface{} {
    a := args[0]
    b := args[1]
    rowsA := a.Length()
    rowsB := b.Length()
    colsB := b.Index(0).Length()
    c := make([][]float64, rowsA)
    for i := 0; i < rowsA; i++ {
        c[i] = make([]float64, colsB)
    }
    for k := 0; k < colsB; k++ {
        for i := 0; i < rowsA; i++ {
            temp := 0.0
            for j := 0; j < rowsB; j++ {
                temp += a.Index(i).Index(j).Float() * b.Index(j).Index(k).Float()
            }
            c[i][k] = temp
        }
    }
    return js.ValueOf(c)
}

func main() {
    js.Global().Set("multiplyMatrices", js.FuncOf(multiplyMatrices))
    select {}
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>My WebAssembly App</title>
    <script src="./wasm_exec.js"></script>
    <script>
      function multiplyMatrices(a, b) {
        const rowsA = a.length
        const rowsB = b.length
        const colsB = b[0].length
        const c = new Array(rowsA)
        for (let i = 0; i < rowsA; i++) {
          c[i] = new Array(colsB)
        }
        for (let k = 0; k < colsB; k++) {
          for (let i = 0; i < rowsA; i++) {
            let temp = 0
            for (let j = 0; j < rowsB; j++) {
              temp += a[i][j] * b[j][k]
            }
            c[i][k] = temp
          }
        }
        return c
      }
      function generateMatrix(rows, cols) {
        const matrix = new Array(rows)
        for (let i = 0; i < rows; i++) {
          matrix[i] = new Array(cols)
          for (let j = 0; j < cols; j++) {
            matrix[i][j] = Math.random()
          }
        }
        return matrix
      }
      const main = async () => {
        const go = new Go()
        const importObject = go.importObject
        const { instance } = await WebAssembly.instantiateStreaming(
          fetch('http://127.0.0.1:5500/main.wasm'),
          importObject
        )
        // go.run(instance)
        const wasmMultiplyMatrices = new Function('a', 'b', 'return multiplyMatrices(a, b);')
        // const wasmMultiplyMatrices = instance.exports.multiplyMatrices
        const rowsA = 5000
        const colsA = 200
        const rowsB = 200
        const colsB = 1500
        const a = generateMatrix(rowsA, colsA)
        const b = generateMatrix(rowsB, colsB)

        let wasmResult
        const t0 = performance.now()
        // for (let i = 0; i < 1000; i++) {
        // wasmResult = wasmMultiplyMatrices(a, b)
        wasmMultiplyMatrices(a, b)
        // }
        const t1 = performance.now()
        console.log(
          `WebAssembly Result: ${JSON.stringify(wasmResult)}. Time taken: ${t1 - t0} milliseconds.`
        )
        let jsResult
        const t2 = performance.now()
        // for (let i = 0; i < 1000; i++) {
        // jsResult = multiplyMatrices(a, b)
        multiplyMatrices(a, b)
        // }
        const t3 = performance.now()
        console.log(
          `JavaScript Result: ${JSON.stringify(jsResult)}. Time taken: ${t3 - t2} milliseconds.`
        )
      }
      main()
    </script>
  </head>
  <body></body>
</html>

结果是性能差不多,WebAssembly甚至可能更差一点

6 总结

使用WebAssembly需要结合使用场景而不是一股脑使用,以下场景中WebAssembly更具优势:
1.Web前端应用:Wasm 可以让 Web 前端应用获得本地应用程序一样的高性能和丰富的功能,比如图像处理、游戏、音视频编辑等。Wasm 可以直接在浏览器中运行,而无需下载额外的插件或应用程序。
2. 后端服务器:Wasm 也可以用于后端服务器应用程序,通过 WebAssembly 运行时可以在服务器上运行一些功能的编译代码,比如图像处理、视频编解码、加密解密等。
3. 移动应用程序:Wasm 也可以用于移动应用程序的开发。开发者可以通过编写 C/C++ 代码并编译成 WebAssembly 格式,然后使用现有的移动应用程序框架(如 React Native)或自己的框架来将这些 WebAssembly 模块集成到移动应用程序中。
4. 桌面应用程序:通过将 WebAssembly 应用程序包装在桌面应用程序中,可以创建具有本地应用程序的性能和功能的跨平台桌面应用程序。
5. 机器学习:Wasm 还可以用于机器学习应用程序的开发,比如 TensorFlow.js 就是将 TensorFlow 的计算图编译成 WebAssembly 格式,然后在浏览器中运行。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebAssembly是一种可移植、高性能的二进制格式,用于在Web浏览器中运行代码。它是由W3C(World Wide Web Consortium)制定的一项标准,旨在提供一种在Web平台上运行高性能应用程序的方式。 WebAssembly的主要特点包括: 1. 性能优越:WebAssembly的二进制格式可以在浏览器中快速加载和执行,比传统的JavaScript执行速度更快。 2. 跨平台:WebAssembly可以在不同的操作系统和硬件架构上运行,使得开发者可以编写一次代码,然后在多个平台上运行。 3. 安全性:WebAssembly的代码在沙箱环境中运行,可以提供更高的安全性,防止恶意代码对用户设备的攻击。 4. 与现有技术的兼容性:WebAssembly可以与JavaScript和其他Web技术无缝集成,使得开发者可以利用现有的代码和工具。 使用WebAssembly,开发者可以将其他语言(如C/C++、Rust等)编译为WebAssembly代码,然后在Web浏览器中运行。这样可以实现在Web平台上运行性能更高的应用程序,例如游戏、图像处理等。 以下是两个使用WebAssembly的例子: 1. 使用Emscripten移植一个C/C++应用程序: ```c #include <stdio.h> int main() { printf("Hello, WebAssembly!"); return 0; } ``` 通过使用Emscripten编译器,可以将上述C代码编译为WebAssembly模块,并在浏览器中运行。 2. 使用Rust编写WebAssembly程序: ```rust #[no_mangle] pub extern "C" fn greet() { println!("Hello, WebAssembly!"); } ``` 通过使用Rust编译器,可以将上述Rust代码编译为WebAssembly模块,并在浏览器中调用greet函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值