前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >借助 WASM 进行密集计算:入门篇

借助 WASM 进行密集计算:入门篇

作者头像
soulteary
发布2021-12-05 10:55:53
1.1K0
发布2021-12-05 10:55:53
举报

在《使用 Docker 和 Golang 快速上手 WebAssembly》一文中,我介绍了如何制作符合 WASI 接口标准的通用 WASM,以及如何在几种不同的场景下进行程序调用。

本篇文章将延续前文,聊聊在如何借助 WASM 增强 Golang、Node.js ,进行更高效的密集计算。

写在前面

或许有读者会说,既然使用 Golang 又何必使用 Node.js,差不多的代码实现场景,两者性能根本不在一个数量级,比较起来不够客观。但其实,完全没有必要做这样的语言对立,除了两种语言的绝对的性能区别之外,或许我们也应该关注下面的事情:

在相同的计算场景下,如果分别以两种语言自身的实现方式为基准,配合 WASM 进行业务实现,是否有可能在:程序执行效率、程序分发内容安全、插件生态上产生更好的变化呢?

以及,我们可以脑洞更大一些,两种语言是否可以通过 WASM 的方式进行生态整合,让丰富的 Node / NPM 生态,灵活的 JavaScript 应用和严谨高效的 Golang 产生化学反应呢?

在开始本文之前,我先来说几个让人意外的观察结论(具体数据可以参考文末表格):

  • 如果说 Go 在使用 WASM 的模式下,普遍执行速度并不会比“原生”差多少,甚至在个别场景下,执行速度还比原生快,你是否会感到诧异?
  • 如果说原本 Node 多进程/线程的执行速度落后相同功能的 Golang 程序 200%,在不进行极端优化的场景下,仅仅是通过引入 WASM,就能让差距缩小到 100%。
  • 甚至在一些情况下,你会发现 Node + WASM 的性能和 Go 最佳的表现其实差距可以从 300% 缩短到接近 30%。

如果你觉得意外,但是又好奇,不妨跟随文章自己亲自来试一试。

测试环境中的几种程序,为了模拟最糟糕的情况,均采用 CLI 一次性触发执行的方式来收集结果,减少 Go 语言运行时的 GC 优化、或者 Node 在运行过程中的 JIT 优化,以及字节码缓存带来的有益帮助。

前置准备

在开始折腾之旅之前,我们首先需要准备环境。为了保障程序运行和测试的结果尽量客观,我们统一使用同一台机器,并且准备相同的容器环境,并将容器可以使用的 CPU 资源数量限制小一些,为宿主机预留一些资源,确保测试的机器不会出现 CPU 不足的状况。

关于构建程序使用的方式和容器环境,可以参考前一篇文章的“环境准备”,为了节约篇幅,就不多赘述了。

下面是各种依赖和组件的版本。

代码语言:javascript
复制
# go version
go version go1.17.3 linux/amd64

# node --version
v17.1.0

# wasmer --version
wasmer 2.0.0

#tinygo version
tinygo version 0.21.0 linux/amd64 (using go version go1.17.3 and LLVM version 11.0.0)

准备 WASM 程序

为了更好的模拟和进行结果对比,我们需要一个计算时间比较久的“活儿”,“斐波那契数列”就是一个不错的选择,尤其是没有进行动态规划优化的“基础版”。

约定模拟密集计算的场景

这里为了方便程序性能对比,我们统一让程序针对斐波那契数列的第40位(大概1亿多的一个整数)进行快速的重复计算。

为了保证容器内的 CPU 充分使用,结果相对客观,程序一律使用“并行计算”的方式来进行数值计算。

使用 Go 编写 具备 WASI 标准接口的 WASM 程序

如果将上面的需求进行翻译,仅实现斐波那契数列的计算。那么使用 Go 不做任何算法优化的话,纯计算函数的实现,代码大概会类似下面:

代码语言:javascript
复制
package main

func main() {}

//export fibonacci
func fibonacci(n uint) uint {
 if n == 0 {
  return 0
 } else if n == 1 {
  return 1
 } else {
  return fibonacci(n-1) + fibonacci(n-2)
 }
}

将上面的内容保存为 wasm.go,参考上篇文章中提到的编写通用的 WASI 程序,执行 tinygo build --no-debug -o module.wasm -wasm-abi=generic -target=wasi wasm.go

我们将会顺利的得到一个编译好的、通用的 WASM 程序,用于稍后的程序测试中。

编写 Go 语言基准版程序

虽然我们已经得到了 WASM 程序,但是为了直观的比较,我们还是需要一个完全使用 Go 实现基础版的程序:

代码语言:javascript
复制
package main

import (
 "fmt"
 "time"
)

func fibonacci(n uint) uint {
 if n == 0 {
  return 0
 } else if n == 1 {
  return 1
 } else {
  return fibonacci(n-1) + fibonacci(n-2)
 }
}

func main() {
 start := time.Now()
 n := uint(40)
 fmt.Println(fibonacci(n))
 fmt.Printf("🎉 都搞定了,用时:%v \n", time.Since(start).Milliseconds())
}

将上面的代码保存为 base.go,然后执行 go run base.go,将会看到类似下面的结果:

代码语言:javascript
复制
102334155
🎉 都搞定了,用时:574

如果你想追求绝对的性能,可以进行 go build base.go,然后执行 ./base,不过因为代码实在是太简单了,从输出结果来看,性能差异并不大。

基础计算功能搞定后,我们来简单调整代码,让代码具备并行计算的能力:

代码语言:javascript
复制
package main

import (
 "fmt"
 "os"
 "runtime"
 "strconv"
 "time"
)

func fibonacci(n uint) uint {
 if n == 0 {
  return 0
 } else if n == 1 {
  return 1
 } else {
  return fibonacci(n-1) + fibonacci(n-2)
 }
}

func calc(n uint, ch chan string) {
 ret := fibonacci(n)
 msg := strconv.FormatUint(uint64(ret), 10)
 fmt.Println(fmt.Sprintf("📦 收到结果 %s", msg))
 ch <- msg
}

func main() {
 numCPUs := runtime.NumCPU()
 n := uint(40)
 ch := make(chan string, numCPUs)

 fmt.Printf("🚀 主进程上线 #ID %v\n", os.Getpid())

 start := time.Now()

 for i := 0; i < numCPUs; i++ {
  fmt.Printf("👷 分发计算任务 #ID %v\n", i)
  go calc(n, ch)
 }

 for i := 0; i < numCPUs; i++ {
  <-ch
 }

 fmt.Printf("🎉 都搞定了,用时:%v \n", time.Since(start).Milliseconds())
}

将代码保存为 full.go,然后执行 go run full.go,将会看到类似下面的结果:

代码语言:javascript
复制
🚀 主进程上线 #ID 2248
👷 分发计算任务 #ID 0
👷 分发计算任务 #ID 1
👷 分发计算任务 #ID 2
👷 分发计算任务 #ID 3
👷 分发计算任务 #ID 4
👷 分发计算任务 #ID 5
👷 分发计算任务 #ID 6
👷 分发计算任务 #ID 7
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
🎉 都搞定了,用时:658

是不是有一丝性价比的味道,前面一次计算结果接近 600 毫秒,这次并发8个(受限于 Docker 环境资源限制)计算,也就 650 毫秒。

编写 Go 语言调用 WASM 程序

代码语言:javascript
复制
package main

import (
 "fmt"
 "os"
 "runtime"
 "time"

 "io/ioutil"

 wasmer "github.com/wasmerio/wasmer-go/wasmer"
)

func main() {
 wasmBytes, _ := ioutil.ReadFile("module.wasm")
 store := wasmer.NewStore(wasmer.NewEngine())
 module, _ := wasmer.NewModule(store, wasmBytes)

 wasiEnv, _ := wasmer.NewWasiStateBuilder("wasi-program").Finalize()
 GenerateImportObject, err := wasiEnv.GenerateImportObject(store, module)
 check(err)

 instance, err := wasmer.NewInstance(module, GenerateImportObject)
 check(err)

 wasmInitial, err := instance.Exports.GetWasiStartFunction()
 check(err)
 wasmInitial()

 fibonacci, err := instance.Exports.GetFunction("fibonacci")
 check(err)

 numCPUs := runtime.NumCPU()
 ch := make(chan string, numCPUs)

 fmt.Printf("🚀 主进程上线 #ID %v\n", os.Getpid())

 start := time.Now()

 for i := 0; i < numCPUs; i++ {
  fmt.Printf("👷 分发计算任务 #ID %v\n", i)

  calc := func(n uint, ch chan string) {
   ret, _ := fibonacci(n)
   msg := fmt.Sprintf("%d", ret)
   fmt.Println(fmt.Sprintf("📦 收到结果 %s", msg))
   ch <- msg
  }

  go calc(40, ch)
 }

 for i := 0; i < numCPUs; i++ {
  <-ch
 }

 fmt.Printf("🎉 都搞定了,用时:%v \n", time.Since(start).Milliseconds())

}

func check(e error) {
 if e != nil {
  panic(e)
 }
}

将代码保存为 wasi.go ,执行 go run wasi.go,会得到类似下面的结果:

代码语言:javascript
复制
🚀 主进程上线 #ID 3198
👷 分发计算任务 #ID 0
👷 分发计算任务 #ID 1
👷 分发计算任务 #ID 2
👷 分发计算任务 #ID 3
👷 分发计算任务 #ID 4
👷 分发计算任务 #ID 5
👷 分发计算任务 #ID 6
👷 分发计算任务 #ID 7
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
🎉 都搞定了,用时:595

多执行几次,你会发现相比较完全使用 Go 实现的程序,多数执行结果居然会更快一些,极限的情况,也不过是差不多快。是不是性价比的味道又来了。(不过为了保证客观,文末会附带多次计算结果)

使用 Node 编写基准版程序(Cluster模式)

相同的代码如果使用 Node.js 来实现,行数会稍微多一点。

虽然程序文件名叫啥都行,但是为了能在 CLI 执行上偷懒,就管它叫 index.js 吧:

代码语言:javascript
复制
const cluster = require('cluster');
const { cpus } = require('os');
const { exit, pid } = require('process');

function fibonacci(num) {
  if (num === 0) {
    return 0;
  } else if (num === 1) {
    return 1;
  } else {
    return fibonacci(num - 1) + fibonacci(num - 2);
  }
}


const numCPUs = cpus().length;

if (cluster.isPrimary) {
  let dataStore = [];
  const readyChecker = () => {
    if (dataStore.length === numCPUs) {
      console.log(`🎉 都搞定了,用时:${new Date - start}`);
      exit(0);
    }
  }

  console.log(`🚀 主进程上线 #ID ${pid}`);
  const start = new Date();

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('online', (worker) => {
    console.log(`👷 分发计算任务 #ID ${worker.id}`);
    worker.send(40);
  });

  const messageHandler = function (msg) {
    console.log("📦 收到结果", msg.ret)
    dataStore.push(msg.ret);
    readyChecker()
  }

  for (const id in cluster.workers) {
    cluster.workers[id].on('message', messageHandler);
  }

} else {
  process.on('message', (msg) => {
    process.send({ ret: fibonacci(msg) });
  });
}

保存好文件,执行 node . ,程序输出内容会类似下面这样:

代码语言:javascript
复制
🚀 主进程上线 #ID 2038
👷 分发计算任务 #ID 1
👷 分发计算任务 #ID 2
👷 分发计算任务 #ID 3
👷 分发计算任务 #ID 4
👷 分发计算任务 #ID 7
👷 分发计算任务 #ID 5
👷 分发计算任务 #ID 6
👷 分发计算任务 #ID 8
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
🎉 都搞定了,用时:1747

目前来看,执行时间差不多是 Go 版程序的 3 倍,不过往好处想,可提升空间应该也非常大。

使用 Node 编写基准版程序(Worker Threads)

当然,每当提到 Node.js Cluster 的时候,就不免会有人说 Cluster 数据交换效率低、比较笨重,所以我们同样测试一把 Worker Threads

其实从 Node.js 12.x LTS 开始,Node 就具备了 Worker Threads 的能力。关于如何使用 Node.js 的 Worker Treads ,以及循序渐进的理解如何使用 SharedArrayBuffer,可以参考这篇文章《How to work with worker threads in NodeJS》,本文就不做基础展开了。考虑到我们模拟的是极限糟糕的情况,所以这里也不必使用 node-worker-threads-pool 之类的三方库,对 threads 做 pool 化了。(实测几乎没差别)

将上面 Cluster 版本的代码简单调整,我们可以得到类似下面的代码,不同的是,为了书写简单,我们这次需要拆分为两个文件:

代码语言:javascript
复制
const { Worker } = require("worker_threads");
const { cpus } = require('os');
const { exit } = require('process');

let dataStore = [];

const readyChecker = () => {
    if (dataStore.length === numCPUs) {
        console.log(`🎉 都搞定了,用时:${new Date - start}`)
        exit(0);
    }
}

const num = [40];
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * num.length);
const sharedArray = new Int32Array(sharedBuffer);
Atomics.store(sharedArray, 0, num);

const numCPUs = cpus().length;

console.log(`🚀 主进程上线 #ID ${process.pid}`);

const start = new Date();

for (let i = 0; i < numCPUs; i++) {
    const worker = new Worker("./worker.js");

    worker.on("message", msg => {
        console.log("📦 收到结果", msg.ret)
        dataStore.push(msg.ret);
        readyChecker()
    });

    console.log(`👷 分发计算任务`);
    worker.postMessage({ num: sharedArray });
}

可以考虑换个目录,先将上面的内容同样保存为 index.js。我们继续来完成 worker.js 的内容。

代码语言:javascript
复制
const { parentPort } = require("worker_threads");

function fibonacci(num) {
    if (num === 0) {
        return 0;
    } else if (num === 1) {
        return 1;
    } else {
        return fibonacci(num - 1) + fibonacci(num - 2);
    }
}

parentPort.on("message", data => {
    parentPort.postMessage({ num: data.num, ret: fibonacci(data.num) });
});

在文件都保存就绪后,执行 node . 同样可以看到类似下面的输出:

代码语言:javascript
复制
🚀 主进程上线 #ID 2190
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
🎉 都搞定了,用时:1768

这里应该是因为交换的数据量比较少,所以执行时间其实和 Cluster 版本差不多。

使用 Node 调用 WASM 程序(Cluster)

简单调整上文中的 Cluster 模式的代码,来实现一个能够使用 WASM 进行计算的程序:

代码语言:javascript
复制
const { readFileSync } = require('fs');
const { WASI } = require('wasi');
const { argv, env } = require('process');

const cluster = require('cluster');
const { cpus } = require('os');
const { exit, pid } = require('process');

const numCPUs = cpus().length;

if (cluster.isPrimary) {
    let dataStore = [];
    const readyChecker = () => {
        if (dataStore.length === numCPUs) {
            console.log(`🎉 都搞定了,用时:${new Date - start}`);
            exit(0);
        }
    }

    console.log(`🚀 主进程上线 #ID ${pid}`);
    const start = new Date();

    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('online', (worker) => {
        console.log(`👷 分发计算任务 #ID ${worker.id}`);
        worker.send(40);
    });

    const messageHandler = function (msg) {
        console.log("📦 收到结果", msg.ret)
        dataStore.push(msg.ret);
        readyChecker()
    }

    for (const id in cluster.workers) {
        cluster.workers[id].on('message', messageHandler);
    }

} else {

    process.on('message', async (msg) => {
        const wasi = new WASI({ args: argv, env });
        const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
        const wasm = await WebAssembly.compile(readFileSync("./module.wasm"));
        const instance = await WebAssembly.instantiate(wasm, importObject);
        wasi.start(instance);
        const ret = await instance.exports.fibonacci(msg)
        process.send({ ret });
    });
}

将内容保存为 index.js 后,执行 node --experimental-wasi-unstable-preview1 . 后,会看到类似下面的输出结果:

代码语言:javascript
复制
🚀 主进程上线 #ID 2338
(node:2338) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
👷 分发计算任务 #ID 1
👷 分发计算任务 #ID 3
👷 分发计算任务 #ID 2
👷 分发计算任务 #ID 5
👷 分发计算任务 #ID 6
👷 分发计算任务 #ID 8
👷 分发计算任务 #ID 4
👷 分发计算任务 #ID 7
(node:2345) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2346) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2360) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2350) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2365) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2377) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2371) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2354) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
🎉 都搞定了,用时:808

是不是再次嗅到了一丝真香的味道。在几乎不需要怎么费劲的情况下,简单调整下代码,执行效率比不使用 WASM 少了一半的时间,甚至在降低了一个数量级之后,如果继续优化、以及让程序持续的运行,或许甚至能够无限趋近于 Go 版本实现的程序的性能。

使用 Node 调用 WASM 程序(Worker Threads)

为了让我们的代码保持简单,我们可以将程序拆分为三个部分:入口程序、Worker程序、WASI 调用程序。还是先来实现入口程序 index.js

代码语言:javascript
复制
const { Worker } = require("worker_threads");
const { cpus } = require('os');
const { exit, pid } = require('process')

let dataStore = [];

const readyChecker = () => {
    if (dataStore.length === numCPUs) {
        console.log(`🎉 都搞定了,用时:${new Date - start}`);
        exit(0);
    }
}

const num = [40];
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * num.length);
const sharedArray = new Int32Array(sharedBuffer);
Atomics.store(sharedArray, 0, num);

const numCPUs = cpus().length;
console.log(`🚀 主进程上线 #ID ${pid}`);

const start = new Date();

for (let i = 0; i < numCPUs; i++) {
    const worker = new Worker("./worker.js");

    worker.on("message", msg => {
        console.log("📦 收到结果", msg.ret)
        dataStore.push(msg.ret);
        readyChecker()
    });

    console.log(`👷 分发计算任务`);
    worker.postMessage({ num: sharedArray });
}

接着实现 worker.js 部分:

代码语言:javascript
复制
const { parentPort } = require("worker_threads");
const wasi = require("./wasi");

parentPort.on("message", async (msg) => {
    const instance = await wasi();
    const ret = await instance.exports.fibonacci(msg.num)
    parentPort.postMessage({ ret });
});

最后来实现新增的 wasi.js 部分:

代码语言:javascript
复制
const { readFileSync } = require('fs');
const { WASI } = require('wasi');
const { argv, env } = require('process');

module.exports = async () => {
    const wasi = new WASI({ args: argv, env });
    const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
    const wasm = await WebAssembly.compile(readFileSync("./module.wasm"));
    const instance = await WebAssembly.instantiate(wasm, importObject);
    wasi.start(instance);
    return instance;
};

将文件都保存好之后,执行 node . 可以看到类似上面 Cluster 版本的运行输出:

代码语言:javascript
复制
🚀 主进程上线 #ID 2927
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
👷 分发计算任务
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2927) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
📦 收到结果 102334155
🎉 都搞定了,用时:825

和分别使用 Cluster 和 Threads 实现基准版程序一样,执行时间也是差不多的。

对上述程序进行批量测试

为了让结果更客观,我们来编写一个小程序,让上面的程序分别重复执行 100 遍,并剔除表现最好和最糟糕的情况,取平均值。

先来实现一个简单的程序,代替我们的手动执行,针对不同的程序目录执行不同的命令,收集 100 次程序运行数据:

代码语言:javascript
复制
const { execSync } = require('child_process');
const { writeFileSync } = require('fs');
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));

const cmd = './base';
const cwd = '../go-case';
const file = './go-base.json';

let data = [];

(async () => {
    for (let i = 1; i <= 100; i++) {
        console.log(`⌛️ 正在收集 ${i} 次结果`)
        const stdout = execSync(cmd, { cwd }).toString();
        const words = stdout.split('\n').filter(n => n && n.includes('搞定'))[0];
        const time = Number(words.match(/\d+/)[0]);
        data.push(time)
        await sleep(100);
    }
    writeFileSync(file, JSON.stringify(data))
})();

程序执行之后的输出会类似下面这样:

代码语言:javascript
复制
⌛️ 正在收集 1 次结果
⌛️ 正在收集 2 次结果
⌛️ 正在收集 3 次结果
...
⌛️ 正在收集 100 次结果

在数据采集完毕之后,我们来编写一个更简单的程序,来查看程序运行的极端情况,以及排除掉极端情况后的平均值。(其实还应该跑个分布,不过偷懒就不做啦,感兴趣的同学可以试试看,会有惊喜)

代码语言:javascript
复制
const raw = require('./data.json').sort((a, b) => a - b);
const body = raw.slice(1,-1);

const sum = body.reduce((x,y)=>x+y, 0);
const average = Math.floor(sum / body.length);

const best = raw[0];
const worst = raw[raw.length-1];

console.log('最佳',best);
console.log('最差',worst);
console.log('平均', average);

测试结果

前文提到,本次测试可以看作上述程序在非常受限制的情况下的比较差的数据。

因为这次我没有使用云服务器,而是使用笔记本上的本地容器,所以在持续运行过程中,由于多次密集计算,会导致设备的发热。存在因为温度导致计算结果放大的情况,如果你采取云端设备进行测试,数值结果应该会比较漂亮

类型

最佳

最差

平均

Go

574

632

586

Go + WASM

533

800

610

Node Cluster

1994

3054

2531

Node Threads

1997

3671

2981

Node Cluster + WASM

892

1694

1305

Node Threads + WASM

887

2160

1334

但是总体而言,趋势还是很明显的,或许足够支撑我们在一些适合的场景下,采取 WASM + Node 或者 WASM + GO 的方式来进行混合开发了。

最后

如果说上一篇文章目的在于让想要折腾 WASM 的同学快速上手,那么这篇文章,希望能够帮助到“犹豫是否采用轻量异构方案”的同学,并给出最简单的实战代码。

因为非常多的客观原因,WASM 的发展和推广或许会和 Docker 一般,非常的慢热。在 2021 年的末尾,我们看到了 Docker 已经爆发出来的巨大能量。

如果你愿意保持开放心态,如同七八年前,对待 Docker 一样的来看待 WASM,相信这个轻量的、标准化的、异构的解决方案应该能为你和你的项目带来非常多的不同。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 折腾技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 前置准备
  • 准备 WASM 程序
    • 约定模拟密集计算的场景
      • 使用 Go 编写 具备 WASI 标准接口的 WASM 程序
      • 编写 Go 语言基准版程序
      • 编写 Go 语言调用 WASM 程序
      • 使用 Node 编写基准版程序(Cluster模式)
      • 使用 Node 编写基准版程序(Worker Threads)
      • 使用 Node 调用 WASM 程序(Cluster)
      • 使用 Node 调用 WASM 程序(Worker Threads)
      • 对上述程序进行批量测试
      • 测试结果
      • 最后
      相关产品与服务
      容器镜像服务
      容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档