]> zoso.dev Git - buffer.git/commitdiff
Optimize hexWrite with an array lookup table (#343)
authorChristopher Jeffrey (JJ) <chjjeffrey@gmail.com>
Thu, 1 Feb 2024 00:54:40 +0000 (19:54 -0500)
committerGitHub <noreply@github.com>
Thu, 1 Feb 2024 00:54:40 +0000 (11:54 +1100)
Co-authored-by: Daniel Cousens <413395+dcousens@users.noreply.github.com>
index.js
package.json
perf/write-hex.js [new file with mode: 0644]
test/write-hex.js [new file with mode: 0644]

index dad9152b76ddd5052fe51cdd47e845544eb62e50..b0b51298aab48a67de41c84f8cb38ddcaa27e311 100644 (file)
--- a/index.js
+++ b/index.js
@@ -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) {
@@ -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) {
index 536fe46c386ec84c33cd8476d274b4f8a81e1260..5ee6b8d9b2a7e106e4b6d81b910c09f8022ce9b7 100644 (file)
@@ -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",
diff --git a/perf/write-hex.js b/perf/write-hex.js
new file mode 100644 (file)
index 0000000..76b03be
--- /dev/null
@@ -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')
+  })
diff --git a/test/write-hex.js b/test/write-hex.js
new file mode 100644 (file)
index 0000000..c10ac2b
--- /dev/null
@@ -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()
+})