Skip to content

Commit 7aee118

Browse files
committed
Disable pool and improve perf for poolSize=1
1 parent 384d3c7 commit 7aee118

File tree

2 files changed

+89
-41
lines changed

2 files changed

+89
-41
lines changed

src/objectid.ts

+58-37
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import { type InspectFn, defaultInspect } from './parser/utils';
44
import { ByteUtils } from './utils/byte_utils';
55
import { NumberUtils } from './utils/number_utils';
66

7+
// Settings for ObjectId Buffer pool
8+
// Disable pool by default in order to ensure compatibility
9+
// Specify larger poolSize to enable pool
710
let currentPool: Uint8Array | null = null;
8-
let poolSize = 1000; // Default: Hold 1000 ObjectId buffers in a pool
11+
let poolSize = 1; // Disable pool by default.
912
let currentPoolOffset = 0;
1013

1114
/**
1215
* Retrieves a ObjectId pool and offset. This function may create a new ObjectId buffer pool and reset the pool offset
1316
* @internal
1417
*/
1518
function getPool(): [Uint8Array, number] {
16-
if (!currentPool || currentPoolOffset + 12 > currentPool.byteLength) {
19+
if (!currentPool || currentPoolOffset + 12 > currentPool.length) {
1720
currentPool = ByteUtils.allocateUnsafe(poolSize * 12);
1821
currentPoolOffset = 0;
1922
}
@@ -75,7 +78,7 @@ export class ObjectId extends BSONValue {
7578
/** ObjectId buffer pool pointer @internal */
7679
private pool: Uint8Array;
7780
/** Buffer pool offset @internal */
78-
private offset: number;
81+
private offset?: number;
7982

8083
/** ObjectId hexString cache @internal */
8184
private __id?: string;
@@ -151,43 +154,55 @@ export class ObjectId extends BSONValue {
151154
workingId = inputId;
152155
}
153156

154-
const [pool, offset] = getPool();
155-
156-
// The following cases use workingId to construct an ObjectId
157-
if (workingId == null || typeof workingId === 'number') {
158-
// The most common use case (blank id, new objectId instance)
159-
// Generate a new id
160-
ObjectId.generate(typeof workingId === 'number' ? workingId : undefined, pool, offset);
161-
} else if (ArrayBuffer.isView(workingId)) {
162-
if (workingId.byteLength === 12) {
163-
inputIndex = 0;
164-
} else if (
165-
typeof inputIndex !== 'number' ||
166-
inputIndex < 0 ||
167-
workingId.byteLength < inputIndex + 12 ||
168-
isNaN(inputIndex)
169-
) {
170-
throw new BSONError('Buffer length must be 12 or a valid offset must be specified');
171-
}
172-
for (let i = 0; i < 12; i++) pool[offset + i] = workingId[inputIndex + i];
173-
} else if (typeof workingId === 'string') {
174-
if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
175-
pool.set(ByteUtils.fromHex(workingId), offset);
157+
let pool: Uint8Array;
158+
let offset: number;
159+
160+
// Special case when poolSize === 1 and a 12 byte buffer is passed in - just persist buffer
161+
if (poolSize === 1 && ArrayBuffer.isView(workingId) && workingId.length === 12) {
162+
pool = ByteUtils.toLocalBufferType(workingId);
163+
offset = 0;
164+
} else {
165+
[pool, offset] = getPool();
166+
167+
// The following cases use workingId to construct an ObjectId
168+
if (workingId == null || typeof workingId === 'number') {
169+
// The most common use case (blank id, new objectId instance)
170+
// Generate a new id
171+
ObjectId.generate(typeof workingId === 'number' ? workingId : undefined, pool, offset);
172+
} else if (ArrayBuffer.isView(workingId)) {
173+
if (workingId.length === 12) {
174+
inputIndex = 0;
175+
} else if (
176+
typeof inputIndex !== 'number' ||
177+
inputIndex < 0 ||
178+
workingId.length < inputIndex + 12 ||
179+
isNaN(inputIndex)
180+
) {
181+
throw new BSONError('Buffer length must be 12 or a valid offset must be specified');
182+
}
183+
for (let i = 0; i < 12; i++) pool[offset + i] = workingId[inputIndex + i];
184+
} else if (typeof workingId === 'string') {
185+
if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
186+
pool.set(ByteUtils.fromHex(workingId), offset);
187+
} else {
188+
throw new BSONError(
189+
'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'
190+
);
191+
}
176192
} else {
177-
throw new BSONError(
178-
'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'
179-
);
193+
throw new BSONError('Argument passed in does not match the accepted types');
180194
}
181-
} else {
182-
throw new BSONError('Argument passed in does not match the accepted types');
183195
}
184196
// If we are caching the hex string
185197
if (ObjectId.cacheHexString) {
186198
this.__id = ByteUtils.toHex(pool, offset, offset + 12);
187199
}
188200
// Increment pool offset once we have completed initialization
189201
this.pool = pool;
190-
this.offset = offset;
202+
// Only set offset if pool is used
203+
if (poolSize > 1) {
204+
this.offset = offset;
205+
}
191206
incrementPool();
192207
}
193208

@@ -201,6 +216,7 @@ export class ObjectId extends BSONValue {
201216
* @readonly
202217
*/
203218
get id(): Uint8Array {
219+
if (this.offset === undefined) return this.pool;
204220
return this.pool.subarray(this.offset, this.offset + 12);
205221
}
206222

@@ -219,8 +235,9 @@ export class ObjectId extends BSONValue {
219235
if (ObjectId.cacheHexString && this.__id) {
220236
return this.__id;
221237
}
238+
const start = this.offset ?? 0;
222239

223-
const hexString = ByteUtils.toHex(this.pool, this.offset, this.offset + 12);
240+
const hexString = ByteUtils.toHex(this.pool, start, start + 12);
224241

225242
if (ObjectId.cacheHexString && !this.__id) {
226243
this.__id = hexString;
@@ -329,16 +346,20 @@ export class ObjectId extends BSONValue {
329346
}
330347

331348
if (ObjectId.is(otherId)) {
332-
if (otherId.pool && typeof otherId.offset === 'number') {
349+
if (otherId.pool) {
333350
for (let i = 11; i >= 0; i--) {
334-
if (this.pool[this.offset + i] !== otherId.pool[otherId.offset + i]) {
351+
const offset = this.offset ?? 0;
352+
const otherOffset = otherId.offset ?? 0;
353+
if (this.pool[offset + i] !== otherId.pool[otherOffset + i]) {
335354
return false;
336355
}
337356
}
338357
return true;
339358
}
340359
// If otherId does not have pool and offset, fallback to buffer comparison for compatibility
341-
return ByteUtils.equals(this.buffer, otherId.buffer);
360+
return (
361+
this.buffer[11] === otherId.buffer[11] && ByteUtils.equals(this.buffer, otherId.buffer)
362+
);
342363
}
343364

344365
if (typeof otherId === 'string') {
@@ -357,7 +378,7 @@ export class ObjectId extends BSONValue {
357378
/** Returns the generation date (accurate up to the second) that this ID was generated. */
358379
getTimestamp(): Date {
359380
const timestamp = new Date();
360-
const time = NumberUtils.getUint32BE(this.pool, this.offset);
381+
const time = NumberUtils.getUint32BE(this.pool, this.offset ?? 0);
361382
timestamp.setTime(Math.floor(time) * 1000);
362383
return timestamp;
363384
}
@@ -370,7 +391,7 @@ export class ObjectId extends BSONValue {
370391
/** @internal */
371392
serializeInto(uint8array: Uint8Array, index: number): 12 {
372393
const pool = this.pool;
373-
const offset = this.offset;
394+
const offset = this.offset ?? 0;
374395
uint8array[index] = pool[offset];
375396
uint8array[index + 1] = pool[offset + 1];
376397
uint8array[index + 2] = pool[offset + 2];

test/node/object_id.test.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,18 @@ describe('ObjectId', function () {
259259
});
260260

261261
it('should correctly use buffer pool for ObjectId creation', function () {
262+
const oldPoolSize = ObjectId.poolSize;
263+
ObjectId.poolSize = 2;
262264
const obj = new ObjectId();
263265
const obj2 = new ObjectId();
264266

267+
expect(obj.offset).to.equal(0);
268+
expect(obj2.offset).to.equal(12);
265269
expect(obj.offset).to.not.equal(obj2.offset);
266270
expect(obj.pool).to.equal(obj2.pool);
267271

268272
expect(obj.id).to.not.equal(obj2.id);
273+
ObjectId.poolSize = oldPoolSize;
269274
});
270275

271276
it('should respect buffer pool size for ObjectId creation', function () {
@@ -307,9 +312,9 @@ describe('ObjectId', function () {
307312
const obj2 = new ObjectId();
308313
const obj3 = new ObjectId();
309314

310-
expect(obj.offset).to.equal(0);
311-
expect(obj2.offset).to.equal(0);
312-
expect(obj3.offset).to.equal(0);
315+
expect(obj.offset).to.equal(undefined);
316+
expect(obj2.offset).to.equal(undefined);
317+
expect(obj3.offset).to.equal(undefined);
313318
expect(obj.pool).to.not.equal(obj2.pool);
314319
expect(obj2.pool).to.not.equal(obj3.pool);
315320

@@ -534,7 +539,28 @@ describe('ObjectId', function () {
534539
let equalId = { _bsontype: 'ObjectId', [oidKId]: oid.id };
535540

536541
const propAccessRecord: string[] = [];
542+
equalId = new Proxy(equalId, {
543+
get(target, prop: string, recv) {
544+
if (prop !== '_bsontype') {
545+
propAccessRecord.push(prop);
546+
}
547+
return Reflect.get(target, prop, recv);
548+
}
549+
});
537550

551+
expect(oid.equals(equalId)).to.be.true;
552+
// once for the 11th byte shortcut
553+
// once for the total equality
554+
expect(propAccessRecord).to.deep.equal(['pool', oidKId, oidKId]);
555+
});
556+
557+
it('should use otherId[kId] Pool for equality when otherId has _bsontype === ObjectId when using pool', () => {
558+
const oldPoolSize = ObjectId.poolSize;
559+
ObjectId.poolSize = 2;
560+
const oid = new ObjectId(oidString);
561+
let equalId = new ObjectId(oidString);
562+
563+
const propAccessRecord: string[] = [];
538564
equalId = new Proxy(equalId, {
539565
get(target, prop: string, recv) {
540566
if (prop !== '_bsontype') {
@@ -547,7 +573,8 @@ describe('ObjectId', function () {
547573
expect(oid.equals(equalId)).to.be.true;
548574
// once for the 11th byte shortcut
549575
// once for the total equality
550-
expect(propAccessRecord).to.deep.equal(['pool', oidKId]);
576+
expect(propAccessRecord).to.contain('pool').contain('offset');
577+
ObjectId.poolSize = oldPoolSize;
551578
});
552579
});
553580

0 commit comments

Comments
 (0)