mcollina / reusify

Reuse objects and functions with style
MIT License
162 stars 18 forks source link

perf: optimization perfomance #14

Open PandaWorker opened 1 month ago

PandaWorker commented 1 month ago

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)
mcollina commented 2 days ago

Wow, seems nicer! Would you like to send a PR?