deno_core icon indicating copy to clipboard operation
deno_core copied to clipboard

perf: resolve `Promise`s natively for async ops

Open CyanChanges opened this issue 5 months ago • 5 comments

Supersede #1158

Waiting for

  • [x] ~~https://github.com/denoland/rusty_v8/issues/1818~~
  • [ ] waiting for for deno_core updates to the latest rusty_v8

Summary

22% more performant async ops, (by resolve Promises natively) and less memory usage (no need of preparing an array of results)

const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')
function tImm(tag, p) {
  const symbol = Symbol('k.notImm')
  function check(v) {
    const imm = v !== symbol
    // ops.op_print(`@${tag} ${imm}\n`)
  }
  const p1 = Promise.any([p, Promise.resolve(symbol)])
  p1.then(check, check)
}
for (let i = 0; i < 50000; i++) {
tImm("op_void_async", ops.op_void_async())
tImm("op_void_async_deferred", ops.op_void_async_deferred())
tImm("op_error_async", ops.op_error_async())
}
// ops.op_print("Hello World\n")
image

CyanChanges avatar Jul 20 '25 15:07 CyanChanges

Help wanted

async stacktrace for error is gone rn

image

I need do the same but in native: image

somewhat solution: (obviously bad, but at least it kinda works) image

Don't have a bindings for this, also this is private i guess (https://github.com/v8/v8/blob/e3529d092163dcfbbb454257dc4103bdebfeda48/src/execution/messages.h#L150

This one should be public?, but still no binding for it i guess: https://github.com/v8/v8/blob/e3529d092163dcfbbb454257dc4103bdebfeda48/include/v8-exception.h#L69

I didn't found a considerable solutions, maybe I need to add bindings to rusty_v8 first? UPDATE: https://github.com/denoland/rusty_v8/issues/1818

CyanChanges avatar Jul 20 '25 16:07 CyanChanges

@CyanChanges can you please provide result when running this code in your PR and on main?

// bench.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  for (let i = 0; i < 500000; i++) {
    await op();
  }
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench.js
// bench_batched.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  const batched = new Array(500000).fill(op);
  await Promise.all(batched.map((fn) => fn()));
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench_batched.js

From our benchmarks this PR appears to be significantly slower than main:

// this PR
target/release/dcore bench.js
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 198ms
op_void_async_deferred took 1166ms
// main

./dcore_main bench.js                                                           [INS]
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 35ms
op_void_async_deferred took 974ms

bartlomieju avatar Aug 04 '25 15:08 bartlomieju

@CyanChanges can you please provide result when running this code in your PR and on main?

// bench.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  for (let i = 0; i < 500000; i++) {
    await op();
  }
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench.js
// bench_batched.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  const batched = new Array(500000).fill(op);
  await Promise.all(batched.map((fn) => fn()));
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench_batched.js

From our benchmarks this PR appears to be significantly slower than main:

// this PR
target/release/dcore bench.js
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 198ms
op_void_async_deferred took 1166ms
// main

./dcore_main bench.js                                                           [INS]
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 35ms
op_void_async_deferred took 974ms

@CyanChanges can you please provide result when running this code in your PR and on main?

// bench.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  for (let i = 0; i < 500000; i++) {
    await op();
  }
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench.js
// bench_batched.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  const batched = new Array(500000).fill(op);
  await Promise.all(batched.map((fn) => fn()));
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench_batched.js

From our benchmarks this PR appears to be significantly slower than main:

// this PR
target/release/dcore bench.js
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 198ms
op_void_async_deferred took 1166ms
// main

./dcore_main bench.js                                                           [INS]
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 35ms
op_void_async_deferred took 974ms

I just switched to NixOS recently, so I needs to configure the dev environment and rebuild it. It may take a while tho

CyanChanges avatar Aug 04 '25 16:08 CyanChanges

@CyanChanges can you please provide result when running this code in your PR and on main?

// bench.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  for (let i = 0; i < 500000; i++) {
    await op();
  }
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench.js
// bench_batched.js
const ops = Deno.core.ops;
// ops.op_print(Object.keys(ops).join('\n')+'\n')

async function bench(name, op) {
  const start = new Date();
  const batched = new Array(500000).fill(op);
  await Promise.all(batched.map((fn) => fn()));
  const end = new Date();
  const duration = end - start;
  ops.op_print(`${name} took ${duration}ms\n`);
}

await bench("op_void_async", ops.op_void_async)
await bench("op_void_async_deferred", ops.op_void_async_deferred)
target/release/dcore bench_batched.js

From our benchmarks this PR appears to be significantly slower than main:

// this PR
target/release/dcore bench.js
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 198ms
op_void_async_deferred took 1166ms
// main

./dcore_main bench.js                                                           [INS]
🛑 deno_core binary is meant for development and testing purposes.
Run bench.js
op_void_async took 35ms
op_void_async_deferred took 974ms
image

CyanChanges avatar Aug 04 '25 16:08 CyanChanges

image Yeah seems the improvements of the PR are mainly from `op_error_async`

CyanChanges avatar Aug 04 '25 16:08 CyanChanges