用WebAssembly将Web App速度提升了20倍

用WebAssembly将Web App速度提升了20倍

WebAssembly是除JavaScript之外另一门可以在浏览器上运行的语言,其他语言(如C/C++/Rust)也可以被编译成WebAssembly在浏览器上运行。WebAssembly是静态类型的语言,使用线性内存,并保存成紧凑的二进制格式,所以速度非常快,可以以“接近原生”的速度运行代码(与从本地命令行运行程序的速度相当)。

到目前为止,WebAssembly已经被用在各种应用程序中,从游戏(如Doom 3)到将桌面应用程序移植到Web(如Autocad和Figma)。它甚至也被用到了浏览器之外,例如被作为一门高效而灵活的Serverless计算编程语言。

这篇文章将介绍如何使用WebAssembly来加速一款Web数据分析工具。

背景介绍

这个Web工具就是fastq.bio,它是一个交互式的Web工具,科学家用它来快速预览DNA序列数据的质量。下面是这个工具的一个截图:

用WebAssembly将Web App速度提升了20倍

我不打算深入介绍这个计算过程,但总的来说,上面的图表为科学家提供了一个有关DNA序列质量的信息,可以帮助他们快速发现数据质量问题。

虽然现在也有很多命令行工具可用来生成这类报告图表,但fastq.bio的目标是让用户可以在浏览器中直接通过交互式的方式预览数据质量,这对于不习惯使用命令行的科学家来说非常有用。

这个工具的输入是一个普通文本文件,其中包含了使用DNA序列指令生成的DNA序列和其中每个核苷酸的质量分数。这种格式被称为“FASTQ”,所以这个工具的名字叫作fastq.bio。

JavaScript 实现

最初版本的fastq.bio要求用户从本地选择一个FASTQ文件,这个工具借助File对象(使用了FileReader API)从文件的随机位置读取一小块数据,然后我们使用JavaScript对这个数据库执行基本的字符串操作,并计算相关的指标。这个指标可以帮助我们跟踪一个DNA片段中有多少A、C、G和T。

在计算好指标之后,我们使用Plotly.js画出结果图表,然后继续读取下一个数据块。我们之所以每次只处理一小块数据,是为了获得更好的用户体验,因为一次性处理整个文件(一个FASTQ文件通常会有几个GB那么大)会让用户等待太长时间。我们发现,每次处理介于0.5 MB到1 MB之间的数据块可以让应用程序看起来是连续的,而且可以更快地为用户返回信息,但这个数字也取决于应用程序的具体细节以及计算机的处理速度。

初始架构非常简单:

用WebAssembly将Web App速度提升了20倍

红色方框部分就是我们要进行的字符串操作,用来生成指标。这个部分是计算密集型的,所以很适合使用WebAssembly来优化。

WebAssembly 实现

为了搞清楚WebAssembly是否可以加快Web应用程序的速度,我们尝试了一些现成的工具,这些工具是使用C/C++/Rust开发的,这样就可以把它们移植成WebAssembly,并且这些工具已经得到科学社区的认可。

经过一些调研,我们最终决定使用seqtk(https://github.com/lh3/seqtk),这是一个被广泛使用的开源工具,使用C语言开发,可以用来评估序列数据的质量。

在将seqtk编译成WebAssembly之前,我们先来看看如何从源代码编译seqtk,并在命令行中运行它。

# Compile to binary
$ gcc seqtk.c \
   -o seqtk \
   -O2 \
   -lm \
   -lz

另一方面,我们可以使用 Emscripten 工具链将 seqtk 编译成 WebAssembly:

用WebAssembly将Web App速度提升了20倍

如果你还没有安装 Emscripten,可以从 Dockerhub 上下载我们提供的 docker 镜像,其中就包含了这个工具链:

用WebAssembly将Web App速度提升了20倍

或者你也可以从头开始安装,只是这样需要更长的时间:

用WebAssembly将Web App速度提升了20倍
$ docker pull robertaboukhalil/emsdk:1.38.26
$ docker run -dt --name wasm-seqtk robertaboukhalil/emsdk:1.38.26

在进入容器后,我们可以使用emcc代替gcc:

# Compile to WebAssembly
$ emcc seqtk.c \
    -o seqtk.js \
    -O2 \
    -lm \
    -s USE_ZLIB=1 \
    -s FORCE_FILESYSTEM=1

编译成二进制文件和编译成WebAssembly其实并没有太多不同之处:

1.Emscripten会生成一个.wasm文件和一个.js文件,而不是生成seqtk二进制文件。

2.我们使用了USE_ZLIB标记,这样就可以支持zlib库。因为zlib已经被移植到WebAssembly,并被广泛使用,所以Emscripten将会将其包含在项目中。

3.我们启用了Emscripten的虚拟文件系统(POSIX风格的文件系统),只是它是运行在浏览器的内存中,在页面被刷新时会消失,除非你使用IndexedDB把它的状态保存在浏览器中)。

为什么使用虚拟文件系统?为了回答这个问题,我们先来比较一下在命令行中调用seqtk和在JavaScript中调用编译过的WebAssembly模块:

# On the command line
$ ./seqtk fqchk data.fastq

# In the browser console
> Module.callMain(["fqchk", "data.fastq"])

访问虚拟文件系统是一个非常重要的能力,这意味着我们可以在不重写seqtk的情况下直接处理字符串。我们可以将数据块挂载到虚拟文件系统中(作为data.fastq文件),然后调用seqtk的main()函数。

在将seqtk编译成WebAssembly后的fastq.bio架构图:

用WebAssembly将Web App速度提升了20倍

如图所示,我们并没有在浏览器主线程上运行计算,而是使用了WebWorker,这样就可以在后台线程上运行计算,避免给浏览器造成阻塞。WebWorker的控制器负责启动Worker,并管理与主线程之间的通信。

然后,我们让Worker运行seqtk命令来处理事先挂载好的文件。在seqtk运行完成之后,Worker通过一个Promise将结果发送回主线程,主线程在接收到消息之后使用结果数据更新图表。与JavaScript实现一样,我们每次只处理一个数据块。

性能优化

为了评估使用WebAssembly是否确实为我们带来了速度上的优势,我们比较了JavaScript实现和WebAssembly实现每秒钟分别可以读取多少指标。我们忽略了生成交互式图表的时间,因为两者在这方面都使用了JavaScript。

在什么都没做的情况下,我们已经可以看到WebAssembly比JavaScript有9倍左右的速度提升:

用WebAssembly将Web App速度提升了20倍

这个结果已经很好了,不过,我们发现,seqtk 生成了很多有用的 QC 指标,但其中有很多并没有被用到。在移除了这些没有被用到的指标之后,速度提升达到了 13 倍。

用WebAssembly将Web App速度提升了20倍

最后还有一个可改进的地方。到目前为止,fastq.bio是通过调用两个不同的C函数来获取指标,其中每个函数负责计算一系列不同的指标。其中一个函数以直方图的形式返回信息,另一个则以DNA序列位置函数的形式返回信息。这意味着同一个数据块会被读取两次,而这其实是不必要的。

所以,我们将这两个函数的代码合成一个。因为原本的两个输出包含了不同数量的列,所以我们使用JavaScript来区分它们。但这样做是值得的:速度提升了20多倍!

用WebAssembly将Web App速度提升了20倍

注意事项

不要指望WebAssembly总能为我们带来20多倍的速度提升,有时候可能只能获得2倍甚至是20%的提升。而如果在内存中加载了太多的数据,有可能速度还会变慢,或者需要在WebAssembly和JavaScript之间进行很多的通信。

未经允许不得转载:码云笔记 » 用WebAssembly将Web App速度提升了20倍
喜欢(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

在线客服

在线客服

  • 扫描二维码,微信联系 扫描二维码,微信联系