Skip to content

Commit 05b9dec

Browse files
committed
perf(NODE-5934): replace DataView uses with bit math
1 parent 9a150e1 commit 05b9dec

File tree

7 files changed

+151
-72
lines changed

7 files changed

+151
-72
lines changed

.eslintrc.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
"no-bigint-usage/no-bigint-literals": "error",
7676
"no-restricted-globals": [
7777
"error",
78-
"BigInt"
78+
"BigInt",
79+
"DataView"
7980
]
8081
},
8182
"overrides": [

src/objectid.ts

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BSONValue } from './bson_value';
22
import { BSONError } from './error';
33
import { type InspectFn, defaultInspect } from './parser/utils';
4-
import { BSONDataView, ByteUtils } from './utils/byte_utils';
4+
import { ByteUtils } from './utils/byte_utils';
55

66
// Regular expression that checks for hex value
77
const checkForHexRegExp = new RegExp('^[0-9a-fA-F]{24}$');
@@ -179,7 +179,13 @@ export class ObjectId extends BSONValue {
179179
const buffer = ByteUtils.allocate(12);
180180

181181
// 4-byte timestamp
182-
BSONDataView.fromUint8Array(buffer).setUint32(0, time, false);
182+
buffer[3] = time;
183+
time = time >>> 8;
184+
buffer[2] = time;
185+
time = time >>> 8;
186+
buffer[1] = time;
187+
time = time >>> 8;
188+
buffer[0] = time;
183189

184190
// set PROCESS_UNIQUE if yet not initialized
185191
if (PROCESS_UNIQUE === null) {
@@ -259,7 +265,11 @@ export class ObjectId extends BSONValue {
259265
/** Returns the generation date (accurate up to the second) that this ID was generated. */
260266
getTimestamp(): Date {
261267
const timestamp = new Date();
262-
const time = BSONDataView.fromUint8Array(this.id).getUint32(0, false);
268+
const time =
269+
this.buffer[3] +
270+
this.buffer[2] * (1 << 8) +
271+
this.buffer[1] * (1 << 16) +
272+
this.buffer[0] * (1 << 24);
263273
timestamp.setTime(Math.floor(time) * 1000);
264274
return timestamp;
265275
}
@@ -275,9 +285,23 @@ export class ObjectId extends BSONValue {
275285
* @param time - an integer number representing a number of seconds.
276286
*/
277287
static createFromTime(time: number): ObjectId {
278-
const buffer = ByteUtils.fromNumberArray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
288+
const buffer = ByteUtils.allocate(12);
289+
buffer[11] = 0;
290+
buffer[10] = 0;
291+
buffer[9] = 0;
292+
buffer[8] = 0;
293+
buffer[7] = 0;
294+
buffer[6] = 0;
295+
buffer[5] = 0;
296+
buffer[4] = 0;
279297
// Encode time into first 4 bytes
280-
BSONDataView.fromUint8Array(buffer).setUint32(0, time, false);
298+
buffer[3] = time;
299+
time = time >>> 8;
300+
buffer[2] = time;
301+
time = time >>> 8;
302+
buffer[1] = time;
303+
time = time >>> 8;
304+
buffer[0] = time;
281305
// Return the new objectId
282306
return new ObjectId(buffer);
283307
}

src/parser/deserializer.ts

+49-32
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { ObjectId } from '../objectid';
1414
import { BSONRegExp } from '../regexp';
1515
import { BSONSymbol } from '../symbol';
1616
import { Timestamp } from '../timestamp';
17-
import { BSONDataView, ByteUtils } from '../utils/byte_utils';
17+
import { ByteUtils } from '../utils/byte_utils';
1818
import { validateUtf8 } from '../validate_utf8';
1919

2020
/** @public */
@@ -128,6 +128,9 @@ export function internalDeserialize(
128128

129129
const allowedDBRefKeys = /^\$ref$|^\$id$|^\$db$/;
130130

131+
const FLOAT = new Float64Array(1);
132+
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
133+
131134
function deserializeObject(
132135
buffer: Uint8Array,
133136
index: number,
@@ -218,8 +221,6 @@ function deserializeObject(
218221

219222
let isPossibleDBRef = isArray ? false : null;
220223

221-
let dataView;
222-
223224
// While we have more left data left keep parsing
224225
while (!done) {
225226
// Read the type
@@ -286,14 +287,17 @@ function deserializeObject(
286287
(buffer[index++] << 8) |
287288
(buffer[index++] << 16) |
288289
(buffer[index++] << 24);
289-
} else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) {
290-
dataView ??= new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
291-
value = new Double(dataView.getFloat64(index, true));
292-
index = index + 8;
293290
} else if (elementType === constants.BSON_DATA_NUMBER) {
294-
dataView ??= new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
295-
value = dataView.getFloat64(index, true);
296-
index = index + 8;
291+
FLOAT_BYTES[0] = buffer[index++];
292+
FLOAT_BYTES[1] = buffer[index++];
293+
FLOAT_BYTES[2] = buffer[index++];
294+
FLOAT_BYTES[3] = buffer[index++];
295+
FLOAT_BYTES[4] = buffer[index++];
296+
FLOAT_BYTES[5] = buffer[index++];
297+
FLOAT_BYTES[6] = buffer[index++];
298+
FLOAT_BYTES[7] = buffer[index++];
299+
value = FLOAT[0];
300+
if (promoteValues === false) value = new Double(value);
297301
} else if (elementType === constants.BSON_DATA_DATE) {
298302
const lowBits =
299303
buffer[index++] |
@@ -363,30 +367,43 @@ function deserializeObject(
363367
} else if (elementType === constants.BSON_DATA_NULL) {
364368
value = null;
365369
} else if (elementType === constants.BSON_DATA_LONG) {
366-
// Unpack the low and high bits
367-
const dataview = BSONDataView.fromUint8Array(buffer.subarray(index, index + 8));
368-
369-
const lowBits =
370-
buffer[index++] |
371-
(buffer[index++] << 8) |
372-
(buffer[index++] << 16) |
373-
(buffer[index++] << 24);
374-
const highBits =
375-
buffer[index++] |
376-
(buffer[index++] << 8) |
377-
(buffer[index++] << 16) |
378-
(buffer[index++] << 24);
379-
const long = new Long(lowBits, highBits);
380370
if (useBigInt64) {
381-
value = dataview.getBigInt64(0, true);
382-
} else if (promoteLongs && promoteValues === true) {
383-
// Promote the long if possible
384-
value =
385-
long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
386-
? long.toNumber()
387-
: long;
371+
const lo =
372+
buffer[index] +
373+
buffer[index + 1] * 2 ** 8 +
374+
buffer[index + 2] * 2 ** 16 +
375+
buffer[index + 3] * 2 ** 24;
376+
const hi =
377+
buffer[index + 4] +
378+
buffer[index + 5] * 2 ** 8 +
379+
buffer[index + 6] * 2 ** 16 +
380+
(buffer[index + 7] << 24); // Overflow
381+
382+
/* eslint-disable-next-line no-restricted-globals -- This is allowed here as useBigInt64=true */
383+
value = (BigInt(hi) << BigInt(32)) + BigInt(lo);
384+
index += 8;
388385
} else {
389-
value = long;
386+
// Unpack the low and high bits
387+
const lowBits =
388+
buffer[index++] |
389+
(buffer[index++] << 8) |
390+
(buffer[index++] << 16) |
391+
(buffer[index++] << 24);
392+
const highBits =
393+
buffer[index++] |
394+
(buffer[index++] << 8) |
395+
(buffer[index++] << 16) |
396+
(buffer[index++] << 24);
397+
const long = new Long(lowBits, highBits);
398+
// Promote the long if possible
399+
if (promoteLongs && promoteValues === true) {
400+
value =
401+
long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
402+
? long.toNumber()
403+
: long;
404+
} else {
405+
value = long;
406+
}
390407
}
391408
} else if (elementType === constants.BSON_DATA_DECIMAL128) {
392409
// Buffer to contain the decimal bytes

src/parser/serializer.ts

+54-22
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ function serializeString(buffer: Uint8Array, key: string, value: string, index:
7272
return index;
7373
}
7474

75-
const NUMBER_SPACE = new DataView(new ArrayBuffer(8), 0, 8);
76-
const FOUR_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 4);
77-
const EIGHT_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 8);
75+
const FLOAT = new Float64Array(1);
76+
const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
7877

7978
function serializeNumber(buffer: Uint8Array, key: string, value: number, index: number) {
8079
const isNegativeZero = Object.is(value, -0);
@@ -87,23 +86,32 @@ function serializeNumber(buffer: Uint8Array, key: string, value: number, index:
8786
? constants.BSON_DATA_INT
8887
: constants.BSON_DATA_NUMBER;
8988

90-
if (type === constants.BSON_DATA_INT) {
91-
NUMBER_SPACE.setInt32(0, value, true);
92-
} else {
93-
NUMBER_SPACE.setFloat64(0, value, true);
94-
}
95-
96-
const bytes =
97-
type === constants.BSON_DATA_INT ? FOUR_BYTE_VIEW_ON_NUMBER : EIGHT_BYTE_VIEW_ON_NUMBER;
98-
9989
buffer[index++] = type;
10090

10191
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
10292
index = index + numberOfWrittenBytes;
10393
buffer[index++] = 0x00;
10494

105-
buffer.set(bytes, index);
106-
index += bytes.byteLength;
95+
if (type === constants.BSON_DATA_INT) {
96+
let int32 = value;
97+
buffer[index++] = int32;
98+
int32 = int32 >>> 8;
99+
buffer[index++] = int32;
100+
int32 = int32 >>> 8;
101+
buffer[index++] = int32;
102+
int32 = int32 >>> 8;
103+
buffer[index++] = int32;
104+
} else {
105+
FLOAT[0] = value;
106+
buffer[index++] = FLOAT_BYTES[0];
107+
buffer[index++] = FLOAT_BYTES[1];
108+
buffer[index++] = FLOAT_BYTES[2];
109+
buffer[index++] = FLOAT_BYTES[3];
110+
buffer[index++] = FLOAT_BYTES[4];
111+
buffer[index++] = FLOAT_BYTES[5];
112+
buffer[index++] = FLOAT_BYTES[6];
113+
buffer[index++] = FLOAT_BYTES[7];
114+
}
107115

108116
return index;
109117
}
@@ -115,10 +123,29 @@ function serializeBigInt(buffer: Uint8Array, key: string, value: bigint, index:
115123
// Encode the name
116124
index += numberOfWrittenBytes;
117125
buffer[index++] = 0;
118-
NUMBER_SPACE.setBigInt64(0, value, true);
119-
// Write BigInt value
120-
buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index);
121-
index += EIGHT_BYTE_VIEW_ON_NUMBER.byteLength;
126+
127+
/* eslint-disable-next-line no-restricted-globals -- This is allowed here as useBigInt64=true */
128+
const mask32bits = BigInt(0xffffffff);
129+
130+
let lo = Number(value & mask32bits);
131+
buffer[index++] = lo;
132+
lo = lo >> 8;
133+
buffer[index++] = lo;
134+
lo = lo >> 8;
135+
buffer[index++] = lo;
136+
lo = lo >> 8;
137+
buffer[index++] = lo;
138+
139+
/* eslint-disable-next-line no-restricted-globals -- This is allowed here as useBigInt64=true */
140+
let hi = Number((value >> BigInt(32)) & mask32bits);
141+
buffer[index++] = hi;
142+
hi = hi >> 8;
143+
buffer[index++] = hi;
144+
hi = hi >> 8;
145+
buffer[index++] = hi;
146+
hi = hi >> 8;
147+
buffer[index++] = hi;
148+
122149
return index;
123150
}
124151

@@ -401,11 +428,16 @@ function serializeDouble(buffer: Uint8Array, key: string, value: Double, index:
401428
buffer[index++] = 0;
402429

403430
// Write float
404-
NUMBER_SPACE.setFloat64(0, value.value, true);
405-
buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index);
431+
FLOAT[0] = value.value;
432+
buffer[index++] = FLOAT_BYTES[0];
433+
buffer[index++] = FLOAT_BYTES[1];
434+
buffer[index++] = FLOAT_BYTES[2];
435+
buffer[index++] = FLOAT_BYTES[3];
436+
buffer[index++] = FLOAT_BYTES[4];
437+
buffer[index++] = FLOAT_BYTES[5];
438+
buffer[index++] = FLOAT_BYTES[6];
439+
buffer[index++] = FLOAT_BYTES[7];
406440

407-
// Adjust index
408-
index = index + 8;
409441
return index;
410442
}
411443

src/utils/byte_utils.ts

-6
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,3 @@ const hasGlobalBuffer = typeof Buffer === 'function' && Buffer.prototype?._isBuf
5151
* @internal
5252
*/
5353
export const ByteUtils: ByteUtils = hasGlobalBuffer ? nodeJsByteUtils : webByteUtils;
54-
55-
export class BSONDataView extends DataView {
56-
static fromUint8Array(input: Uint8Array) {
57-
return new DataView(input.buffer, input.byteOffset, input.byteLength);
58-
}
59-
}

src/utils/web_byte_utils.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ export const webByteUtils = {
189189
return new TextEncoder().encode(input).byteLength;
190190
},
191191

192-
encodeUTF8Into(buffer: Uint8Array, source: string, byteOffset: number): number {
192+
encodeUTF8Into(uint8array: Uint8Array, source: string, byteOffset: number): number {
193193
const bytes = new TextEncoder().encode(source);
194-
buffer.set(bytes, byteOffset);
194+
uint8array.set(bytes, byteOffset);
195195
return bytes.byteLength;
196196
},
197197

test/node/bigint.test.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { BSON, BSONError, EJSON, __noBigInt__ } from '../register-bson';
22
import { bufferFromHexArray } from './tools/utils';
33
import { expect } from 'chai';
44
import { BSON_DATA_LONG } from '../../src/constants';
5-
import { BSONDataView } from '../../src/utils/byte_utils';
65

76
describe('BSON BigInt support', function () {
87
beforeEach(function () {
@@ -126,7 +125,11 @@ describe('BSON BigInt support', function () {
126125
const DATA_TYPE_OFFSET = 4;
127126
const KEY_OFFSET = 5;
128127

129-
const dataView = BSONDataView.fromUint8Array(serializedDoc);
128+
const dataView = new DataView(
129+
serializedDoc.buffer,
130+
serializedDoc.byteOffset,
131+
serializedDoc.byteLength
132+
);
130133
const keySlice = serializedDoc.slice(KEY_OFFSET);
131134

132135
let keyLength = 0;
@@ -407,7 +410,11 @@ describe('BSON BigInt support', function () {
407410
const serialized = BSON.serialize(number);
408411

409412
const VALUE_OFFSET = 7;
410-
const dataView = BSONDataView.fromUint8Array(serialized);
413+
const dataView = new DataView(
414+
serialized.buffer,
415+
serialized.byteOffset,
416+
serialized.byteLength
417+
);
411418
const serializedValue = dataView.getBigInt64(VALUE_OFFSET, true);
412419
const parsed = JSON.parse(stringified);
413420

@@ -431,7 +438,11 @@ describe('BSON BigInt support', function () {
431438
const serializedDoc = BSON.serialize(number);
432439

433440
const VALUE_OFFSET = 7;
434-
const dataView = BSONDataView.fromUint8Array(serializedDoc);
441+
const dataView = new DataView(
442+
serializedDoc.buffer,
443+
serializedDoc.byteOffset,
444+
serializedDoc.byteLength
445+
);
435446
const parsed = JSON.parse(stringified);
436447

437448
expect(parsed).to.have.property('a');

0 commit comments

Comments
 (0)