跪拜 Guibai
← Back to the summary

Node.js's 2026 first-half perf haul: SIMD text encoding, faster require(), and a leaner event loop

Node.js's core repository has landed a batch of performance changes every month from January to June this year. Most people upgrade and just feel it's a bit faster, but can't say where exactly.

The person who organized these changes by month is Gürgün Dayıoğlu, a Node.js core collaborator and Fastify maintainer, who has been writing a post every month since October.

January touched text encoding and CommonJS imports.

TextEncoder.encode() is the string-to-bytes interface common to browsers and Node. Its underlying implementation switched to simdutf, a library that uses SIMD instructions to process UTF-8 in bulk, much faster than character-by-character conversion.

const bytes = new TextEncoder().encode("你好,Node.js");
const text = new TextDecoder().decode(bytes);

On the CJS side, the parser switched to an implementation called merve.

The require() syntax hasn't changed; what changed is the step that scans export names on first file read. One-time processes like CLI tools and test startup notice it most: cold imports are about 25% faster.

February was startup and buffered writes.

Node's ESM loader used to be built from scratch on every startup. Now it's baked into a startup snapshot, making ESM startup 6.06% faster.

import { readFile } from "node:fs/promises";

const text = await readFile("./data.txt", "utf8");

buf.write() also switched to simdutf; a 2048-character write is 577% faster. Code that stuffs text into Buffer, like serialization and database drivers, gains the most.

const buf = Buffer.alloc(2048);
buf.write("一段不短的文本", "utf8");

In the same month, util.styleText() took a fast path when no validation stream is needed.

March was Buffer.

Buffer.copyBytesFrom() with an offset is 210% faster, and other Buffer operations were changed too.

const src = Buffer.from("abcdef");
const dst = Buffer.alloc(3);

Buffer.copyBytesFrom(dst, src, 1);
console.log(dst.toString()); // bcd

On the crypto side, EC public key import. JWT signature verification often imports public keys from JWK, which is the JSON representation of such keys. Import speed went from 76,259 per second to 168,920 per second.

import { createPublicKey } from "node:crypto";

const key = createPublicKey({ key: jwk, format: "jwk" });

EventEmitter no longer clones the listener array on emit, and Web Streams' pipeTo() became lighter. These shipped with 24.15.0 and 26.

April was the most fragmented, all on common interfaces.

When URLSearchParams encounters duplicate keys, set() and delete() previously scanned the whole table repeatedly. delete() is 1852% faster in extreme cases; not that dramatic in normal use, but the redundant scans are gone.

const params = new URLSearchParams("a=1&a=2&a=3");

params.delete("a");

Buffer.toString("hex") on a 64-byte Buffer is 41.64% faster. Hashing, IDs, and tokens logged all go through this path.

const id = Buffer.from("hi").toString("hex"); // 6869

Built-in node:sqlite reads text columns 29.51% faster, and pure ASCII text takes less memory.

Experimental node:ffi got fast paths for calls passing numbers and pointers; pointer calls are 60% faster.

process.nextTick() skips the rejection check when there are no pending promises, going from 6.92 million per second to 9.04 million.

April's changes shipped with 26.1.0 and 24.16.0.

May leaned toward the Web platform.

util.styleText() with hex colors previously created a new formatter each time; now it caches, making it 1135% faster.

import { styleText } from "node:util";

console.log(styleText("red", "构建失败"));

crypto.subtle previously had to determine which algorithm to use on every call; that step is 164.69% faster.

QUIC, the transport protocol under HTTP/3, is still experimental in Node. A batch of packet-processing changes raised requests per second by about 10%.

May shipped with 26.3.0 and 24.19.0.

The six months of work are spread across several layers; horizontally, the shape is like the diagram below.

June was almost all text.

TextEncoder.encode() previously copied the string first, then encoded; now that step is skipped, making 8KB strings 43.9% faster.

const big = "a".repeat(8192);
const bytes = new TextEncoder().encode(big);

buf.copy() copying Buffer slices is 22.33% faster, and pipeTo() does a little less per chunk, bumping throughput about 5%.

The event loop skips internal callback checks when there are no queued tasks, from 77,957 per second to 81,438 per second, and every Node process benefits.

June shipped with 26.5.0.

The post notes the version for each month, listed below.

Upgrading requires no code changes; all of these are in 24.x and 26.x minor versions.

To see which batch your version picked up, search the changelog for perf.

All Node.js versions: https://nodejs.org/en/about/previous-releases

Do you usually upgrade the runtime for startup time? Feel free to comment below.


About me:

Comments

Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.

卷不动了噻

Fewer people use Node now, right?

前端之虎陈随易

Plenty.