Skip to content

perf: optimization perfomance #14

@PandaWorker

Description

@PandaWorker

Code

interface ReusifyItem {
	next: this | null;
}

function reusify<T extends ReusifyItem>(Constructor: new () => T) {
	let head: T | null = null;
	let tail: T | null = null;

	head = tail = new Constructor();

	function acquire(): T {
		var current = head;

		if (current?.next) {
			head = current.next;
		} else {
			tail = head = new Constructor();
		}

		current!.next = null;

		return current!;
	}

	function release(obj: T) {
		tail!.next = obj;
		tail = obj;
	}

	return {
		acquire: acquire,
		release: release,
	};
}

class Reusify<T extends ReusifyItem> {
	private head: T | null;
	private tail: T | null;
	private Constructor: new () => T;

	constructor(Constructor: new () => T) {
		this.tail = this.head = new Constructor();
		this.Constructor = Constructor;
	}

	public acquire(): T {
		var current = this.head;

		if (current?.next) {
			this.head = current.next;
			current.next = null;
		} else {
			this.tail = this.head = new this.Constructor();
		}

		return current!;
	}

	public release(obj: T): void {
		this.tail = this.tail!.next = obj;
	}
}

Tests and benchmarks

// tests
import assert from 'node:assert/strict';

class Task {
	next = null;
}

function MyTask() {
	// @ts-expect-error:
	this.next = null;
}

Deno.test({
	name: 'func (task instance class)',
	fn: () => {
		const instance = reusify(Task);

		var obj = instance.acquire();
		var obj2 = instance.acquire();
		var obj3 = instance.acquire();

		assert(obj !== obj2);
		assert(obj2 !== obj3);
		assert(obj3 !== obj);

		assert.notEqual(obj, obj2);
		assert.notEqual(obj2, obj3);
		assert.notEqual(obj3, obj);

		assert.equal(obj.next, null);
		assert.equal(obj2.next, null);
		assert.equal(obj3.next, null);

		instance.release(obj);
		instance.release(obj2);
		instance.release(obj3);

		// skip one
		instance.acquire();

		var obj4 = instance.acquire();
		var obj5 = instance.acquire();
		var obj6 = instance.acquire();

		assert(obj4 === obj, '4');
		assert(obj5 === obj2, '5');
		assert(obj6 === obj3, '6');
	},
});

Deno.test({
	name: 'func (task instance object or func)',
	fn: () => {
		const instance = reusify(Task);

		var obj = instance.acquire();
		var obj2 = instance.acquire();
		var obj3 = instance.acquire();

		assert(obj !== obj2);
		assert(obj2 !== obj3);
		assert(obj3 !== obj);

		assert.notEqual(obj, obj2);
		assert.notEqual(obj2, obj3);
		assert.notEqual(obj3, obj);

		assert.equal(obj.next, null);
		assert.equal(obj2.next, null);
		assert.equal(obj3.next, null);

		instance.release(obj);
		instance.release(obj2);
		instance.release(obj3);

		// skip one
		instance.acquire();

		var obj4 = instance.acquire();
		var obj5 = instance.acquire();
		var obj6 = instance.acquire();

		assert(obj4 === obj, '4');
		assert(obj5 === obj2, '5');
		assert(obj6 === obj3, '6');
	},
});

Deno.test({
	name: 'class (task instance object or func)',
	fn: () => {
		// @ts-expect-error:
		const instance = new Reusify(MyTask);

		var obj = instance.acquire();
		var obj2 = instance.acquire();
		var obj3 = instance.acquire();

		assert(obj !== obj2);
		assert(obj2 !== obj3);
		assert(obj3 !== obj);

		assert.notEqual(obj, obj2);
		assert.notEqual(obj2, obj3);
		assert.notEqual(obj3, obj);

		assert.equal(obj.next, null);
		assert.equal(obj2.next, null);
		assert.equal(obj3.next, null);

		instance.release(obj);
		instance.release(obj2);
		instance.release(obj3);

		// skip one
		instance.acquire();

		var obj4 = instance.acquire();
		var obj5 = instance.acquire();
		var obj6 = instance.acquire();

		assert(obj4 === obj, '4');
		assert(obj5 === obj2, '5');
		assert(obj6 === obj3, '6');
	},
});

Deno.test({
	name: 'class (task instance class)',
	fn: () => {
		const instance = new Reusify(Task);

		var obj = instance.acquire();
		var obj2 = instance.acquire();
		var obj3 = instance.acquire();

		assert(obj !== obj2);
		assert(obj2 !== obj3);
		assert(obj3 !== obj);

		assert.notEqual(obj, obj2);
		assert.notEqual(obj2, obj3);
		assert.notEqual(obj3, obj);

		assert.equal(obj.next, null);
		assert.equal(obj2.next, null);
		assert.equal(obj3.next, null);

		instance.release(obj);
		instance.release(obj2);
		instance.release(obj3);

		// skip one
		instance.acquire();

		var obj4 = instance.acquire();
		var obj5 = instance.acquire();
		var obj6 = instance.acquire();

		assert(obj4 === obj, '4');
		assert(obj5 === obj2, '5');
		assert(obj6 === obj3, '6');
	},
});

// benchmarks
Deno.bench({
	name: 'func (taks instance func)',
	group: 'reusify',
	fn: () => {
		// @ts-expect-error:
		const queue = reusify(MyTask);

		for (let i = 0; i < 10_000; i++) {
			const task1 = queue.acquire();
			const task2 = queue.acquire();
			const task3 = queue.acquire();

			queue.release(task1);
			queue.release(task2);
			queue.release(task3);
		}
	},
});

Deno.bench({
	name: 'class (task instance func)',
	group: 'reusify',
	fn: () => {
		// @ts-expect-error:
		const queue = new Reusify(MyTask);
		
		for (let i = 0; i < 10_000; i++) {
			const task1 = queue.acquire();
			const task2 = queue.acquire();
			const task3 = queue.acquire();

			queue.release(task1);
			queue.release(task2);
			queue.release(task3);
		}
	},
});

Deno.bench({
	name: 'func (task instance class)',
	group: 'reusify',
	fn: () => {
		const queue = reusify(Task);

		for (let i = 0; i < 10_000; i++) {
			const task1 = queue.acquire();
			const task2 = queue.acquire();
			const task3 = queue.acquire();

			queue.release(task1);
			queue.release(task2);
			queue.release(task3);
		}
	},
});

Deno.bench({
	name: 'class (task instance class)',
	group: 'reusify',
	fn: () => {
		const queue = new Reusify(Task);
		
		for (let i = 0; i < 10_000; i++) {
			const task1 = queue.acquire();
			const task2 = queue.acquire();
			const task3 = queue.acquire();

			queue.release(task1);
			queue.release(task2);
			queue.release(task3);
		}
	},
});

Benchmarks results

    CPU | Apple M1 Max
Runtime | Deno 1.46.3 (aarch64-apple-darwin)


benchmark                     time/iter (avg)        iter/s      (min … max)           p75      p99     p995
----------------------------- ----------------------------- --------------------- --------------------------

group reusify
func (taks instance func)            130.3 µs         7,673 (128.2 µs … 253.1 µs) 131.3 µs 147.0 µs 153.8 µs
class (task instance func)            82.8 µs        12,070 ( 78.0 µs … 286.2 µs)  82.9 µs 107.2 µs 117.5 µs
func (task instance class)           181.7 µs         5,503 (172.0 µs … 372.2 µs) 183.1 µs 213.1 µs 228.7 µs
class (task instance class)          112.6 µs         8,881 (108.5 µs … 304.2 µs) 112.5 µs 152.2 µs 160.5 µs

summary
  class (task instance func)
     1.36x faster than class (task instance class)
     1.57x faster than func (taks instance func)
     2.19x faster than func (task instance class)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions