paulmillr / noble-secp256k1

Fastest 4KB JS implementation of secp256k1 signatures and ECDH
https://paulmillr.com/noble
MIT License
757 stars 114 forks source link

Speedup byte/hex/number conversions #53

Closed brandonblack closed 2 years ago

brandonblack commented 2 years ago
brandonblack commented 2 years ago

No idea if you want these, I was just mucking around. They don't move the needle on practical speed, feel free to close/ignore.

paulmillr commented 2 years ago
  1. setBiguint64 is not available in safari 14, we can't use it. Unless we shim like in noble-hashes
  2. Could you post some online benchmark for bytesToHex? I was confident array join was slower.
  3. Could you post results of npm run bench before/after improved bytesToNumber (only 1 function)?
brandonblack commented 2 years ago
  1. Ah, I thought I could just target ES2020.

Will do some more benchmarking and post results. I didn't benchmark in browsers, only on node.

brandonblack commented 2 years ago

Here's the bytesToNumber benchmarks (2 runs new, 2 runs old). The difference looks to be lost in the noise, at least on my CPU:

New bytesToNumber

Benchmarking
Initialized: 36ms
RAM: rss=46.9mb heap=9.3mb used=5.8mb ext=1.2mb

getPublicKey(utils.randomPrivateKey()) x 3,074 ops/sec @ 325μs/op
sign x 2,411 ops/sec @ 414μs/op
signSync x 2,462 ops/sec @ 406μs/op
verify x 477 ops/sec @ 2ms/op
recoverPublicKey x 437 ops/sec @ 2ms/op
getSharedSecret aka ecdh x 285 ops/sec @ 3ms/op
getSharedSecret (precomputed) x 3,255 ops/sec @ 307μs/op
Point.fromHex (decompression) x 6,602 ops/sec @ 151μs/op
schnorr.sign x 335 ops/sec @ 2ms/op
schnorr.verify x 439 ops/sec @ 2ms/op

RAM: rss=65.7mb heap=24.5mb used=8.7mb ext=1.2mb

Benchmarking
Initialized: 43ms
RAM: rss=46.7mb heap=9.3mb used=5.8mb ext=1.2mb

getPublicKey(utils.randomPrivateKey()) x 2,863 ops/sec @ 349μs/op
sign x 2,223 ops/sec @ 449μs/op
signSync x 2,248 ops/sec @ 444μs/op
verify x 432 ops/sec @ 2ms/op
recoverPublicKey x 412 ops/sec @ 2ms/op
getSharedSecret aka ecdh x 262 ops/sec @ 3ms/op
getSharedSecret (precomputed) x 3,154 ops/sec @ 317μs/op
Point.fromHex (decompression) x 6,202 ops/sec @ 161μs/op
schnorr.sign x 317 ops/sec @ 3ms/op
schnorr.verify x 452 ops/sec @ 2ms/op

RAM: rss=65.0mb heap=24.7mb used=13.7mb ext=1.2mb

-------
Old bytesToNumber

Benchmarking
Initialized: 38ms
RAM: rss=46.2mb heap=9.3mb used=5.7mb ext=1.2mb

getPublicKey(utils.randomPrivateKey()) x 3,136 ops/sec @ 318μs/op
sign x 2,324 ops/sec @ 430μs/op
signSync x 2,417 ops/sec @ 413μs/op
verify x 476 ops/sec @ 2ms/op
recoverPublicKey x 438 ops/sec @ 2ms/op
getSharedSecret aka ecdh x 286 ops/sec @ 3ms/op
getSharedSecret (precomputed) x 3,366 ops/sec @ 297μs/op
Point.fromHex (decompression) x 6,408 ops/sec @ 156μs/op
schnorr.sign x 347 ops/sec @ 2ms/op
schnorr.verify x 473 ops/sec @ 2ms/op

RAM: rss=64.0mb heap=24.2mb used=7.0mb ext=1.2mb

Benchmarking
Initialized: 46ms
RAM: rss=44.0mb heap=9.3mb used=5.8mb ext=1.2mb

getPublicKey(utils.randomPrivateKey()) x 2,890 ops/sec @ 345μs/op
sign x 2,213 ops/sec @ 451μs/op
signSync x 2,189 ops/sec @ 456μs/op
verify x 447 ops/sec @ 2ms/op
recoverPublicKey x 411 ops/sec @ 2ms/op
getSharedSecret aka ecdh x 268 ops/sec @ 3ms/op
getSharedSecret (precomputed) x 3,199 ops/sec @ 312μs/op
Point.fromHex (decompression) x 6,162 ops/sec @ 162μs/op
schnorr.sign x 319 ops/sec @ 3ms/op
schnorr.verify x 450 ops/sec @ 2ms/op

RAM: rss=65.7mb heap=24.7mb used=9.4mb ext=1.2mb
brandonblack commented 2 years ago

You are absolutely right on bytesToHex don't know how I messed up that result.

Here's a micro-bmark of these 2 functions:

Old:
bytesToNumber x 561,797 ops/sec @ 1μs/op
bytesToHex x 1,733,102 ops/sec @ 577ns/op

New:
bytesToNumber x 935,453 ops/sec @ 1μs/op
bytesToHex x 750,750 ops/sec @ 1μs/op
brandonblack commented 2 years ago

Without getBigUint64 this bytesToNumber is about the same as the one in main:

bytesToNumber x 590,667 ops/sec @ 1μs/op
paulmillr commented 2 years ago

closing since the actual speed improvements are tiny compared to complexity