Skip to content

Commit

Permalink
Optimize hexWrite with an array lookup table (#343)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Cousens <413395+dcousens@users.noreply.github.com>
  • Loading branch information
chjj and dcousens committed Feb 1, 2024
1 parent cbd0ceb commit 54885d4
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 34 deletions.
67 changes: 34 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -855,19 +855,24 @@ function hexWrite (buf, string, offset, length) {

const strLen = string.length

if (length > strLen / 2) {
length = strLen / 2
if (length > (strLen >>> 1)) {
length = strLen >>> 1
}
let i
for (i = 0; i < length; ++i) {
const a = hexCharValueTable[string[i * 2]]
const b = hexCharValueTable[string[i * 2 + 1]]
if (a === undefined || b === undefined) {

for (let i = 0; i < length; ++i) {
const a = string.charCodeAt(i * 2 + 0)
const b = string.charCodeAt(i * 2 + 1)
const hi = hexCharValueTable[a & 0x7f]
const lo = hexCharValueTable[b & 0x7f]

if ((a | b | hi | lo) & ~0x7f) {
return i
}
buf[offset + i] = a << 4 | b

buf[offset + i] = (hi << 4) | lo
}
return i

return length
}

function utf8Write (buf, string, offset, length) {
Expand Down Expand Up @@ -2118,30 +2123,26 @@ const hexSliceLookupTable = (function () {
})()

// hex lookup table for Buffer.from(x, 'hex')
const hexCharValueTable = {
0: 0,
1: 1,
2: 2,
3: 3,
4: 4,
5: 5,
6: 6,
7: 7,
8: 8,
9: 9,
a: 10,
b: 11,
c: 12,
d: 13,
e: 14,
f: 15,
A: 10,
B: 11,
C: 12,
D: 13,
E: 14,
F: 15
}
/* eslint-disable no-multi-spaces, indent */
const hexCharValueTable = [
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
]
/* eslint-enable no-multi-spaces, indent */

// Return not function with Error if BigInt not supported
function defineBigIntMethod (fn) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
},
"scripts": {
"perf": "browserify --debug perf/bracket-notation.js > perf/bundle.js && open perf/index.html",
"perf-node": "node perf/bracket-notation.js && node perf/concat.js && node perf/copy-big.js && node perf/copy.js && node perf/new-big.js && node perf/new.js && node perf/readDoubleBE.js && node perf/readFloatBE.js && node perf/readUInt32LE.js && node perf/slice.js && node perf/writeFloatBE.js",
"perf-node": "node perf/bracket-notation.js && node perf/concat.js && node perf/copy-big.js && node perf/copy.js && node perf/new-big.js && node perf/new.js && node perf/readDoubleBE.js && node perf/readFloatBE.js && node perf/readUInt32LE.js && node perf/slice.js && node perf/writeFloatBE.js && node perf/write-hex.js",
"size": "browserify -r ./ | uglifyjs -c -m | gzip | wc -c",
"standard": "standard",
"test": "tape test/*.js test/node/*.js",
Expand Down
24 changes: 24 additions & 0 deletions perf/write-hex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const BrowserBuffer = require('../').Buffer // (this module)
const util = require('./util')
const suite = util.suite()

const LENGTH = 4096
const browserSubject = BrowserBuffer.alloc(LENGTH)
const nodeSubject = Buffer.alloc(LENGTH)

const charset = '0123456789abcdef'

let str = ''

for (let i = 0; i < LENGTH * 2; i++)
str += charset[Math.random() * charset.length | 0]

suite
.add('BrowserBuffer#write(' + LENGTH + ', "hex")', function () {
browserSubject.write(str, 'hex')
})

if (!process.browser) suite
.add('NodeBuffer#write(' + LENGTH + ', "hex")', function () {
nodeSubject.write(str, 'hex')
})
59 changes: 59 additions & 0 deletions test/write-hex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict'

const Buffer = require('../').Buffer
const test = require('tape')

test('buffer.write("hex") should stop on invalid characters', function (t) {
// Test the entire 16-bit space.
for (let ch = 0; ch <= 0xffff; ch++) {
// 0-9
if (ch >= 0x30 && ch <= 0x39) {
continue
}

// A-F
if (ch >= 0x41 && ch <= 0x46) {
continue
}

// a-f
if (ch >= 0x61 && ch <= 0x66) {
continue
}

for (const str of [
'abcd' + String.fromCharCode(ch) + 'ef0',
'abcde' + String.fromCharCode(ch) + 'f0',
'abcd' + String.fromCharCode(ch + 0) + String.fromCharCode(ch + 1) + 'f0',
'abcde' + String.fromCharCode(ch + 0) + String.fromCharCode(ch + 1) + '0'
]) {
const buf = Buffer.alloc(4)
t.equal(str.length, 8)
t.equal(buf.write(str, 'hex'), 2)
t.equal(buf.toString('hex'), 'abcd0000')
t.equal(Buffer.from(str, 'hex').toString('hex'), 'abcd')
}
}

t.end()
})

test('buffer.write("hex") should truncate odd string lengths', function (t) {
const buf = Buffer.alloc(32)
const charset = '0123456789abcdef'

let str = ''

for (let i = 0; i < 63; i++) {
str += charset[Math.random() * charset.length | 0]
}

t.equal(buf.write('abcde', 'hex'), 2)
t.equal(buf.toString('hex', 0, 3), 'abcd00')

buf.fill(0)

t.equal(buf.write(str, 'hex'), 31)
t.equal(buf.toString('hex', 0, 32), str.slice(0, -1) + '00')
t.end()
})

0 comments on commit 54885d4

Please sign in to comment.