Skip to content

Commit 117d8de

Browse files
committed
Implement runner execution function without async
This improves performance on firefox by ~50%
1 parent 3b7a122 commit 117d8de

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

demo/src/execute.ts

+64-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ import { loadHex } from './intelhex';
1515
// ATmega328p params
1616
const FLASH = 0x8000;
1717

18+
const timeouts: Function[] = new Array<Function>();
19+
const messageName = 'zero-timeout-message';
20+
21+
function handleMessage(event: MessageEvent) {
22+
if (event.source == window && event.data == messageName) {
23+
event.stopPropagation();
24+
if (timeouts.length > 0) {
25+
const fn = timeouts.shift();
26+
if (fn !== undefined) {
27+
fn();
28+
}
29+
}
30+
}
31+
}
32+
33+
function setZeroTimeout(fn: Function) {
34+
timeouts.push(fn);
35+
window.postMessage(messageName, '*');
36+
}
37+
38+
window.addEventListener('message', handleMessage, true);
39+
1840
export class AVRRunner {
1941
readonly program = new Uint16Array(FLASH);
2042
readonly cpu: CPU;
@@ -24,8 +46,10 @@ export class AVRRunner {
2446
readonly portD: AVRIOPort;
2547
readonly usart: AVRUSART;
2648
readonly speed = 16e6; // 16 MHZ
49+
readonly workUnitCycles = 100000;
2750

2851
private stopped = false;
52+
private nextTick = 0;
2953

3054
constructor(hex: string) {
3155
loadHex(hex, new Uint8Array(this.program.buffer));
@@ -41,13 +65,21 @@ export class AVRRunner {
4165
this.stopped = false;
4266
const workUnitCycles = 500000;
4367
let nextTick = this.cpu.cycles + workUnitCycles;
68+
4469
for (;;) {
4570
avrInstruction(this.cpu);
4671
this.timer.tick();
4772
this.usart.tick();
4873
if (this.cpu.cycles >= nextTick) {
4974
callback(this.cpu);
50-
await new Promise((resolve) => setTimeout(resolve, 0));
75+
const prevTime = performance.now();
76+
await new Promise((resolve) => setZeroTimeout(resolve));
77+
//await new Promise((resolve) => setTimeout(resolve, 0));
78+
//await new Promise((resolve) => window.requestAnimationFrame(resolve))
79+
const afterTime = performance.now();
80+
81+
console.log('execute await timing', afterTime - prevTime, 'ms');
82+
5183
if (this.stopped) {
5284
break;
5385
}
@@ -56,6 +88,37 @@ export class AVRRunner {
5688
}
5789
}
5890

91+
// Reimplementatio
92+
executeNoAsync(callback: (cpu: CPU) => void) {
93+
// Initialize on first function start
94+
// Not sure why this is useful since this.cpu.cycles is always = to 0 on start
95+
// Maybe for the possibility to stop and resume execution ?
96+
if (this.nextTick === 0) {
97+
this.nextTick = this.cpu.cycles + this.workUnitCycles;
98+
}
99+
100+
for (;;) {
101+
avrInstruction(this.cpu);
102+
this.timer.tick();
103+
this.usart.tick();
104+
if (this.cpu.cycles >= this.nextTick) {
105+
callback(this.cpu);
106+
107+
if (this.stopped) {
108+
return;
109+
}
110+
111+
this.nextTick += this.workUnitCycles;
112+
113+
// We've done our chunk of work, exit the loop
114+
break;
115+
}
116+
}
117+
118+
// Schedule next execution
119+
setZeroTimeout(() => this.executeNoAsync(callback));
120+
}
121+
59122
stop() {
60123
this.stopped = true;
61124
}

demo/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function executeProgram(hex: string) {
6969
serialOutputText.textContent += String.fromCharCode(value);
7070
};
7171
const cpuPerf = new CPUPerformance(runner.cpu, MHZ);
72-
runner.execute((cpu) => {
72+
runner.executeNoAsync((cpu) => {
7373
const time = formatTime(cpu.cycles / MHZ);
7474
const speed = (cpuPerf.update() * 100).toFixed(0);
7575
statusLabel.textContent = `Simulation time: ${time} (${speed}%)`;

0 commit comments

Comments
 (0)