• 作者:老汪软件技巧
  • 发表时间:2024-11-10 04:00
  • 浏览量:

了解如何通过 Web Workers 和 WebAssembly 显著提升 JavaScript 应用的性能,并使用斐波那契算法作为案例分析。

JavaScript 通常运行在单线程上,通常称为“主线程”。这意味着 JavaScript 以同步方式一次执行一个任务。主线程还负责渲染任务,例如页面绘制和布局,以及处理用户交互,这也意味着长时间运行的 JavaScript 任务会导致浏览器变得无响应。这就是为什么当一个耗时的 JavaScript 函数运行时,网页可能会“卡住”,阻碍用户的正常操作。

我们将通过模拟斐波那契算法的繁重计算来演示如何阻塞主线程,并将通过以下几种方法来解决主线程阻塞的问题:

斐波那契算法

在本文的所有案例研究中,我们将使用一个简单且非常常见的斐波那契算法,其时间复杂度为 O(2^n)。

const calculateFibonacci = (n: number): number => {
  if (n <= 1) return n;
  return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
};

单线程

现在,让我们直接在主线程上实现斐波那契算法。当按钮被点击时,简单地调用斐波那契函数。

子组件 Spinner.vue:

<template>
  <div class="flex justify-center items-center">
    <div class="animate-spin rounded-full h-16 w-16 border-t-4 border-b-4 border-blue-500">div>
  div>
template>
​
<script lang="ts" setup>
script>
​
<style scoped>style>

父组件 WebAssemblySingle.vue:

<template>
  <div class="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white">
    <button @click="handleCalculate"
      class="mb-8 px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
      计算斐波那契数列
    button>
    <Spinner v-if="isLoading" />
    <p v-else class="text-xl">结果: {{ result }}p>
  div>
template>
​
<script lang="ts" setup>
import Spinner from '@/components/Spinner.vue';
import { ref } from 'vue';
​
const result = refnull>(null);
const isLoading = ref(false);
​
const calculateFibonacci = (n: number): number => {
  if (n <= 1) return n;
  return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
};
​
const handleCalculate = () => {
  isLoading.value = true;
  const fibonacciResult = calculateFibonacci(42);
  result.value = fibonacciResult;
  isLoading.value = false;
};
script>
​
<style scoped>style>

现在,让我们尝试点击 "计算斐波那契数列" 按钮,同时测量性能。要测量代码的性能,我们可以使用 Chrome DevTools 中的性能工具。

如您在界面中所见,我们的加载动画按钮甚至没有出现,取而代之的是突然显示了计算结果。从性能工具中我们也可以看到,由于斐波那契算法在主线程上的繁重计算,旋转动画被阻塞了大约 2.11 秒。

单线程

多线程(Web Worker)

将繁重的计算从主线程移开的常用方法是使用 Web Worker。

/**
 * 将斐波那契算法移到 Web Worker 中
 */
self.addEventListener("message", function (e) {
  const n = e.data;
​
  const fibonacci = (n) => {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
  };
​
  const result = fibonacci(n);
  self.postMessage(result);
});

子组件 Spinner.vue 保持不变,父组件为 WebAssemblyWorker.vue:

<template>
  <div class="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white">
    <button @click="handleCalculate"
      class="mb-8 px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
      计算斐波那契数列
    button>
    <Spinner v-if="isLoading" />
    <p v-else class="text-xl">结果: {{ result }}p>
  div>
template>
​
<script lang="ts" setup>
import Spinner from '@/components/Spinner.vue';
import { ref } from 'vue';
​
const result = refnull>(null);
const isLoading = ref(false);
​
const calculateFibonacci = (n: number): number => {
  if (n <= 1) return n;
  return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
};
​
const handleCalculate = () => {
  isLoading.value = true;
  const fibonacciResult = calculateFibonacci(42);
  result.value = fibonacciResult;
  isLoading.value = false;
};
script>
​
<style scoped>style>

现在,如果我们进行性能测试,可以看到加载动画平稳运行。这是因为我们将繁重的计算任务移到了工作线程,避免了阻塞主线程。

可以看到,单线程和工作线程的计算时间都大约是 2 秒。那么问题来了,我们如何进一步优化呢?答案是使用 WebAssembly。

性能提升怎么算_性能提升是什么意思_

Worker

图解说明:动画正在正常运行,此时斐波那契算法已在另一个线程上执行,有效避免了主线程的阻塞。

WebAssembly — AssemblyScript

作为一名前端工程师,若在其他语言上的经验有限并想尝试 WebAssembly,我们通常会选择 AssemblyScript,因为它的开发体验最接近 TypeScript。

以下是用 AssemblyScript 编写的等效斐波那契代码。

export function fibonacci(n: i32): i32 {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

编译该代码后,会生成一个 release.wasm 文件。然后我们可以在 JavaScript 代码中使用这个 Wasm 文件。

子组件 Spinner.vue 保持不变,父组件为 AssemblyScript.vue:

<template>
  <div class="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white">
    <button @click="handleCalculate"
      class="mb-8 px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
      计算斐波那契数列
    button>
    <Spinner v-if="isLoading" />
    <p v-else class="text-xl">结果: {{ result }}p>
  div>
template>
​
<script lang="ts" setup>
import Spinner from '@/components/Spinner.vue';
​
import { ref } from 'vue';
​
const result = refnull>(null);
const isLoading = ref(false);
​
const handleCalculate = async () => {
  isLoading.value = true;
​
  const wasmUrl = new URL('@/assembly/release.wasm', import.meta.url);
  const wasmModule = await fetch(wasmUrl);
  const buffer = await wasmModule.arrayBuffer();
  const module = await WebAssembly.instantiate(buffer);
  const wasm = module.instance.exports;
​
  result.value = wasm.fibonacci(42);
  isLoading.value = false;
};
script>
​
<style scoped>style>

现在,如果我们再次测量性能,尽管仍在主线程上,但加载动画出现了,并且没有被繁重的计算阻塞。斐波那契算法现在大约耗时 830 毫秒,比仅使用 JavaScript 快了约 60%。

AssemblyScript

WebAssembly — Rust

Rust 是 WebAssembly 的热门选择之一,Mozilla 的官方文档也推荐了它。让我们尝试用 Rust 实现相同的斐波那契算法。

use wasm_bindgen::prelude::*;
​
// 通过 WebAssembly 将函数暴露给 JavaScript
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

子组件 Spinner.vue 保持不变,父组件为 AssemblyScript.vue:

<template>
  <div class="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white">
    <button @click="handleCalculate"
      class="mb-8 px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
      计算斐波那契数列
    button>
    <Spinner v-if="isLoading" />
    <p v-else class="text-xl">结果: {{ result }}p>
  div>
template>
​
<script lang="ts" setup>
import Spinner from '@/components/Spinner.vue';
​
import { ref } from 'vue';
import init, { fibonacci } from '@/assembly/pkg/hello_wasm.js';
​
const result = refnull>(null);
const isLoading = ref(false);
​
const handleCalculate = async () => {
  isLoading.value = true;
​
  await init();
  result.value = fibonacci(42);
​
  isLoading.value = false;
};
script>
​
<style scoped>style>

现在,让我们看看使用 Rust 编写的 WebAssembly 的效果。我们仍然在主线程上运行,但使用了 Wasm。和 AssemblyScript 类似,即使在主线程上运行 Wasm,加载动画仍然可以正常显示,不会被阻塞。令人惊讶的是,这个繁重的计算现在仅耗时 490 毫秒,比仅使用 JavaScript 快了 76%。

Rust.png

总结

备注:如果您对本文中的完整代码有兴趣,可以在评论区留言,或通过私信等方式联系我获取。


上一条查看详情 +Linux系统-发行版介绍
下一条 查看详情 +没有了