mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Optimize Emitter.fire for 0 and 1 listeners
Before:
Context "Emitter.fire()"
Context "0 listeners"
Case "#1" : 1 runs - average throughput: 97.77 MB/s
Context "1 listener"
Case "#1" : 1 runs - average throughput: 49.30 MB/s
Context "2 listeners"
Case "#1" : 1 runs - average throughput: 35.37 MB/s
Context "5 listeners"
Case "#1" : 1 runs - average throughput: 15.32 MB/s
After:
Context "Emitter.fire()"
Context "0 listeners"
Case "#1" : 1 runs - average throughput: 376.94 MB/s
Context "1 listener"
Case "#1" : 1 runs - average throughput: 84.29 MB/s
Context "2 listeners"
Case "#1" : 1 runs - average throughput: 32.36 MB/s
Context "5 listeners"
Case "#1" : 1 runs - average throughput: 15.93 MB/s
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2024-2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { Emitter } from 'common/Event';
|
||||
|
||||
describe('Emitter', () => {
|
||||
it('should fire with 0 listeners without error', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
emitter.fire(42);
|
||||
});
|
||||
|
||||
it('should fire with 1 listener', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
let received: number | undefined;
|
||||
emitter.event(e => { received = e; });
|
||||
emitter.fire(42);
|
||||
assert.strictEqual(received, 42);
|
||||
});
|
||||
|
||||
it('should fire with 1 listener using thisArgs', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
const obj = { value: 0, handler(e: number) { this.value = e; } };
|
||||
emitter.event(obj.handler, obj);
|
||||
emitter.fire(42);
|
||||
assert.strictEqual(obj.value, 42);
|
||||
});
|
||||
|
||||
it('should fire with multiple listeners', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
const results: number[] = [];
|
||||
emitter.event(e => results.push(e * 1));
|
||||
emitter.event(e => results.push(e * 2));
|
||||
emitter.event(e => results.push(e * 3));
|
||||
emitter.fire(10);
|
||||
assert.deepEqual(results, [10, 20, 30]);
|
||||
});
|
||||
|
||||
it('should handle listener removal during fire', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
const results: string[] = [];
|
||||
emitter.event(() => results.push('first'));
|
||||
const disposable = emitter.event(() => {
|
||||
results.push('second');
|
||||
disposable.dispose();
|
||||
});
|
||||
emitter.event(() => results.push('third'));
|
||||
emitter.fire(1);
|
||||
assert.deepEqual(results, ['first', 'second', 'third']);
|
||||
});
|
||||
|
||||
it('should not fire after dispose', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
let called = false;
|
||||
emitter.event(() => { called = true; });
|
||||
emitter.dispose();
|
||||
emitter.fire(42);
|
||||
assert.strictEqual(called, false);
|
||||
});
|
||||
|
||||
it('should allow disposing a listener', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
let count = 0;
|
||||
const disposable = emitter.event(() => { count++; });
|
||||
emitter.fire(1);
|
||||
disposable.dispose();
|
||||
emitter.fire(2);
|
||||
assert.strictEqual(count, 1);
|
||||
});
|
||||
});
|
||||
+14
-4
@@ -53,10 +53,20 @@ export class Emitter<T> {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
// Snapshot listeners to allow modifications during iteration
|
||||
const listeners = this._listeners.slice();
|
||||
for (const { fn, thisArgs } of listeners) {
|
||||
fn.call(thisArgs, event);
|
||||
switch (this._listeners.length) {
|
||||
case 0: return;
|
||||
case 1: {
|
||||
const { fn, thisArgs } = this._listeners[0];
|
||||
fn.call(thisArgs, event);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
// Snapshot listeners to allow modifications during iteration (2+ listeners)
|
||||
const listeners = this._listeners.slice();
|
||||
for (const { fn, thisArgs } of listeners) {
|
||||
fn.call(thisArgs, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
||||
import { Emitter } from 'common/Event';
|
||||
|
||||
const ITERATIONS = 1_000_000;
|
||||
|
||||
perfContext('Emitter.fire()', () => {
|
||||
perfContext('0 listeners', () => {
|
||||
let emitter: Emitter<number>;
|
||||
before(() => {
|
||||
emitter = new Emitter<number>();
|
||||
});
|
||||
new ThroughputRuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
emitter.fire(i);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageThroughput();
|
||||
});
|
||||
|
||||
perfContext('1 listener', () => {
|
||||
let emitter: Emitter<number>;
|
||||
let sum = 0;
|
||||
before(() => {
|
||||
emitter = new Emitter<number>();
|
||||
emitter.event(e => { sum += e; });
|
||||
});
|
||||
new ThroughputRuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
emitter.fire(i);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageThroughput();
|
||||
});
|
||||
|
||||
perfContext('2 listeners', () => {
|
||||
let emitter: Emitter<number>;
|
||||
let sum = 0;
|
||||
before(() => {
|
||||
emitter = new Emitter<number>();
|
||||
emitter.event(e => { sum += e; });
|
||||
emitter.event(e => { sum += e * 2; });
|
||||
});
|
||||
new ThroughputRuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
emitter.fire(i);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageThroughput();
|
||||
});
|
||||
|
||||
perfContext('5 listeners', () => {
|
||||
let emitter: Emitter<number>;
|
||||
let sum = 0;
|
||||
before(() => {
|
||||
emitter = new Emitter<number>();
|
||||
for (let j = 0; j < 5; j++) {
|
||||
emitter.event(e => { sum += e; });
|
||||
}
|
||||
});
|
||||
new ThroughputRuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
emitter.fire(i);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageThroughput();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user