Skip to content

Commit 968a6ee

Browse files
committed
perf(cpu): speed up event system
Use a linked list instead of array. This makes the simulator runs almost twice as fast in case of timers with prescaler of 1, e.g. when using the TVout library. In addition, we use a pool of clock event objects to avoid expensive GCs.
1 parent 548adfd commit 968a6ee

File tree

1 file changed

+47
-25
lines changed

1 file changed

+47
-25
lines changed

src/cpu/cpu.ts

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type AVRClockEventCallback = () => void;
6161
interface AVRClockEventEntry {
6262
cycles: number;
6363
callback: AVRClockEventCallback;
64+
next: AVRClockEventEntry | null;
6465
}
6566

6667
export class CPU implements ICPU {
@@ -71,7 +72,8 @@ export class CPU implements ICPU {
7172
readonly readHooks: CPUMemoryReadHooks = [];
7273
readonly writeHooks: CPUMemoryHooks = [];
7374
private readonly pendingInterrupts: AVRInterruptConfig[] = [];
74-
private readonly clockEvents: AVRClockEventEntry[] = [];
75+
private nextClockEvent: AVRClockEventEntry | null = null;
76+
private readonly clockEventPool: AVRClockEventEntry[] = []; // helps avoid garbage collection
7577
readonly pc22Bits = this.progBytes.length > 0x20000;
7678

7779
// This lets the Timer Compare output override GPIO pins:
@@ -80,7 +82,6 @@ export class CPU implements ICPU {
8082
pc: u32 = 0;
8183
cycles: u32 = 0;
8284
nextInterrupt: i16 = -1;
83-
private nextClockEvent: u32 = 0;
8485

8586
constructor(public progMem: Uint16Array, private sramBytes = 8192) {
8687
this.reset();
@@ -175,22 +176,25 @@ export class CPU implements ICPU {
175176
}
176177

177178
addClockEvent(callback: AVRClockEventCallback, cycles: number) {
178-
const entry = { cycles: this.cycles + Math.max(1, cycles), callback };
179-
// Add the new entry while keeping the array sorted
180-
const { clockEvents } = this;
181-
if (!clockEvents.length || clockEvents[clockEvents.length - 1].cycles <= entry.cycles) {
182-
clockEvents.push(entry);
183-
} else if (clockEvents[0].cycles >= entry.cycles) {
184-
clockEvents.unshift(entry);
179+
const { clockEventPool } = this;
180+
cycles = this.cycles + Math.max(1, cycles);
181+
const maybeEntry = clockEventPool.pop();
182+
const entry: AVRClockEventEntry = maybeEntry ?? { cycles, callback, next: null };
183+
entry.cycles = cycles;
184+
entry.callback = callback;
185+
let { nextClockEvent: clockEvent } = this;
186+
let lastItem = null;
187+
while (clockEvent && clockEvent.cycles < cycles) {
188+
lastItem = clockEvent;
189+
clockEvent = clockEvent.next;
190+
}
191+
if (lastItem) {
192+
lastItem.next = entry;
193+
entry.next = clockEvent;
185194
} else {
186-
for (let i = 1; i < clockEvents.length; i++) {
187-
if (clockEvents[i].cycles >= entry.cycles) {
188-
clockEvents.splice(i, 0, entry);
189-
break;
190-
}
191-
}
195+
this.nextClockEvent = entry;
196+
entry.next = clockEvent;
192197
}
193-
this.nextClockEvent = this.clockEvents[0].cycles;
194198
return callback;
195199
}
196200

@@ -203,20 +207,38 @@ export class CPU implements ICPU {
203207
}
204208

205209
clearClockEvent(callback: AVRClockEventCallback) {
206-
const index = this.clockEvents.findIndex((item) => item.callback === callback);
207-
if (index >= 0) {
208-
this.clockEvents.splice(index, 1);
209-
this.nextClockEvent = this.clockEvents[0]?.cycles ?? 0;
210-
return true;
210+
let { nextClockEvent: clockEvent } = this;
211+
if (!clockEvent) {
212+
return false;
213+
}
214+
const { clockEventPool } = this;
215+
let lastItem = null;
216+
while (clockEvent) {
217+
if (clockEvent.callback === callback) {
218+
if (lastItem) {
219+
lastItem.next = clockEvent.next;
220+
} else {
221+
this.nextClockEvent = clockEvent.next;
222+
}
223+
if (clockEventPool.length < 10) {
224+
clockEventPool.push(clockEvent);
225+
}
226+
return true;
227+
}
228+
lastItem = clockEvent;
229+
clockEvent = clockEvent.next;
211230
}
212231
return false;
213232
}
214233

215234
tick() {
216-
const { nextClockEvent, clockEvents } = this;
217-
if (nextClockEvent && nextClockEvent <= this.cycles) {
218-
clockEvents.shift()?.callback();
219-
this.nextClockEvent = clockEvents[0]?.cycles ?? 0;
235+
const { nextClockEvent } = this;
236+
if (nextClockEvent && nextClockEvent.cycles <= this.cycles) {
237+
nextClockEvent.callback();
238+
this.nextClockEvent = nextClockEvent.next;
239+
if (this.clockEventPool.length < 10) {
240+
this.clockEventPool.push(nextClockEvent);
241+
}
220242
}
221243

222244
const { nextInterrupt } = this;

0 commit comments

Comments
 (0)