本页内容

deno bench,基准测试工具

Deno 有一个内置的基准测试运行器,您可以使用它来检查 JavaScript 或 TypeScript 代码的性能。

快速入门 跳转到标题

首先,让我们创建一个名为 url_bench.ts 的文件,并使用 Deno.bench() 函数注册一个基准测试。

// url_bench.ts
Deno.bench("URL parsing", () => {
  new URL("https://deno.land");
});

其次,使用 deno bench 子命令运行基准测试。

deno bench url_bench.ts
cpu: Apple M1 Max
runtime: deno 1.21.0 (aarch64-apple-darwin)

file:///dev/deno/url_bench.ts
benchmark        time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------- -----------------------------
URL parsing   17.29 µs/iter  (16.67 µs … 153.62 µs)  17.25 µs  18.92 µs  22.25 µs

编写基准测试 跳转到标题

要定义一个基准测试,您需要使用对 Deno.bench API 的调用来注册它。此 API 有多个重载,以允许最大的灵活性并轻松地在表单之间切换(例如,当您需要快速关注单个基准测试进行调试时,使用 only: true 选项)。

// Compact form: name and function
Deno.bench("hello world #1", () => {
  new URL("https://deno.land");
});

// Compact form: named function.
Deno.bench(function helloWorld3() {
  new URL("https://deno.land");
});

// Longer form: bench definition.
Deno.bench({
  name: "hello world #2",
  fn: () => {
    new URL("https://deno.land");
  },
});

// Similar to compact form, with additional configuration as a second argument.
Deno.bench("hello world #4", { permissions: { read: true } }, () => {
  new URL("https://deno.land");
});

// Similar to longer form, with bench function as a second argument.
Deno.bench(
  { name: "hello world #5", permissions: { read: true } },
  () => {
    new URL("https://deno.land");
  },
);

// Similar to longer form, with a named bench function as a second argument.
Deno.bench({ permissions: { read: true } }, function helloWorld6() {
  new URL("https://deno.land");
});

异步函数 跳转到标题

您还可以通过传递一个返回 Promise 的基准测试函数来对异步代码进行基准测试。为此,您可以在定义函数时使用 async 关键字。

Deno.bench("async hello world", async () => {
  await 1;
});

临界区 跳转到标题

有时基准测试案例需要包含设置和拆卸代码,这些代码会污染基准测试结果。例如,如果您想测量读取一个小文件所需的时间,您需要打开文件、读取它,然后关闭它。如果文件足够小,打开和关闭文件所需的时间可能会超过读取文件本身所需的时间。

为了帮助解决这种情况,您可以使用 Deno.BenchContext.startDeno.BenchContext.end 来告诉基准测试工具您要测量的临界区。这两个调用之间的所有内容都将从测量中排除。

import { readAll } from "https://deno.land/[email protected]/io/read_all.ts";

Deno.bench("foo", async (b) => {
  // Open a file that we will act upon.
  const file = await Deno.open("a_big_data_file.txt");

  // Tell the benchmarking tool that this is the only section you want
  // to measure.
  b.start();

  // Now let's measure how long it takes to read all of the data from the file.
  await readAll(file);

  // End measurement here.
  b.end();

  // Now we can perform some potentially time-consuming teardown that will not
  // taint out benchmark results.
  file.close();
});

分组和基线 跳转到标题

在注册基准测试案例时,可以使用 Deno.BenchDefinition.group 选项将其分配给一个组。

// url_bench.ts
Deno.bench("url parse", { group: "url" }, () => {
  new URL("https://deno.land");
});

将多个案例分配给一个组并比较它们相对于“基线”案例的性能很有用。

在这个例子中,我们将检查 Date.now()performance.now() 的性能,为此,我们将使用 Deno.BenchDefinition.baseline 选项将第一个案例标记为“基线”。

// time_bench.ts
Deno.bench("Date.now()", { group: "timing", baseline: true }, () => {
  Date.now();
});

Deno.bench("performance.now()", { group: "timing" }, () => {
  performance.now();
});
$ deno bench time_bench.ts
cpu: Apple M1 Max
runtime: deno 1.21.0 (aarch64-apple-darwin)

file:///dev/deno/time_bench.ts
benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
Date.now()         125.24 ns/iter (118.98 ns … 559.95 ns) 123.62 ns 150.69 ns 156.63 ns
performance.now()    2.67 µs/iter     (2.64 µs … 2.82 µs)   2.67 µs   2.82 µs   2.82 µs

summary
  Date.now()
   21.29x times faster than performance.now()

您可以在同一个文件中指定多个组。

运行基准测试 跳转到标题

要运行基准测试,请使用包含基准测试函数的文件调用 deno bench。您也可以省略文件名,在这种情况下,当前目录(递归)中所有匹配 glob {*_,*.,}bench.{ts, tsx, mts, js, mjs, jsx} 的基准测试都将运行。如果您传递一个目录,则将运行目录中所有匹配此 glob 的文件。

glob 展开为

  • 名为 bench.{ts, tsx, mts, js, mjs, jsx} 的文件,
  • 或以 .bench.{ts, tsx, mts, js, mjs, jsx} 结尾的文件,
  • 或以 _bench.{ts, tsx, mts, js, mjs, jsx} 结尾的文件。
# Run all benches in the current directory and all sub-directories
deno bench

# Run all benches in the util directory
deno bench util/

# Run just my_bench.ts
deno bench my_bench.ts

⚠️ 如果您想将其他 CLI 参数传递给基准测试文件,请使用 -- 来通知 Deno 剩余参数是脚本参数。

# Pass additional arguments to the bench file
deno bench my_bench.ts -- -e --foo --bar

deno bench 使用与 deno run 相同的权限模型,因此需要例如 --allow-write 才能在基准测试期间写入文件系统。

要查看 deno bench 的所有运行时选项,您可以参考命令行帮助。

deno help bench

过滤 跳转到标题

有多种选项可以过滤您正在运行的基准测试。

命令行过滤 跳转到标题

可以使用命令行 --filter 选项单独或成组运行基准测试。

过滤器标志接受字符串或模式作为值。

假设以下基准测试

Deno.bench({
  name: "my-bench",
  fn: () => {/* bench function zero */},
});
Deno.bench({
  name: "bench-1",
  fn: () => {/* bench function one */},
});
Deno.bench({
  name: "bench2",
  fn: () => {/* bench function two */},
});

此命令将运行所有这些基准测试,因为它们都包含“bench”一词。

deno bench --filter "bench" benchmarks/

另一方面,以下命令使用模式,并将运行第二个和第三个基准测试。

deno bench --filter "/bench-*\d/" benchmarks/

要让 Deno 知道您要使用模式,请将过滤器用正斜杠括起来,就像 JavaScript 中的正则表达式语法糖一样。

基准定义过滤 跳转到标题

在基准测试本身中,您有两个过滤选项。

过滤掉(忽略这些基准测试) 跳转到标题

有时您可能希望根据某种条件忽略基准测试(例如,您只希望基准测试在 Windows 上运行)。为此,您可以在基准定义中使用 ignore 布尔值。如果将其设置为 true,则会跳过该基准测试。

Deno.bench({
  name: "bench windows feature",
  ignore: Deno.build.os !== "windows",
  fn() {
    // do windows feature
  },
});

过滤进(只运行这些基准测试) 跳转到标题

有时您可能在一个大型基准测试类中遇到性能问题,并且您希望只关注那个特定的基准测试,暂时忽略其他基准测试。为此,您可以使用 only 选项告诉基准测试工具只运行将此选项设置为 true 的基准测试。多个基准测试可以设置此选项。虽然基准测试运行将报告每个基准测试的成功或失败,但如果任何基准测试被标记为 only,则整个基准测试运行将始终失败,因为这只是一项临时措施,它会禁用几乎所有基准测试。

Deno.bench({
  name: "Focus on this bench only",
  only: true,
  fn() {
    // bench complicated stuff
  },
});

JSON 输出 跳转到标题

要以 JSON 格式检索输出,请使用 --json 标志

$ deno bench --json bench_me.js
{
  "runtime": "Deno/1.31.0 x86_64-apple-darwin",
  "cpu": "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz",
  "benches": [
    "origin": "file:///dev/bench_me.js",
    "group": null,
    "name": "Deno.UnsafePointerView#getUint32",
    "baseline": false,
    "result": {
      "ok": {
        "n": 49,
        "min": 1251.9348,
        "max": 1441.2696,
        "avg": 1308.7523755102038,
        "p75": 1324.1055,
        "p99": 1441.2696,
        "p995": 1441.2696,
        "p999": 1441.2696
      }
    }
  ]
}