-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/benchmarks and memory optimizations (#103)
* Add benchmark program * Refactor ProcessorStatus from class to struct to reduce object memory allocation. * Refactor instrumentation to reduce object creation and increase performance. Make instrumentation optional and only activated when stats are shown in UI. * Refactor sprite collision code to not allocate objects on heap.
- Loading branch information
Showing
71 changed files
with
1,306 additions
and
357 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -282,3 +282,5 @@ nuget.config | |
|
||
.DS_Store | ||
.vscode/settings.json | ||
|
||
BenchmarkDotNet.Artifacts/ |
89 changes: 89 additions & 0 deletions
89
benchmarks/Highbyte.DotNet6502.Benchmarks/6502/Execute6502InstructionBenchmark.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System.Runtime.InteropServices; | ||
using BenchmarkDotNet.Attributes; | ||
using BenchmarkDotNet.Engines; | ||
using BenchmarkDotNet.Jobs; | ||
|
||
namespace Highbyte.DotNet6502.Benchmarks; | ||
|
||
[MemoryDiagnoser] // Memory diagnoser is used to measure memory allocations | ||
//[ShortRunJob] // WARNING: ShortRunJob is a custom job runs faster than normal, but is less accurate. | ||
//[DryJob] // DANGER: DryJob is a custom job that runs very quickly, but VERY INACCURATE. Use only to verify that benchmarks actual runs. | ||
public class Execute6502InstructionBenchmark | ||
{ | ||
private CPU _cpu = default!; | ||
private Memory _mem = default!; | ||
private ushort _startAddress; | ||
|
||
[Params(1)] | ||
//[Params(1, 10, 100)] | ||
public int NumberOfInstructionsToExecute; | ||
|
||
// GlobalSetup is executed once, or if Params are used: once per each Params value combination | ||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
Console.WriteLine("// " + "GlobalSetup"); | ||
|
||
_cpu = new CPU(); | ||
_mem = new Memory(); | ||
_startAddress = 0xc000; | ||
LoadProgram(_mem, _startAddress); | ||
|
||
} | ||
|
||
private void LoadProgram(Memory mem, ushort startAddress) | ||
{ | ||
var bytes = new byte[64 * 1024]; | ||
//// Random code (could include illegal opcodes) | ||
////new Random(42).NextBytes(bytes); | ||
//for (int i = 0; i < bytes.Length; i++) | ||
//{ | ||
// bytes[i] = (byte)OpCodeId.NOP; | ||
//} | ||
//mem.StoreData(0, bytes); | ||
|
||
//JSR + LDA_I + RTS + LDA_I + JMP-> 5 instructions | ||
// Code at start address | ||
ushort branchAddress = (ushort)(_startAddress + 0x10); | ||
var address = _startAddress; | ||
mem.WriteByte(ref address, OpCodeId.JSR); | ||
mem.WriteWord(ref address, branchAddress); | ||
mem.WriteByte(ref address, OpCodeId.LDA_I); | ||
mem.WriteByte(ref address, 42); | ||
mem.WriteByte(ref address, OpCodeId.JMP_ABS); | ||
mem.WriteWord(ref address, _startAddress); // Jump back to start address | ||
|
||
// Code at branch jsr address | ||
address = branchAddress; | ||
mem.WriteByte(ref address, OpCodeId.LDA_I); | ||
mem.WriteByte(ref address, 21); | ||
mem.WriteByte(ref address, OpCodeId.RTS); | ||
|
||
} | ||
|
||
//[GlobalCleanup] | ||
//public void GlobalCleanup() | ||
//{ | ||
// Console.WriteLine("// " + "GlobalCleanup"); | ||
//} | ||
|
||
[Benchmark] | ||
public void ExecIns() | ||
{ | ||
_cpu.PC = _startAddress; | ||
for (int i = 0; i < NumberOfInstructionsToExecute; i++) | ||
{ | ||
_cpu.ExecuteOneInstruction(_mem); | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void ExecInsMinimal() | ||
{ | ||
_cpu.PC = _startAddress; | ||
for (int i = 0; i < NumberOfInstructionsToExecute; i++) | ||
{ | ||
_cpu.ExecuteOneInstructionMinimal(_mem); | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
benchmarks/Highbyte.DotNet6502.Benchmarks/6502/ExecuteAll6502InstructionsBenchmark.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System.Runtime.InteropServices; | ||
using BenchmarkDotNet.Attributes; | ||
using BenchmarkDotNet.Engines; | ||
using BenchmarkDotNet.Jobs; | ||
|
||
namespace Highbyte.DotNet6502.Benchmarks; | ||
|
||
[MemoryDiagnoser] // Memory diagnoser is used to measure memory allocations | ||
//[ShortRunJob] // WARNING: ShortRunJob is a custom job runs faster than normal, but is less accurate. | ||
//[DryJob] // DANGER: DryJob is a custom job that runs very quickly, but VERY INACCURATE. Use only to verify that benchmarks actual runs. | ||
public class ExecuteAll6502InstructionsBenchmark | ||
{ | ||
private CPU _cpu = default!; | ||
private Memory _mem = default!; | ||
private ushort _startAddress; | ||
|
||
[ParamsSource(nameof(OpCodes))] | ||
public OpCodeId OpCode; | ||
|
||
public IEnumerable<OpCodeId> OpCodes => Enum.GetValues<OpCodeId>(); | ||
|
||
// GlobalSetup is executed once, or if Params are used: once per each Params value combination | ||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
Console.WriteLine("// " + "GlobalSetup"); | ||
|
||
_cpu = new CPU(); | ||
_mem = new Memory(); | ||
_startAddress = 0xc000; | ||
} | ||
|
||
//[GlobalCleanup] | ||
//public void GlobalCleanup() | ||
//{ | ||
// Console.WriteLine("// " + "GlobalCleanup"); | ||
//} | ||
|
||
//[Benchmark] | ||
//public void ExecIns() | ||
//{ | ||
// _cpu.PC = _startAddress; | ||
// _mem.WriteByte(_startAddress, (byte)OpCode); | ||
// _cpu.ExecuteOneInstruction(_mem); | ||
//} | ||
|
||
[Benchmark] | ||
public void ExecInsMinimal() | ||
{ | ||
_cpu.PC = _startAddress; | ||
_mem.WriteByte(_startAddress, (byte)OpCode); | ||
_cpu.ExecuteOneInstructionMinimal(_mem); | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteFrameBenchmark.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
using System.Runtime.InteropServices; | ||
using BenchmarkDotNet.Attributes; | ||
using BenchmarkDotNet.Engines; | ||
using BenchmarkDotNet.Jobs; | ||
using Highbyte.DotNet6502.Systems; | ||
using Highbyte.DotNet6502.Systems.Commodore64; | ||
using Highbyte.DotNet6502.Systems.Commodore64.Config; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
|
||
namespace Highbyte.DotNet6502.Benchmarks.Commodore64; | ||
|
||
[MemoryDiagnoser] // Memory diagnoser is used to measure memory allocations | ||
[ShortRunJob] // WARNING: ShortRunJob is a custom job runs faster than normal, but is less accurate. | ||
//[DryJob] // DANGER: DryJob is a custom job that runs very quickly, but VERY INACCURATE. Use only to verify that benchmarks actual runs. | ||
public class C64ExecuteFrameBenchmark | ||
{ | ||
private C64 _c64WithInstrumentation = default!; | ||
private C64 _c64WithoutInstrumentation = default!; | ||
private SystemRunner _systemRunnerWithInstrumentation = default!; | ||
private SystemRunner _systemRunnerWithoutInstrumentation = default!; | ||
private ushort _startAddress; | ||
|
||
[Params(1)] | ||
//[Params(1, 100, 1000)] | ||
public int NumberOfFramesToExecute; | ||
|
||
// GlobalSetup is executed once, or if Params are used: once per each Params value combination | ||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
Console.WriteLine("// " + "GlobalSetup"); | ||
|
||
var c64ConfigWithInstrumentation = new C64Config | ||
{ | ||
C64Model = "C64NTSC", // C64NTSC, C64PAL | ||
Vic2Model = "NTSC", // NTSC, NTSC_old, PAL | ||
LoadROMs = false, | ||
TimerMode = TimerMode.UpdateEachRasterLine, | ||
AudioEnabled = true, | ||
InstrumentationEnabled = true | ||
}; | ||
_c64WithInstrumentation = C64.BuildC64(c64ConfigWithInstrumentation, new NullLoggerFactory()); | ||
|
||
c64ConfigWithInstrumentation.InstrumentationEnabled = false; | ||
_c64WithoutInstrumentation = C64.BuildC64(c64ConfigWithInstrumentation, new NullLoggerFactory()); | ||
|
||
_startAddress = 0xc000; | ||
|
||
LoadProgram(_c64WithInstrumentation.Mem, _startAddress); | ||
LoadProgram(_c64WithoutInstrumentation.Mem, _startAddress); | ||
|
||
var systemRunnerBuilderWithInstrumentation = new SystemRunnerBuilder<C64, NullRenderContext, NullInputHandlerContext, NullAudioHandlerContext>(_c64WithInstrumentation); | ||
_systemRunnerWithInstrumentation = systemRunnerBuilderWithInstrumentation.Build(); | ||
|
||
var systemRunnerBuilderWithoutInstrumentation = new SystemRunnerBuilder<C64, NullRenderContext, NullInputHandlerContext, NullAudioHandlerContext>(_c64WithoutInstrumentation); | ||
_systemRunnerWithoutInstrumentation = systemRunnerBuilderWithoutInstrumentation.Build(); | ||
|
||
} | ||
|
||
private void LoadProgram(Memory mem, ushort startAddress) | ||
{ | ||
var bytes = new byte[64 * 1024]; | ||
// Random code (could include illegal opcodes) | ||
//new Random(42).NextBytes(bytes); | ||
for (var i = 0; i < bytes.Length; i++) | ||
{ | ||
bytes[i] = (byte)OpCodeId.RTS; | ||
} | ||
mem.StoreData(0, bytes); | ||
|
||
// JSR + LDA_I + RTS + LDA_I + JMP -> 5 instructions | ||
// Code at start address | ||
//ushort branchAddress = (ushort)(_startAddress + 0x10); | ||
//var address = _startAddress; | ||
//mem.WriteByte(ref address, OpCodeId.JSR); | ||
//mem.WriteWord(ref address, branchAddress); | ||
//mem.WriteByte(ref address, OpCodeId.LDA_I); | ||
//mem.WriteByte(ref address, 42); | ||
//mem.WriteByte(ref address, OpCodeId.JMP_ABS); | ||
//mem.WriteWord(ref address, _startAddress); // Jump back to start address | ||
|
||
//// Code at branch jsr address | ||
//address = branchAddress; | ||
//mem.WriteByte(ref address, OpCodeId.LDA_I); | ||
//mem.WriteByte(ref address, 21); | ||
//mem.WriteByte(ref address, OpCodeId.RTS); | ||
|
||
} | ||
|
||
//[GlobalCleanup] | ||
//public void GlobalCleanup() | ||
//{ | ||
// Console.WriteLine("// " + "GlobalCleanup"); | ||
//} | ||
|
||
[Benchmark(Baseline = true)] | ||
public void ExecFrameWithoutInstrumentation() | ||
{ | ||
_c64WithoutInstrumentation.CPU.PC = _startAddress; | ||
for (var i = 0; i < NumberOfFramesToExecute; i++) | ||
{ | ||
var execEvaluatorTriggerResult = _c64WithoutInstrumentation.ExecuteOneFrame(_systemRunnerWithoutInstrumentation); | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void ExecFrameWithInstrumentation() | ||
{ | ||
_c64WithInstrumentation.CPU.PC = _startAddress; | ||
for (var i = 0; i < NumberOfFramesToExecute; i++) | ||
{ | ||
var execEvaluatorTriggerResult = _c64WithInstrumentation.ExecuteOneFrame(_systemRunnerWithInstrumentation); | ||
} | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteInstructionBenchmark.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
using System.Runtime.InteropServices; | ||
using BenchmarkDotNet.Attributes; | ||
using BenchmarkDotNet.Engines; | ||
using BenchmarkDotNet.Jobs; | ||
using Highbyte.DotNet6502.Systems; | ||
using Highbyte.DotNet6502.Systems.Commodore64; | ||
using Highbyte.DotNet6502.Systems.Commodore64.Config; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
|
||
namespace Highbyte.DotNet6502.Benchmarks.Commodore64; | ||
|
||
[MemoryDiagnoser] // Memory diagnoser is used to measure memory allocations | ||
//[ShortRunJob] // WARNING: ShortRunJob is a custom job runs faster than normal, but is less accurate. | ||
//[DryJob] // DANGER: DryJob is a custom job that runs very quickly, but VERY INACCURATE. Use only to verify that benchmarks actual runs. | ||
public class C64ExecuteInstructionBenchmark | ||
{ | ||
private C64 _c64WithInstrumentation = default!; | ||
private C64 _c64WithoutInstrumentation = default!; | ||
private SystemRunner _systemRunnerWithInstrumentation = default!; | ||
private SystemRunner _systemRunnerWithoutInstrumentation = default!; | ||
private ushort _startAddress; | ||
|
||
//[Params(1)] | ||
[Params(1, 100, 1000)] | ||
public int NumberOfInstructionsToExecute; | ||
|
||
// GlobalSetup is executed once, or if Params are used: once per each Params value combination | ||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
Console.WriteLine("// " + "GlobalSetup"); | ||
|
||
var c64ConfigWithInstrumentation = new C64Config | ||
{ | ||
C64Model = "C64NTSC", // C64NTSC, C64PAL | ||
Vic2Model = "NTSC", // NTSC, NTSC_old, PAL | ||
LoadROMs = false, | ||
TimerMode = TimerMode.UpdateEachRasterLine, | ||
AudioEnabled = true, | ||
InstrumentationEnabled = true | ||
}; | ||
_c64WithInstrumentation = C64.BuildC64(c64ConfigWithInstrumentation, new NullLoggerFactory()); | ||
|
||
c64ConfigWithInstrumentation.InstrumentationEnabled = false; | ||
_c64WithoutInstrumentation = C64.BuildC64(c64ConfigWithInstrumentation, new NullLoggerFactory()); | ||
|
||
_startAddress = 0xc000; | ||
|
||
LoadProgram(_c64WithInstrumentation.Mem, _startAddress); | ||
LoadProgram(_c64WithoutInstrumentation.Mem, _startAddress); | ||
|
||
var systemRunnerBuilderWithInstrumentation = new SystemRunnerBuilder<C64, NullRenderContext, NullInputHandlerContext, NullAudioHandlerContext>(_c64WithInstrumentation); | ||
_systemRunnerWithInstrumentation = systemRunnerBuilderWithInstrumentation.Build(); | ||
|
||
var systemRunnerBuilderWithoutInstrumentation = new SystemRunnerBuilder<C64, NullRenderContext, NullInputHandlerContext, NullAudioHandlerContext>(_c64WithoutInstrumentation); | ||
_systemRunnerWithoutInstrumentation = systemRunnerBuilderWithoutInstrumentation.Build(); | ||
|
||
} | ||
|
||
private void LoadProgram(Memory mem, ushort startAddress) | ||
{ | ||
var bytes = new byte[64 * 1024]; | ||
// Random code (could include illegal opcodes) | ||
//new Random(42).NextBytes(bytes); | ||
for (var i = 0; i < bytes.Length; i++) | ||
{ | ||
bytes[i] = (byte)OpCodeId.RTS; | ||
} | ||
mem.StoreData(0, bytes); | ||
|
||
// JSR + LDA_I + RTS + LDA_I + JMP -> 5 instructions | ||
// Code at start address | ||
//ushort branchAddress = (ushort)(_startAddress + 0x10); | ||
//var address = _startAddress; | ||
//mem.WriteByte(ref address, OpCodeId.JSR); | ||
//mem.WriteWord(ref address, branchAddress); | ||
//mem.WriteByte(ref address, OpCodeId.LDA_I); | ||
//mem.WriteByte(ref address, 42); | ||
//mem.WriteByte(ref address, OpCodeId.JMP_ABS); | ||
//mem.WriteWord(ref address, _startAddress); // Jump back to start address | ||
|
||
//// Code at branch jsr address | ||
//address = branchAddress; | ||
//mem.WriteByte(ref address, OpCodeId.LDA_I); | ||
//mem.WriteByte(ref address, 21); | ||
//mem.WriteByte(ref address, OpCodeId.RTS); | ||
|
||
} | ||
|
||
//[GlobalCleanup] | ||
//public void GlobalCleanup() | ||
//{ | ||
// Console.WriteLine("// " + "GlobalCleanup"); | ||
//} | ||
|
||
[Benchmark(Baseline = true)] | ||
public void ExecInsWithoutInstrumentation() | ||
{ | ||
_c64WithoutInstrumentation.CPU.PC = _startAddress; | ||
for (var i = 0; i < NumberOfInstructionsToExecute; i++) | ||
{ | ||
_c64WithoutInstrumentation.ExecuteOneInstruction(_systemRunnerWithoutInstrumentation, out InstructionExecResult instructionExecResult); | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void ExecInsWithInstrumentation() | ||
{ | ||
_c64WithInstrumentation.CPU.PC = _startAddress; | ||
for (var i = 0; i < NumberOfInstructionsToExecute; i++) | ||
{ | ||
_c64WithInstrumentation.ExecuteOneInstruction(_systemRunnerWithInstrumentation, out InstructionExecResult instructionExecResult); | ||
} | ||
} | ||
} |
Oops, something went wrong.