apps/bbench - CPU, graphics, memory and disk benchmark for KolibriOS (#561)
- Add `bbench` to IMG and System Panel - Universal benchmark with 17 tests for CPU, graphics, memory and disk - Tests can be toggled and configured - Generates derailed reports in HTML - Remove `MGB` and `FSPEED` from System Panel, move them from IMG to ISO --------- Co-authored-by: Burer <burer@kolibrios.org> Reviewed-on: #561 Reviewed-by: bad_Dr3dd0x <1702+bad_dr3dd0x@noreply.localhost> Reviewed-by: Burer <burer@kolibrios.org> Co-authored-by: leency <lipatov.kiril@gmail.com>
This commit was merged in pull request #561.
This commit is contained in:
+3
-2
@@ -428,7 +428,6 @@ tup.append_table(img_files, {
|
||||
{"DISPTEST", VAR_PROGS .. "/testing/disptest/disptest"},
|
||||
{"END", VAR_PROGS .. "/system/end/light/end"},
|
||||
{"ESKIN", VAR_PROGS .. "/system/eskin/eskin"},
|
||||
{"FSPEED", VAR_PROGS .. "/testing/fspeed/fspeed"},
|
||||
{"GMON", VAR_PROGS .. "/system/gmon/gmon"},
|
||||
{"HDD_INFO", VAR_PROGS .. "/system/hdd_info/hdd_info"},
|
||||
{"KBD", VAR_PROGS .. "/testing/kbd/kbd"},
|
||||
@@ -437,7 +436,6 @@ tup.append_table(img_files, {
|
||||
{"LOADDRV", VAR_PROGS .. "/system/loaddrv/loaddrv"},
|
||||
{"MAGNIFY", VAR_PROGS .. "/system/magnify/magnify"},
|
||||
{"MADMOUSE", VAR_PROGS .. "/other/madmouse/madmouse"},
|
||||
{"MGB", VAR_PROGS .. "/testing/mgb/mgb"},
|
||||
{"MOUSEMUL", VAR_PROGS .. "/system/mousemul/mousemul"},
|
||||
{"MOUSEPOS", VAR_PROGS .. "/other/mousepos/mousepos"},
|
||||
{"MYKEY", VAR_PROGS .. "/system/MyKey/MyKey"},
|
||||
@@ -642,6 +640,8 @@ tup.append_table(extra_files, {
|
||||
{"kolibrios/media/zsea/plugins/rotate.obj", VAR_PROGS .. "/media/zsea/plugins/rotate/rotate.obj"},
|
||||
{"kolibrios/media/zsea/plugins/scaling.obj", VAR_PROGS .. "/media/zsea/plugins/scaling/scaling.obj"},
|
||||
{"kolibrios/utils/AMDtemp", VAR_PROGS .. "/system/amd_temp_view/AMDtemp"},
|
||||
{"kolibrios/utils/fspeed", VAR_PROGS .. "/testing/fspeed/fspeed"},
|
||||
{"kolibrios/utils/mgb", VAR_PROGS .. "/testing/mgb/mgb"},
|
||||
{"kolibrios/utils/kfm/kfm", VAR_PROGS .. "/fs/kfm/kfm"},
|
||||
{"kolibrios/utils/tedit/t_edit", VAR_PROGS .. "/other/t_edit/t_edit"},
|
||||
{"kolibrios/3D/blocks/block.bin", VAR_PROGS .. "/bcc32/games/blocks/block.bin"},
|
||||
@@ -683,6 +683,7 @@ tup.append_table(img_files, {
|
||||
{"EASYSHOT", VAR_PROGS .. "/cmm/misc/easyshot.com"},
|
||||
{"MOUSECFG", VAR_PROGS .. "/cmm/mousecfg/mousecfg.com"},
|
||||
{"BARSCFG", VAR_PROGS .. "/cmm/barscfg/barscfg.com"},
|
||||
{"BBENCH", VAR_PROGS .. "/cmm/bbench/bbench.com"},
|
||||
{"SEARCH", VAR_PROGS .. "/cmm/misc/search.com"},
|
||||
{"SYSPANEL", VAR_PROGS .. "/cmm/misc/software_widget.com"},
|
||||
{"SYSMON", VAR_PROGS .. "/cmm/sysmon/sysmon.com"},
|
||||
|
||||
@@ -35,7 +35,6 @@ Console Board=develop/dbgboard,19
|
||||
PCI Devices=pcidev,38
|
||||
CPU Info=cpuid,37
|
||||
Display Test=disptest,113
|
||||
Graphic Bench=mgb,121
|
||||
Benchmark=bbench,121
|
||||
HDD Info=hdd_info,50
|
||||
File Speed=fspeed,45
|
||||
Network Speed=network/dl|-test,51
|
||||
|
||||
@@ -35,7 +35,6 @@ Consola de depuracion=develop/dbgboard,19
|
||||
Dispositivos PCI=pcidev,38
|
||||
CPU Info=cpuid,37
|
||||
Prueba de pantalla=disptest,113
|
||||
Rendimiento grafico=mgb,121
|
||||
Benchmark=bbench,121
|
||||
Info de disco=hdd_info,50
|
||||
Velocidad FS=fspeed,45
|
||||
Velocidad de red=network/dl|-test,51
|
||||
|
||||
@@ -35,7 +35,6 @@ AMDtemp=/k/utils/AMDtemp,128
|
||||
Устройства PCI=pcidev,38
|
||||
CPU Info=cpuid,37
|
||||
Тест экрана=disptest,113
|
||||
Скорость графики=mgb,121
|
||||
Benchmark=bbench,121
|
||||
Информация о дисках=hdd_info,50
|
||||
Скорость ФС=fspeed,45
|
||||
Скорость сети=network/dl|-test,51
|
||||
@@ -0,0 +1,6 @@
|
||||
if tup.getconfig("NO_CMM") ~= "" then return end
|
||||
if tup.getconfig("LANG") == "ru_RU"
|
||||
then C_LANG = "LANG_RUS"
|
||||
else C_LANG = "LANG_ENG" -- this includes default case without config
|
||||
end
|
||||
tup.rule("bbench.c", "c-- /D=$(C_LANG) /OPATH=%o %f" .. tup.getconfig("KPACK_CMD"), "bbench.com")
|
||||
@@ -0,0 +1,616 @@
|
||||
//========================================================//
|
||||
// //
|
||||
// BirdBench - a modular system benchmark //
|
||||
// The window is a launcher: tick the tests you want, //
|
||||
// press Run - results open as an HTML report in //
|
||||
// WebView (clearer there than in the tiny window). //
|
||||
// //
|
||||
// Add a test : edit tests_cpu.h / tests_gpu.h / ... //
|
||||
// Add a rival : edit cpudb.h //
|
||||
// //
|
||||
//========================================================//
|
||||
|
||||
#define MEMSIZE 4096*1536 // ~6 MB heap (2+2 MB work buffers + canvas)
|
||||
|
||||
#include "../lib/kolibri.h" // must be first (sets entry point)
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/strings.h"
|
||||
#include "../lib/fs.h"
|
||||
#include "../lib/io.h"
|
||||
#include "../lib/gui.h"
|
||||
#include "../lib/system.h"
|
||||
#include "../lib/gui/checkbox.h"
|
||||
#include "../lib/gui/menu.h"
|
||||
|
||||
#include "bbench.h"
|
||||
#include "tests_cpu.h"
|
||||
#include "tests_gpu.h"
|
||||
#include "tests_disk.h"
|
||||
#include "cpudb.h"
|
||||
|
||||
//---------------- layout ----------------//
|
||||
#define PAD 16
|
||||
#define COL2_X 170 // second column (Graphics)
|
||||
#define COL3_X 328 // third column (Disk)
|
||||
#define HEADER_H 42
|
||||
#define ROW_H 22
|
||||
#define WIN_W GCV_W
|
||||
#define WIN_H GCV_H + HEADER_H
|
||||
|
||||
#define BUTTON_Y WIN_H - 62
|
||||
#define BUTTON_W 70
|
||||
#define BUTTON_H 24
|
||||
|
||||
#define STATUSBAR_Y WIN_H - 25
|
||||
|
||||
#define BAR_FULL 3000 // score that fills a full HTML bar
|
||||
|
||||
#define BTN_RUN 30
|
||||
#define BTN_ALL 33
|
||||
#define BTN_NONE 34
|
||||
#define BTN_DISK_DROP 39 // disk dropdown toggle
|
||||
#define MAX_DISKS 20
|
||||
#define BTN_SECT_BASE 50 // 50..52 section masters
|
||||
#define BTN_CHECK_BASE 60 // 60.. per-test checkboxes
|
||||
|
||||
checkbox cb; // reused as a renderer for every checkbox
|
||||
|
||||
byte t_enabled[MAX_TESTS];
|
||||
dword sums[SECT_NUM];
|
||||
dword cnts[SECT_NUM];
|
||||
|
||||
char sysinfo[] = "CPU: %d MHz RAM: %d MB Screen: %dx%d@%db";
|
||||
char revinfo[] = " BB Revision: %d";
|
||||
|
||||
//---------------- disk list (LMENU dropdown) ----------------//
|
||||
char disk_store[MAX_DISKS*24];
|
||||
dword disk_dirp[MAX_DISKS];
|
||||
int disk_count = 0;
|
||||
int disk_sel = 0;
|
||||
byte disk_menu_open = 0; // an LMENU popup is currently open
|
||||
char disk_menu[MAX_DISKS*26]; // "\n"-separated item list for /sys/develop/menu
|
||||
dword disk_drop_x, disk_drop_y;
|
||||
char probe_buf[16];
|
||||
|
||||
int DiskWritable(dword dir)
|
||||
{
|
||||
char pf[80];
|
||||
dword r;
|
||||
strcpy(#pf, dir); strcat(#pf, "/bbprobe.tmp");
|
||||
r = FileWrite(#pf, #probe_buf, 16);
|
||||
if (r==0) dk_delete(#pf);
|
||||
if (r==0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AddDisk(dword dir)
|
||||
{
|
||||
dword slot;
|
||||
if (disk_count >= MAX_DISKS) return;
|
||||
slot = disk_count * 24; slot = slot + #disk_store;
|
||||
strcpy(slot, dir);
|
||||
disk_dirp[disk_count] = slot;
|
||||
disk_count++;
|
||||
}
|
||||
|
||||
void TryDisk(dword dir) { if (DiskWritable(dir)) AddDisk(dir); }
|
||||
|
||||
void ScanDisks() // enumerate real mounts under "/", keep writable
|
||||
{
|
||||
int i, j;
|
||||
dword nm, l;
|
||||
char base[48];
|
||||
char dig[2];
|
||||
byte c0, c1, skip;
|
||||
disk_count = 0;
|
||||
dig[1] = 0;
|
||||
io.dir.load("/", DIR_ONLYREAL);
|
||||
for (i=0; i<io.dir.count; i++) {
|
||||
nm = io.dir.position(i);
|
||||
c0 = DSBYTE[nm]; c1 = DSBYTE[nm+1];
|
||||
skip = 0;
|
||||
l = strlen(nm);
|
||||
if (l > 12) skip = 1; // path slots are fixed 24 bytes
|
||||
if (c0=='c') && (c1=='d') skip = 1; // CD (read-only)
|
||||
if (c0=='f') && (c1=='d') skip = 1; // floppy
|
||||
if (c0=='r') && (c1=='d') skip = 1; // ramdisk /rd (system)
|
||||
if (!skip) {
|
||||
// whole-disk FS (rare: unpartitioned media)
|
||||
strcpy(#base, "/"); strcat(#base, nm);
|
||||
TryDisk(#base);
|
||||
// probe partitions /1../9 - the writable one is NOT always #1
|
||||
// (e.g. #1 = EFI/NTFS-reserved, data partition = #2 or #3)
|
||||
for (j=1; j<=9; j++) {
|
||||
dig[0] = j + '0';
|
||||
strcpy(#base, "/"); strcat(#base, nm);
|
||||
strcat(#base, "/"); strcat(#base, #dig);
|
||||
TryDisk(#base);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (disk_count < 1) AddDisk("/tmp0/1");
|
||||
disk_sel = 0;
|
||||
disk_dir = disk_dirp[0];
|
||||
disk_menu[0] = 0; // "\n"-joined paths for the LMENU app
|
||||
for (i=0; i<disk_count; i++) {
|
||||
strcat(#disk_menu, disk_dirp[i]);
|
||||
if (i < disk_count-1) strcat(#disk_menu, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
//---------------- helpers ----------------//
|
||||
void bench_exit() // single exit point: never leave bbtst.tmp behind
|
||||
{
|
||||
Disk_Cleanup();
|
||||
ExitProcess();
|
||||
}
|
||||
|
||||
void FlatButton(dword x, id, bg, tc, label)
|
||||
{
|
||||
DefineButton(x, BUTTON_Y, BUTTON_W, BUTTON_H - 1, id, bg);
|
||||
WriteText(-strlen(label)*8 + BUTTON_W / 2 + x,
|
||||
BUTTON_H - 16 / 2 + BUTTON_Y, 0x90, tc, label);
|
||||
}
|
||||
|
||||
//---------------- checkbox model ----------------//
|
||||
byte SectionAllOn(dword sect)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<t_count; i++) if (t_sect[i]==sect) && (!t_enabled[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ToggleSection(dword sect)
|
||||
{
|
||||
int i;
|
||||
byte v;
|
||||
v = SectionAllOn(sect); v = v ^ 1;
|
||||
for (i=0; i<t_count; i++) if (t_sect[i]==sect) t_enabled[i] = v;
|
||||
}
|
||||
|
||||
void SetAll(byte v)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<t_count; i++) t_enabled[i] = v;
|
||||
}
|
||||
|
||||
//========================================================//
|
||||
// HTML export //
|
||||
//========================================================//
|
||||
#define ICON_CPU 48
|
||||
#define ICON_GPU 52
|
||||
#define ICON_DISK 50
|
||||
|
||||
#define EBUF_SIZE 16384 // report buffer; a full 48-test run needs ~6 KB
|
||||
#define BAR_W 26 // bar width in characters, same for every row
|
||||
|
||||
#define COL_NAME 20 // same columns for test and compare rows, so
|
||||
#define COL_SCORE 8 // numbers and bars line up down the whole page
|
||||
#define COL_INFO 16
|
||||
|
||||
// one copy of each colour; html_head repeats them for browsers that honour
|
||||
// <style> - WebView reads bg= straight off the tag
|
||||
char c_bar1[] = "#2c7be5"; char c_trk1[] = "#cdd2da";
|
||||
char c_bar2[] = "#4a90e2"; char c_trk2[] = "#e4e7ec";
|
||||
char c_cmp [] = "#8fb8e8";
|
||||
|
||||
dword ebuf = 0;
|
||||
dword epos = 0;
|
||||
char etmp[176];
|
||||
char epath[64];
|
||||
|
||||
void AppendBcd(dword dst, byte b) // BCD byte -> 2 ASCII digits
|
||||
{
|
||||
dword hi, lo;
|
||||
hi = b >> 4; hi = hi & 0xF;
|
||||
lo = b & 0xF;
|
||||
DSBYTE[dst] = hi + '0';
|
||||
DSBYTE[dst+1] = lo + '0';
|
||||
}
|
||||
|
||||
void BuildExportPath() // unique name from date + time
|
||||
{
|
||||
dword dt, tm, adr, v;
|
||||
EAX = 29; $int 0x40; dt = EAX; // fn 29: 0x00DDMMYY (BCD)
|
||||
EAX = 3; $int 0x40; tm = EAX; // fn 3: 0x00SSMMHH (BCD)
|
||||
strcpy(#epath, "/tmp0/1/bb_");
|
||||
adr = #epath; adr = adr + strlen(#epath);
|
||||
v = dt; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // YY
|
||||
v = dt >> 8; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // MM
|
||||
v = dt >> 16; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // DD
|
||||
DSBYTE[adr] = '_'; adr = adr + 1;
|
||||
v = tm; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // HH
|
||||
v = tm >> 8; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // MM
|
||||
v = tm >> 16; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // SS
|
||||
DSBYTE[adr] = 0;
|
||||
strcat(#epath, ".htm");
|
||||
}
|
||||
|
||||
void Hput(dword s)
|
||||
{
|
||||
dword l = strlen(s);
|
||||
if (epos + l >= EBUF_SIZE) return; // truncate the report, never the heap
|
||||
strcpy(ebuf+epos, s);
|
||||
epos = epos + l;
|
||||
}
|
||||
|
||||
void Hfield(dword s, dword width)
|
||||
{
|
||||
dword l = strlen(s);
|
||||
Hput(s);
|
||||
while (l < width) { Hput(" "); l++; }
|
||||
}
|
||||
|
||||
// one bar for test rows (max = BAR_FULL) and compare rows (max = fastest CPU)
|
||||
void Hbar(dword val, max, fillcol, trackcol)
|
||||
{
|
||||
dword filled, track, i;
|
||||
if (max < 1) max = 1;
|
||||
filled = muldiv(val, BAR_W, max); if (filled > BAR_W) filled = BAR_W;
|
||||
track = BAR_W - filled;
|
||||
Hput("<font bg="); Hput(fillcol); Hput(">");
|
||||
for (i=0; i<filled; i++) Hput(" ");
|
||||
Hput("</font><font bg="); Hput(trackcol); Hput(">");
|
||||
for (i=0; i<track; i++) Hput(" ");
|
||||
Hput("</font>\n");
|
||||
}
|
||||
|
||||
void ExportSection(dword sect, title, icon)
|
||||
{
|
||||
int i, r;
|
||||
dword whole, frac, barcol, trkcol;
|
||||
if (!SectionRan(sect)) return; // no completed test here -> no header at all
|
||||
if (sect==SECT_DISK) // show which disk was actually tested
|
||||
sprintf(#etmp, "<h3><kosicon n=%d><b>%s</b> (%s): %d</h3><blockquote>\n", icon, title, disk_dir, sect_score[sect]);
|
||||
else
|
||||
sprintf(#etmp, "<h3><kosicon n=%d><b>%s</b>: %d</h3><blockquote>\n", icon, title, sect_score[sect]);
|
||||
Hput(#etmp);
|
||||
r = 0;
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_sect[i]==sect) {
|
||||
if (t_done[i]==2) { // not applicable here
|
||||
Hfield(t_name[i], COL_NAME);
|
||||
Hfield("skipped", COL_SCORE);
|
||||
Hput("\n");
|
||||
}
|
||||
if (t_done[i]==1) {
|
||||
whole = t_raw[i] / 100; frac = t_raw[i] % 100;
|
||||
if (r & 1) { barcol = #c_bar2; trkcol = #c_trk2; }
|
||||
else { barcol = #c_bar1; trkcol = #c_trk1; }
|
||||
Hfield(t_name[i], COL_NAME);
|
||||
sprintf(#etmp, "%d", t_score[i]); Hfield(#etmp, COL_SCORE);
|
||||
if (frac < 10) sprintf(#etmp, "%d.0%d %s", whole, frac, t_unit[i]);
|
||||
else sprintf(#etmp, "%d.%d %s", whole, frac, t_unit[i]);
|
||||
Hfield(#etmp, COL_INFO);
|
||||
Hbar(t_score[i], BAR_FULL, barcol, trkcol);
|
||||
r++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Hput("</blockquote>\n");
|
||||
}
|
||||
|
||||
void CompareRow(dword name, mhz, score, maxscore, fillcol, trkcol)
|
||||
{
|
||||
char sb[20];
|
||||
Hfield(name, COL_NAME);
|
||||
sprintf(#sb, "%d", score); Hfield(#sb, COL_SCORE);
|
||||
sprintf(#sb, "%d MHz", mhz); Hfield(#sb, COL_INFO);
|
||||
Hbar(score, maxscore, fillcol, trkcol);
|
||||
}
|
||||
|
||||
#ifdef BB_CALIB
|
||||
// Ready-to-paste block: without it calibration means transcribing every
|
||||
// number by hand, so nobody does it and the REF_ values stay guesses.
|
||||
void ExportCalib()
|
||||
{
|
||||
int i;
|
||||
Hput("<h3><b>Calibration</b></h3><blockquote>\n");
|
||||
Hput("This machine's metrics. Put each one in the matching REF_ define to\n");
|
||||
Hput("score it 1000 everywhere, then bump BB_REVISION and re-measure cpudb.\n\n");
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_done[i]==1) {
|
||||
Hfield(t_name[i], COL_NAME);
|
||||
sprintf(#etmp, "%d", t_raw[i]); Hput(#etmp);
|
||||
Hput("\n");
|
||||
}
|
||||
}
|
||||
if (SectionRan(SECT_CPU)) {
|
||||
sprintf(#etmp, "\ncpudb_add(\"CPU name here\", %d, %d, %d);\n",
|
||||
sys_cpu_mhz, sect_score[SECT_CPU], BB_REVISION);
|
||||
Hput(#etmp);
|
||||
}
|
||||
Hput("</blockquote>\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void ExportCompare()
|
||||
{
|
||||
int i;
|
||||
dword mx, thisc;
|
||||
thisc = sect_score[SECT_CPU];
|
||||
Hput("<h3><kosicon n=47><b>CPU compare</b></h3><blockquote>\n");
|
||||
mx = thisc;
|
||||
for (i=0; i<cpudb_count; i++) if (cpudb_score[i] > mx) mx = cpudb_score[i];
|
||||
if (mx < 1) mx = 1;
|
||||
CompareRow("This PC", sys_cpu_mhz, thisc, mx, #c_bar1, #c_trk1);
|
||||
for (i=0; i<cpudb_count; i++) {
|
||||
if (i < 8) CompareRow(cpudb_name[i], cpudb_mhz[i], cpudb_score[i], mx, #c_cmp, #c_trk2);
|
||||
}
|
||||
Hput("</blockquote>\n");
|
||||
}
|
||||
|
||||
// styles for Chrome/Firefox (WebView ignores <style>, uses bg= directly)
|
||||
char html_head[] = "</title><style>h3{margin:0}
|
||||
font{margin-bottom:1px;display:inline-block;}
|
||||
font[bg='#2c7be5']{background:#2c7be5}font[bg='#4a90e2']{background:#4a90e2}
|
||||
font[bg='#8fb8e8']{background:#8fb8e8}font[bg='#cdd2da']{background:#cdd2da}
|
||||
font[bg='#e4e7ec']{background:#e4e7ec}
|
||||
</style><body bgcolor=#ffffff><pre><h2>BirdBench results</h2>";
|
||||
|
||||
void ExportHTML()
|
||||
{
|
||||
dword sp, fname;
|
||||
if (!ebuf) ebuf = malloc(EBUF_SIZE);
|
||||
if (!ebuf) return;
|
||||
BuildExportPath(); // need the name for <title>
|
||||
sp = strrchr(#epath, '/');
|
||||
fname = #epath + sp; // -> "bb_YYMMDD_HHMMSS.htm"
|
||||
epos = 0;
|
||||
DSBYTE[ebuf] = 0;
|
||||
Hput("<html><title>"); Hput(fname);
|
||||
Hput(#html_head);
|
||||
Hput("<font color=#86868b>");
|
||||
sprintf(#etmp, #sysinfo, sys_cpu_mhz, sys_ram_mb, screen.w, screen.h, sys_bpp);
|
||||
Hput(#etmp);
|
||||
sprintf(#etmp, #revinfo, BB_REVISION);
|
||||
Hput(#etmp);
|
||||
Hput("</font>\n\n");
|
||||
ExportSection(SECT_CPU, "CPU", ICON_CPU); // each self-guards on t_done
|
||||
ExportSection(SECT_GPU, "Graphics", ICON_GPU);
|
||||
ExportSection(SECT_DISK, "Disk", ICON_DISK);
|
||||
if (SectionRan(SECT_CPU)) ExportCompare();
|
||||
#ifdef BB_CALIB
|
||||
ExportCalib();
|
||||
#endif
|
||||
Hput("</pre></body></html>");
|
||||
FileWrite(#epath, ebuf, epos);
|
||||
RunProgram("/sys/network/webview", #epath);
|
||||
}
|
||||
|
||||
//========================================================//
|
||||
// the panel //
|
||||
//========================================================//
|
||||
void DrawDiskButton(dword x, y) // combobox trigger (Eolite style) -> opens LMENU
|
||||
{
|
||||
#define DISK_BW 100
|
||||
#define DISK_BH 18
|
||||
dword ax;
|
||||
disk_drop_x = x; disk_drop_y = y;
|
||||
ax = x + DISK_BW - DISK_BH; // arrow-cell left
|
||||
// sunken field with a light background
|
||||
DrawRectangle3D(x, y, DISK_BW-1, DISK_BH, sc.dark, sc.light);
|
||||
DrawBar(x+1, y+1, DISK_BW-2, DISK_BH-1, sc.light);
|
||||
WriteText(x+5, y+3, 0x90, sc.work_text, disk_dirp[disk_sel]);
|
||||
// raised arrow button on the right
|
||||
DrawRectangle3D(x-1, y-1, DISK_BW+1, DISK_BH+2, sc.line, sc.line);
|
||||
DrawRectangle3D(ax-1, y-1, DISK_BH+1, DISK_BH+2, sc.line, sc.line);
|
||||
DrawRectangle3D(ax, y, DISK_BH-1, DISK_BH, sc.light, sc.dark);
|
||||
DrawBar(ax+1, y+1, DISK_BH-2, DISK_BH-1, sc.work);
|
||||
WriteText(ax+6, y+6, 0x80, sc.work_text, "\x19"); // CP866 down arrow
|
||||
DefineHiddenButton(x, y, DISK_BW-1, DISK_BH, BTN_DISK_DROP);
|
||||
}
|
||||
|
||||
dword DrawSectionChecks(dword sect, title, x, y)
|
||||
{
|
||||
int i;
|
||||
cb.disabled = 0;
|
||||
cb.text = title;
|
||||
cb.checked = SectionAllOn(sect);
|
||||
cb.id = BTN_SECT_BASE + sect;
|
||||
cb.draw(x, y);
|
||||
y = y + 30; // bigger gap after the master (the first)
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_sect[i]==sect) {
|
||||
cb.disabled = 0;
|
||||
cb.text = t_name[i];
|
||||
cb.checked = t_enabled[i];
|
||||
cb.id = BTN_CHECK_BASE + i;
|
||||
cb.draw(x, y); // aligned with the master (no extra indent)
|
||||
y = y + ROW_H;
|
||||
}
|
||||
}
|
||||
DrawBar(x-1, HEADER_H+32, math.min(140, WIN_W-x-10), 1, sc.light); // line under the top (master) checkboxes
|
||||
DrawBar(x-1, HEADER_H+33, math.min(140, WIN_W-x-10), 1, sc.dark); // line under the top (master) checkboxes
|
||||
if (sect==SECT_DISK) { // dropdown under bottom checkbox
|
||||
DrawDiskButton(x, y+2); y = y + 24;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
void DrawPanel()
|
||||
{
|
||||
char line[96];
|
||||
// Icons
|
||||
draw_icon_32(67, 6, sc.work, 37); // CPU chip, centered over column 1
|
||||
draw_icon_32(225, 6, sc.work, 27); // Graphics card, over column 2
|
||||
draw_icon_32(362, 6, sc.work, 50); // Disk, over column 3
|
||||
// Main Checkboxes
|
||||
DrawSectionChecks(SECT_CPU, "CPU", PAD, HEADER_H + 10);
|
||||
DrawSectionChecks(SECT_GPU, "Graphics", COL2_X, HEADER_H + 10);
|
||||
DrawSectionChecks(SECT_DISK, "Disk", COL3_X, HEADER_H + 10);
|
||||
// Buttons
|
||||
FlatButton(PAD, BTN_ALL, sc.work, sc.work_text, "All");
|
||||
FlatButton(PAD + BUTTON_W + 9, BTN_NONE, sc.work, sc.work_text, "None");
|
||||
FlatButton(WIN_W - BUTTON_W - PAD, BTN_RUN, 0x4A90E2, 0xFFFFFF, "Run");
|
||||
// Foter
|
||||
DrawBar(0, STATUSBAR_Y, WIN_W, 1, sc.light);
|
||||
DrawBar(0, STATUSBAR_Y+1, WIN_W, 1, sc.dark);
|
||||
sprintf(#line, #sysinfo, sys_cpu_mhz, sys_ram_mb, screen.w, screen.h, sys_bpp);
|
||||
WriteText(PAD, STATUSBAR_Y+7, 0x90, sc.work_text, #line);
|
||||
// Disk open
|
||||
}
|
||||
|
||||
proc_info Form; // global: menu.h reads Form.left/top for LMENU
|
||||
|
||||
void draw_window()
|
||||
{
|
||||
dword wx, wy;
|
||||
sc.get();
|
||||
RefreshScreen();
|
||||
wx = 0; if (screen.w > WIN_W) { wx = screen.w - WIN_W; wx = wx/2; }
|
||||
wy = 0; if (screen.h > WIN_H) { wy = screen.h - WIN_H; wy = wy/2; }
|
||||
DefineAndDrawWindow(wx, wy, WIN_W+9, WIN_H+skin_h+4, 0x34, sc.work, "BirdBench", 0);
|
||||
GetProcessInfo(#Form, SelfInfo);
|
||||
DrawPanel();
|
||||
}
|
||||
|
||||
//---------------- run flow ----------------//
|
||||
char bench_caption[] = "This Bird is Benching";
|
||||
|
||||
void DrawRunScreen() // once per run: clear the body + draw the mascot
|
||||
{
|
||||
dword tx;
|
||||
DrawBar(0, 0, WIN_W, STATUSBAR_Y, sc.work);
|
||||
draw_icon_32(WIN_W-32/2, 96, sc.work, 121);
|
||||
tx = strlen(#bench_caption)*8; // 0x90 = 8x16 font
|
||||
tx = WIN_W - tx; tx = tx/2;
|
||||
WriteText(tx, 140, 0x90, sc.work_text, #bench_caption);
|
||||
}
|
||||
|
||||
void DrawRunStatus(dword name, idx, total, sect)
|
||||
{
|
||||
char msg[80];
|
||||
dword icon;
|
||||
icon = ICON_CPU;
|
||||
if (sect==SECT_GPU) icon = ICON_GPU;
|
||||
if (sect==SECT_DISK) icon = ICON_DISK;
|
||||
DrawBar(0, 0, WIN_W, HEADER_H, sc.work); // header only
|
||||
draw_icon_16w(PAD, 11, icon); // section icon, left of text
|
||||
sprintf(#msg, "Running %s (%d/%d)", name, idx, total);
|
||||
WriteText(PAD+24, 13, 0x90, sc.work_text, #msg);
|
||||
}
|
||||
|
||||
// drain events queued while a test was running; returns 1 if ESC = abort
|
||||
byte AbortRequested()
|
||||
{
|
||||
dword ev, id;
|
||||
byte stop = 0;
|
||||
loop() {
|
||||
ev = CheckEvent();
|
||||
if (!ev) break;
|
||||
if (ev==evButton) {
|
||||
id = GetButtonID();
|
||||
if (id==1) bench_exit(); // window X pressed mid-run
|
||||
}
|
||||
if (ev==evKey) {
|
||||
GetKeys();
|
||||
if (key_scancode==SCAN_CODE_ESC) stop = 1;
|
||||
}
|
||||
if (ev==evReDraw) { draw_window(); DrawRunScreen(); } // keep the run look
|
||||
}
|
||||
return stop;
|
||||
}
|
||||
|
||||
void RunSelected()
|
||||
{
|
||||
int i, idx, total;
|
||||
dword s, v;
|
||||
byte aborted;
|
||||
total = 0;
|
||||
for (i=0; i<t_count; i++) if (t_enabled[i]) total++;
|
||||
if (total < 1) return;
|
||||
for (s=0; s<SECT_NUM; s++) { sums[s] = 0; cnts[s] = 0; }
|
||||
for (i=0; i<t_count; i++) t_done[i] = 0;
|
||||
// occlusion skews fn13/fn7/fn38/fn4 (the kernel only draws visible
|
||||
// parts) - keep the window on top for the duration of the run
|
||||
SetWindowLayerBehaviour(-1, ZPOS_ALWAYS_TOP);
|
||||
DrawRunScreen(); // the mascot, once per run
|
||||
aborted = 0;
|
||||
idx = 0;
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_enabled[i]) {
|
||||
if (AbortRequested()) { aborted = 1; break; }
|
||||
idx++;
|
||||
DrawRunStatus(t_name[i], idx, total, t_sect[i]);
|
||||
RunOne(i);
|
||||
if (t_done[i]==1) { // skipped carry no score
|
||||
s = t_sect[i];
|
||||
v = t_score[i]; if (v < 1) v = 1;
|
||||
sums[s] = sums[s] + ilog2_16(v); // geometric mean accumulator
|
||||
cnts[s] = cnts[s] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Disk_Cleanup(); // remove the 8 MB temp file
|
||||
SetWindowLayerBehaviour(-1, ZPOS_NORMAL);
|
||||
// section score = geomean of its tests (robust to single-test outliers)
|
||||
for (s=0; s<SECT_NUM; s++) {
|
||||
if (cnts[s] > 0) { v = sums[s] / cnts[s]; sect_score[s] = iexp2_16(v); }
|
||||
else sect_score[s] = 0;
|
||||
}
|
||||
draw_window();
|
||||
if (!aborted) ExportHTML(); // auto-open the report (ESC = silent abort)
|
||||
}
|
||||
|
||||
//---------------- events ----------------//
|
||||
void HandleButton(dword id)
|
||||
{
|
||||
if (id==1) bench_exit();
|
||||
if (id==BTN_DISK_DROP) { // open the LMENU popup below the combobox
|
||||
open_lmenu(disk_drop_x, disk_drop_y+DISK_BH+1, MENU_TOP_LEFT, disk_sel+1, #disk_menu);
|
||||
disk_menu_open = 1;
|
||||
return;
|
||||
}
|
||||
if (id==BTN_RUN) RunSelected();
|
||||
if (id==BTN_ALL) { SetAll(1); draw_window(); }
|
||||
if (id==BTN_NONE) { SetAll(0); draw_window(); }
|
||||
if (id>=BTN_SECT_BASE) && (id<BTN_SECT_BASE+SECT_NUM) {
|
||||
ToggleSection(id - BTN_SECT_BASE);
|
||||
draw_window();
|
||||
}
|
||||
if (id>=BTN_CHECK_BASE) && (id<BTN_CHECK_BASE+MAX_TESTS) {
|
||||
t_enabled[id-BTN_CHECK_BASE] ^= 1;
|
||||
draw_window();
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int i;
|
||||
dword click;
|
||||
GetSysInfo();
|
||||
if (!BenchAllocBuffers()) {
|
||||
notify("'BirdBench\nNot enough memory for the work buffers!' -E");
|
||||
ExitProcess();
|
||||
}
|
||||
cpudb_init();
|
||||
Register_CPU();
|
||||
Register_GPU();
|
||||
Register_DISK();
|
||||
ScanDisks();
|
||||
for (i=0; i<t_count; i++) t_enabled[i] = 1;
|
||||
|
||||
loop() switch(WaitEvent())
|
||||
{
|
||||
case evButton:
|
||||
HandleButton(GetButtonID());
|
||||
break;
|
||||
case evKey:
|
||||
GetKeys();
|
||||
if (key_scancode==SCAN_CODE_ESC) bench_exit();
|
||||
if (key_scancode==SCAN_CODE_ENTER) RunSelected();
|
||||
break;
|
||||
case evReDraw:
|
||||
if (disk_menu_open) { // an LMENU popup was opened
|
||||
click = get_menu_click();
|
||||
if (click) { // user picked a disk (1-based)
|
||||
disk_sel = click - 1;
|
||||
disk_dir = disk_dirp[disk_sel];
|
||||
}
|
||||
if (!menu_process_id) disk_menu_open = 0; // popup closed
|
||||
}
|
||||
draw_window();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
#ifndef INCLUDE_BENCH_H
|
||||
#define INCLUDE_BENCH_H
|
||||
//========================================================//
|
||||
// KolibriMark - benchmark engine //
|
||||
// Test registry, timing, scoring, system info. //
|
||||
// (No UI here - see kbench.c) //
|
||||
//========================================================//
|
||||
|
||||
#define SECT_CPU 0
|
||||
#define SECT_GPU 1
|
||||
#define SECT_DISK 2
|
||||
#define SECT_NUM 3
|
||||
|
||||
#define MAX_TESTS 48
|
||||
|
||||
//#define BB_CALIB // uncomment to add the calibration block to the report
|
||||
|
||||
#define BB_REVISION 1 // version of the tests + REF_ values, shown in the report
|
||||
#define BB_SKIP 0xFFFFFFFF // test cannot run here: no score, out of the geomean
|
||||
|
||||
// ---- shared work buffers ----
|
||||
#define GCV_X 0 // graphics canvas = window body (WIN_W/H tied to these)
|
||||
#define GCV_Y 42
|
||||
#define GCV_W 455 // wide enough for 3 columns of checkboxes
|
||||
#define GCV_H 240
|
||||
|
||||
#define CPY_BYTES 2097152 // 2 MB: copy/latency working set (> retro-era caches)
|
||||
|
||||
// buf_a: copy source, hash input, disk-write source (content never matters)
|
||||
// buf_b: copy dest, sieve + pointer-chase workspace (each test re-inits it)
|
||||
dword buf_a, buf_b, buf_canvas;
|
||||
|
||||
// ---- parallel-array registry (modular: RegisterTest appends) ----
|
||||
dword t_name [MAX_TESTS]; // -> name string
|
||||
dword t_unit [MAX_TESTS]; // -> unit string, e.g. "MB/s"
|
||||
dword t_fn [MAX_TESTS]; // -> test function (returns metric x100)
|
||||
dword t_ref [MAX_TESTS]; // reference metric x100 that scores 1000
|
||||
dword t_raw [MAX_TESTS]; // last measured metric x100
|
||||
dword t_score[MAX_TESTS]; // last computed score
|
||||
byte t_sect [MAX_TESTS];
|
||||
byte t_done [MAX_TESTS]; // 0 = not run, 1 = has a result, 2 = skipped
|
||||
int t_count = 0;
|
||||
|
||||
dword sect_score[SECT_NUM]; // aggregate score per section
|
||||
|
||||
void RegisterTest(dword sect, name, unit, ref, fn)
|
||||
{
|
||||
t_sect [t_count] = sect;
|
||||
t_name [t_count] = name;
|
||||
t_unit [t_count] = unit;
|
||||
t_ref [t_count] = ref;
|
||||
t_fn [t_count] = fn;
|
||||
t_raw [t_count] = 0;
|
||||
t_score[t_count] = 0;
|
||||
t_done [t_count] = 0;
|
||||
t_count++;
|
||||
}
|
||||
|
||||
byte SectionRan(dword sect) // true if any test here produced a result
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<t_count; i++) if (t_sect[i]==sect) && (t_done[i]==1) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- 64-bit-safe a*b/c ----
|
||||
dword muldiv(dword a, b, c)
|
||||
{
|
||||
if (c<1) c = 1;
|
||||
EAX = a;
|
||||
ECX = b;
|
||||
$mul ecx
|
||||
ECX = c;
|
||||
$div ecx
|
||||
}
|
||||
|
||||
// ---- integer log2 / exp2 in 16.16 fixed point ----
|
||||
// Used for the geometric mean of section scores: with an arithmetic mean
|
||||
// one outlier test (VRAM write spans 2 orders of magnitude between WC and
|
||||
// non-WC machines) would dominate the whole section.
|
||||
// log2(1+f) ~ f + 0.343*f*(1-f), error < 0.5% after the exp2 round-trip.
|
||||
dword pw2tab[32]; // 2^n table (C-- has no variable shifts)
|
||||
|
||||
dword ilog2_16(dword s) // log2(s) in 16.16, s >= 1
|
||||
{
|
||||
dword e, m, x, c;
|
||||
if (s < 2) return 0;
|
||||
e = 0; m = s;
|
||||
while (m > 1) { m = m / 2; e++; }
|
||||
x = muldiv(s, 65536, pw2tab[e]);
|
||||
x = x - 65536; // fractional mantissa, 16.16
|
||||
c = 65536 - x;
|
||||
c = muldiv(x, c, 65536);
|
||||
c = muldiv(c, 22479, 65536); // 0.343 in 16.16
|
||||
x = x + c;
|
||||
e = e << 16;
|
||||
return e + x;
|
||||
}
|
||||
|
||||
dword iexp2_16(dword v) // 2^(v/65536), inverse of ilog2_16
|
||||
{
|
||||
dword e, f, c, m;
|
||||
e = v >> 16;
|
||||
f = v & 0xFFFF;
|
||||
c = 65536 - f;
|
||||
c = muldiv(f, c, 65536);
|
||||
c = muldiv(c, 22479, 65536);
|
||||
f = f - c;
|
||||
m = 65536 + f; // 2^frac in 16.16
|
||||
if (e > 30) return 0xFFFFFFFF;
|
||||
return muldiv(m, pw2tab[e], 65536);
|
||||
}
|
||||
|
||||
// ---- timing (system fn 26.9: 1/100 s counter) ----
|
||||
dword bench_t0;
|
||||
void BenchBegin() { bench_t0 = GetStartTime(); }
|
||||
dword BenchTicks() // elapsed 1/100 s, min 1
|
||||
{
|
||||
dword e = GetStartTime() - bench_t0;
|
||||
if (e < 1) e = 1;
|
||||
return e;
|
||||
}
|
||||
// count = amount processed in DISPLAY units; returns units/sec x100
|
||||
dword PerSecX100(dword count)
|
||||
{
|
||||
return muldiv(count, 10000, BenchTicks());
|
||||
}
|
||||
|
||||
// ---- call a registered test by pointer, return its metric x100 ----
|
||||
dword CallFn(dword fn)
|
||||
{
|
||||
ESI = fn;
|
||||
$call esi
|
||||
}
|
||||
|
||||
// ---- run one registered test ----
|
||||
void RunOne(int i)
|
||||
{
|
||||
dword raw = CallFn(t_fn[i]);
|
||||
if (raw == BB_SKIP) {
|
||||
t_raw[i] = 0;
|
||||
t_score[i] = 0;
|
||||
t_done[i] = 2;
|
||||
return;
|
||||
}
|
||||
t_raw[i] = raw;
|
||||
t_score[i] = muldiv(raw, 1000, t_ref[i]);
|
||||
t_done[i] = 1;
|
||||
}
|
||||
|
||||
// ---- allocate shared buffers + seed with data ----
|
||||
byte BenchAllocBuffers() // 0 = out of memory
|
||||
{
|
||||
dword i, n;
|
||||
buf_a = malloc(CPY_BYTES);
|
||||
buf_b = malloc(CPY_BYTES);
|
||||
buf_canvas = malloc(GCV_W*GCV_H*3);
|
||||
if (!buf_a) || (!buf_b) || (!buf_canvas) return 0;
|
||||
i = 0;
|
||||
while (i < CPY_BYTES) {
|
||||
ESDWORD[buf_a+i] = i;
|
||||
i += 4;
|
||||
}
|
||||
n = GCV_W*GCV_H; n = n*3;
|
||||
for (i=0; i<n; i++) DSBYTE[buf_canvas+i] = i; // gradient BBGGRR
|
||||
pw2tab[0] = 1;
|
||||
for (i=1; i<32; i++) { n = pw2tab[i-1]; pw2tab[i] = n + n; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
//======================= system info =======================//
|
||||
dword sys_cpu_mhz;
|
||||
dword sys_ram_mb;
|
||||
dword sys_bpp; // current video mode bits per pixel
|
||||
|
||||
// re-read screen resolution + bpp (they change on a mode switch)
|
||||
void RefreshScreen()
|
||||
{
|
||||
dword d;
|
||||
EAX = 14; $int 0x40 // screen size: (w-1)<<16 + (h-1)
|
||||
d = EAX;
|
||||
screen.h = d & 0xFFFF; screen.h++;
|
||||
screen.w = d >> 16; screen.w++;
|
||||
EAX = 61; EBX = 2; $int 0x40 // fn 61.2 = bits per pixel
|
||||
sys_bpp = EAX;
|
||||
}
|
||||
|
||||
// RAM + CPU clock (fn 18.5). No CPUID/RDTSC -> fully 386-compatible (no
|
||||
// "CPU required: Pentium"). The processor is just labelled "CPU" in the UI.
|
||||
void GetSysInfo()
|
||||
{
|
||||
dword hz;
|
||||
sys_ram_mb = GetTotalRAM() / 1024;
|
||||
EAX = 18; EBX = 5; $int 0x40; // fn 18.5 = CPU clock rate, Hz
|
||||
hz = EAX;
|
||||
sys_cpu_mhz = hz / 1000000;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
@del *.
|
||||
@..\c--\c--.exe bbench.c
|
||||
@rename *.com *.
|
||||
@del warning.txt
|
||||
if not exist bbench ( @pause )
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef INCLUDE_CPUDB_H
|
||||
#define INCLUDE_CPUDB_H
|
||||
//========================================================//
|
||||
// CPU comparison database -- EDIT ME //
|
||||
// //
|
||||
// One line per reference CPU in cpudb_init(), copied straight //
|
||||
// from the Calibration block of a run on that machine. Entries //
|
||||
// measured under another BB_REVISION are hidden. //
|
||||
// The values below are placeholders - replace them. //
|
||||
//========================================================//
|
||||
|
||||
#define CPUDB_MAX 24
|
||||
dword cpudb_name [CPUDB_MAX];
|
||||
dword cpudb_mhz [CPUDB_MAX];
|
||||
dword cpudb_score[CPUDB_MAX];
|
||||
int cpudb_count = 0;
|
||||
|
||||
// entries from another revision are dropped: their numbers mean something else
|
||||
void cpudb_add(dword name, mhz, score, rev)
|
||||
{
|
||||
if (cpudb_count >= CPUDB_MAX) return;
|
||||
if (rev != BB_REVISION) return;
|
||||
cpudb_name [cpudb_count] = name;
|
||||
cpudb_mhz [cpudb_count] = mhz;
|
||||
cpudb_score[cpudb_count] = score;
|
||||
cpudb_count++;
|
||||
}
|
||||
|
||||
void cpudb_init()
|
||||
{
|
||||
cpudb_add("Intel Core i5-2400", 3100, 1600, 1);
|
||||
cpudb_add("AMD Athlon XP 2500+", 1833, 360, 1);
|
||||
cpudb_add("Intel Pentium 4", 2400, 300, 1);
|
||||
cpudb_add("Intel Pentium III", 1000, 180, 1);
|
||||
cpudb_add("Intel Atom N270", 1600, 420, 1);
|
||||
cpudb_add("VIA C7", 1200, 150, 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,124 @@
|
||||
# BirdBench
|
||||
|
||||
A small, modular system benchmark for KolibriOS, styled after Geekbench.
|
||||
Single-threaded (KolibriOS exposes no SMP to apps). 386-compatible
|
||||
(no CPUID/RDTSC — the MHz value comes from sysfn 18.5).
|
||||
|
||||
## Sections
|
||||
|
||||
* **CPU** — Integer, Floating Point, Memory Copy, Memory Latency,
|
||||
Prime Sieve, Hash (FNV-1a).
|
||||
Integer/Float run **4 independent chains** so superscalar CPUs (P5+) can
|
||||
pair/overlap them — a strictly serial chain would only measure latency.
|
||||
Float has no division on purpose: one fdiv (15–40+ cycles) would dominate
|
||||
the chain and the test would degenerate into an fdiv-latency meter.
|
||||
Memory Copy is a **forward** `rep movsd` over 2 MB (lib `memmov` copies
|
||||
backward when dst>src, which never hits the fast-string path on modern
|
||||
CPUs). Memory Latency is a serial pointer chase over a 2 MB random cycle
|
||||
(Sattolo shuffle) — defeats prefetch and cache, measures dependent-load
|
||||
latency; `100 / (Macc/s) = ns` per access.
|
||||
* **Graphics** — Fill (f13), Blit (f7), VRAM Read (f36), VRAM Write (gs:),
|
||||
Lines (f38), Text (f4).
|
||||
Fill/Blit = writes through the driver's blitter (may be HW-accelerated),
|
||||
VRAM Read (f36) = read-back, VRAM Write (gs:) = raw CPU `rep stosd` straight
|
||||
into the LFB via the GS selector — this one exposes write-combining (MTRR/PAT):
|
||||
no WC and it collapses ~10-50x. The report shows the current video mode —
|
||||
switch modes with the vidmode picker and re-run to compare VESA-LFB vs a
|
||||
native driver vs KMS at the same resolution.
|
||||
Before writing, the GS segment limit is checked with `lsl` — on banked-VGA
|
||||
modes (no LFB) the test skips instead of #GP-faulting. Color counters are
|
||||
masked to 24 bits: fn13 treats bit 31 as the gradient-fill flag and fn38
|
||||
treats bit 24 as the inversed-line flag — unmasked they silently switch the
|
||||
kernel to a different (slower) operation mid-test.
|
||||
Text draws **both kernel fonts** (6×9 and 8×16) and forces **subpixel font
|
||||
smoothing** for the duration (fn 48.9 saves the user's setting, fn 48.10
|
||||
sets/restores it) — the heaviest glyph path, so machines are compared on
|
||||
equal footing regardless of their smoothing setting.
|
||||
* **Disk** — Sequential write / read (2 MB ops) + Random write / read
|
||||
(4 KB blocks at fixed-seed random offsets in an 8 MB file, via fn 70
|
||||
positioned I/O) + **File System** (create 10 files + 10 folders, delete
|
||||
them all — metadata op/s, fn 70.2/70.9/70.8; names are pre-built so no
|
||||
sprintf lands in the timed loop). Folder support is probed first: not every
|
||||
writable FS driver implements fn 70.9 (exFAT has no CreateFolder at all) —
|
||||
there the test degrades to files-only instead of scoring 0. Pick the target
|
||||
disk in the UI — the report's Disk header shows which disk was tested. All
|
||||
disk rates are MiB/s.
|
||||
Temp files are deleted when the run finishes.
|
||||
NB: KolibriOS apps can't drop the FS cache, so on a real disk the random
|
||||
numbers reflect the FS small-op path (not raw seek/IOPS), and on the RAM
|
||||
disk random ≈ sequential (no seek penalty).
|
||||
|
||||
The window is a **launcher**: tick the tests you want (per-test checkboxes,
|
||||
per-section master checkbox, or All / None), pick a disk target, press **Run**.
|
||||
Only ticked tests run; **Esc aborts** between tests. During a run the window
|
||||
is forced always-on-top: the kernel only draws the visible parts of a window,
|
||||
so an occluded window would inflate the Graphics scores. When a run finishes,
|
||||
an HTML report is written to `/tmp0/1/bb_YYMMDD_HHMMSS.htm` (unique name) and
|
||||
**auto-opened in WebView**.
|
||||
|
||||
The report (not the window) shows the results: per-section score, one row per
|
||||
test with a colored bar (alternating shades) plus a gray/light-gray track, and
|
||||
a **CPU compare** chart of your score vs the CPUs in `cpudb.h` (with a MHz
|
||||
column; the compare bars line up with the test bars). Results are only
|
||||
in the web page — clearer there than in the small window.
|
||||
|
||||
Each test reports a raw throughput (MB/s, MPix/s, ...) and a *score*
|
||||
(1000 = the reference machine defined by the `REF_*` constants in the
|
||||
module). The section score is the **geometric mean** of its ticked tests'
|
||||
scores — a single outlier test (VRAM Write spans two orders of magnitude
|
||||
between WC and non-WC machines) can't dominate the section the way it
|
||||
would with an arithmetic mean.
|
||||
|
||||
## How to add a test
|
||||
|
||||
1. Open the section module (`tests_cpu.h`, `tests_gpu.h` or `tests_disk.h`).
|
||||
2. Write a function returning the metric ×100 (two decimals):
|
||||
|
||||
```c
|
||||
dword t_cpu_mytest()
|
||||
{
|
||||
dword c = 0;
|
||||
BenchBegin();
|
||||
do { /* one unit of work */ c++; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // units/sec ×100
|
||||
}
|
||||
```
|
||||
|
||||
`c` counts how many *display units* you processed (1 MB, 1 Mpixel,
|
||||
1 Kline, ...). The framework turns that into a per-second rate.
|
||||
3. Register it and give it a reference value:
|
||||
|
||||
```c
|
||||
#define REF_MYTEST 5000 // 50.00 units/s -> score 1000
|
||||
RegisterTest(SECT_CPU, "My Test", "u/s", REF_MYTEST, #t_cpu_mytest);
|
||||
```
|
||||
|
||||
That's it — it shows up in the list automatically.
|
||||
|
||||
## How to add a comparison CPU
|
||||
|
||||
Edit `cpudb.h`, add a line to `cpudb_init()`:
|
||||
|
||||
```c
|
||||
cpudb_add("Intel Core 2 Duo E8400", 3000, 520); // name, MHz, measured CPU score
|
||||
```
|
||||
|
||||
NB: any change to the test definitions invalidates previously measured
|
||||
db entries — re-measure them with the same binary you compare against.
|
||||
|
||||
## Build
|
||||
|
||||
Run `build.bat` (needs `../c--/c--.exe`). Output: `bbench`.
|
||||
|
||||
## Notes / calibration
|
||||
|
||||
* The `REF_*` constants are placeholders. Pick a machine as your baseline,
|
||||
run it, and set each `REF_*` to that machine's raw metric ×100 so it
|
||||
scores ~1000. Everything else scales relative to it.
|
||||
* All "MB" are MiB (2^20) across CPU, VRAM Write and Disk tests.
|
||||
* Random-I/O offsets and the latency chain use **fixed seeds** — every run
|
||||
and every machine gets the same access pattern (reproducibility).
|
||||
* On machines with a large L3 the 2 MB working set of Memory Copy/Latency
|
||||
measures the cache, not RAM — unavoidable at sizes that still fit
|
||||
retro-era boxes; scores stay comparable within an era.
|
||||
* Needs ~6 MB RAM (see `MEMSIZE`).
|
||||
@@ -0,0 +1,188 @@
|
||||
#ifndef INCLUDE_TESTS_CPU_H
|
||||
#define INCLUDE_TESTS_CPU_H
|
||||
//========================================================//
|
||||
// CPU test module. //
|
||||
// Each test returns its metric x100 (two decimals). //
|
||||
// To add a test: write t_cpu_xxx() then RegisterTest(). //
|
||||
//========================================================//
|
||||
|
||||
// reference metric x100 that should score 1000 (calibration)
|
||||
#define REF_INT 5000 // 50.00 MOps/s
|
||||
#define REF_FLOAT 2000 // 20.00 MFLOP/s
|
||||
#define REF_MEM 80000 // 800.00 MB/s
|
||||
#define REF_LAT 1000 // 10.00 Macc/s (= 100 ns per access)
|
||||
#define REF_SIEVE 3000 // 30.00 Mcell/s
|
||||
#define REF_HASH 10000 // 100.00 MB/s
|
||||
|
||||
#define CPY_MB 2 // CPY_BYTES in whole MiB (see bbench.h)
|
||||
#define LAT_N 524288 // nodes in the 2 MB pointer chain
|
||||
|
||||
dword cpu_sink;
|
||||
float cpu_fsink;
|
||||
dword lat_rnd;
|
||||
|
||||
// forward rep movsd block copy. NB: lib memmov() copies BACKWARD when
|
||||
// dst>src (std; rep movsd), which never hits the fast-string path on
|
||||
// modern CPUs and would understate them - so we do our own, forward.
|
||||
inline fastcall copy_fwd(EDI, ESI, ECX) // dst, src, bytes (mult of 4)
|
||||
{
|
||||
asm {
|
||||
CLD
|
||||
SHR ECX, 2
|
||||
REP MOVSD
|
||||
}
|
||||
}
|
||||
|
||||
void sieve1m(dword buf)
|
||||
{
|
||||
dword i, j;
|
||||
EDI = buf;
|
||||
EAX = 0;
|
||||
ECX = 262144; // 1 MB / 4
|
||||
asm {
|
||||
CLD
|
||||
REP STOSD
|
||||
}
|
||||
for (i=2; i*i<1000000; i++) {
|
||||
if (DSBYTE[buf+i]==0) {
|
||||
j = i*i;
|
||||
while (j<1000000) { DSBYTE[buf+j]=1; j += i; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dword hash1mb(dword buf)
|
||||
{
|
||||
dword i, h;
|
||||
h = 2166136261;
|
||||
for (i=0; i<1048576; i++) {
|
||||
h = h ^ DSBYTE[buf+i];
|
||||
h = h * 16777619;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// build one random cycle of byte-offsets over buf (Sattolo shuffle):
|
||||
// following p = [buf+p] visits every node in random order - defeats
|
||||
// both the prefetcher and the cache -> pure dependent-load latency.
|
||||
void lat_build(dword buf)
|
||||
{
|
||||
dword i, j, t, ai, aj;
|
||||
for (i=0; i<LAT_N; i++) { ai = i*4; ESDWORD[buf+ai] = ai; }
|
||||
lat_rnd = 0x1A2B3C4D; // fixed seed: same chain on every machine
|
||||
i = LAT_N;
|
||||
while (i > 1) {
|
||||
i = i - 1;
|
||||
lat_rnd = lat_rnd * 1103515245; lat_rnd = lat_rnd + 12345;
|
||||
j = lat_rnd >> 8; j = j % i;
|
||||
ai = i*4; ai = ai + buf;
|
||||
aj = j*4; aj = aj + buf;
|
||||
t = ESDWORD[ai]; ESDWORD[ai] = ESDWORD[aj]; ESDWORD[aj] = t;
|
||||
}
|
||||
}
|
||||
|
||||
//--- Integer: 4 independent ALU chains, ~1e6 ops per unit ---
|
||||
// Interleaved so superscalar CPUs (P5+) can overlap them; a strictly
|
||||
// serial chain would only measure latency and hide pipelining.
|
||||
dword t_cpu_int()
|
||||
{
|
||||
dword c=0, i, x1, x2, x3, x4;
|
||||
BenchBegin();
|
||||
do {
|
||||
x1 = 0x12345678; x2 = 0x9E3779B9; x3 = 0x01234567; x4 = 0xABCDEF01;
|
||||
for (i=0; i<50000; i++) {
|
||||
x1 = x1 + i; x2 = x2 + i; x3 = x3 + i; x4 = x4 + i;
|
||||
x1 = x1 ^ 0x55AA55AA; x2 = x2 ^ 0x33CC33CC; x3 = x3 ^ 0x0F0F0F0F; x4 = x4 ^ 0x5A5A5A5A;
|
||||
x1 = x1 * 3; x2 = x2 * 3; x3 = x3 * 3; x4 = x4 * 3;
|
||||
x1 = x1 - i; x2 = x2 - i; x3 = x3 - i; x4 = x4 - i;
|
||||
x1 = x1 + x1; x2 = x2 + x2; x3 = x3 + x3; x4 = x4 + x4;
|
||||
}
|
||||
cpu_sink = x1; cpu_sink = cpu_sink ^ x2;
|
||||
cpu_sink = cpu_sink ^ x3; cpu_sink = cpu_sink ^ x4;
|
||||
c++; // 50000 * 20 = 1e6 ops
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MOps/s
|
||||
}
|
||||
|
||||
//--- Floating point: 4 independent mul/add chains, ~1e6 flops per unit ---
|
||||
// No division: one fdiv (15-40+ cycles) would dominate the whole chain
|
||||
// and the test would just measure fdiv latency.
|
||||
dword t_cpu_float()
|
||||
{
|
||||
dword c=0, i;
|
||||
float a1, a2, a3, a4, b, k;
|
||||
BenchBegin();
|
||||
do {
|
||||
a1 = 1.0; a2 = 1.1; a3 = 1.2; a4 = 1.3;
|
||||
b = 1.0000151; k = 0.9999847;
|
||||
for (i=0; i<62500; i++) {
|
||||
a1 = a1 * b; a2 = a2 * b; a3 = a3 * b; a4 = a4 * b;
|
||||
a1 = a1 + k; a2 = a2 + k; a3 = a3 + k; a4 = a4 + k;
|
||||
a1 = a1 * k; a2 = a2 * k; a3 = a3 * k; a4 = a4 * k;
|
||||
a1 = a1 - k; a2 = a2 - k; a3 = a3 - k; a4 = a4 - k;
|
||||
}
|
||||
cpu_fsink = a1; cpu_fsink = cpu_fsink + a2;
|
||||
cpu_fsink = cpu_fsink + a3; cpu_fsink = cpu_fsink + a4;
|
||||
c++; // 62500 * 16 = 1e6 flops
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MFLOP/s
|
||||
}
|
||||
|
||||
//--- Memory bandwidth: 2 MB forward copy per unit ---
|
||||
dword t_cpu_mem()
|
||||
{
|
||||
dword c=0;
|
||||
BenchBegin();
|
||||
do { copy_fwd(buf_b, buf_a, CPY_BYTES); c += CPY_MB; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
//--- Memory latency: serial pointer chase over 2 MB, 1e6 hops per unit ---
|
||||
dword t_cpu_latency()
|
||||
{
|
||||
dword c=0, k, p;
|
||||
lat_build(buf_b);
|
||||
BenchBegin();
|
||||
do {
|
||||
p = 0;
|
||||
for (k=0; k<250000; k++) {
|
||||
p = ESDWORD[buf_b+p];
|
||||
p = ESDWORD[buf_b+p];
|
||||
p = ESDWORD[buf_b+p];
|
||||
p = ESDWORD[buf_b+p];
|
||||
}
|
||||
cpu_sink = p;
|
||||
c++; // 250000 * 4 = 1e6 dependent loads
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // Macc/s (100/result = ns per access)
|
||||
}
|
||||
|
||||
//--- Prime sieve: 1e6 cells per unit ---
|
||||
dword t_cpu_sieve()
|
||||
{
|
||||
dword c=0;
|
||||
BenchBegin();
|
||||
do { sieve1m(buf_b); c++; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // Mcell/s
|
||||
}
|
||||
|
||||
//--- Hash throughput (FNV-1a): 1 MB per unit ---
|
||||
dword t_cpu_hash()
|
||||
{
|
||||
dword c=0;
|
||||
BenchBegin();
|
||||
do { cpu_sink = hash1mb(buf_a); c++; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
void Register_CPU()
|
||||
{
|
||||
RegisterTest(SECT_CPU, "Integer", "MOps/s", REF_INT, #t_cpu_int);
|
||||
RegisterTest(SECT_CPU, "Floating Point", "MFLOP/s", REF_FLOAT, #t_cpu_float);
|
||||
RegisterTest(SECT_CPU, "Memory Copy", "MB/s", REF_MEM, #t_cpu_mem);
|
||||
RegisterTest(SECT_CPU, "Memory Latency", "Macc/s", REF_LAT, #t_cpu_latency);
|
||||
RegisterTest(SECT_CPU, "Prime Sieve", "Mcell/s", REF_SIEVE, #t_cpu_sieve);
|
||||
RegisterTest(SECT_CPU, "Hash FNV-1a", "MB/s", REF_HASH, #t_cpu_hash);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,311 @@
|
||||
#ifndef INCLUDE_TESTS_DISK_H
|
||||
#define INCLUDE_TESTS_DISK_H
|
||||
//========================================================//
|
||||
// Disk test module. //
|
||||
// Writes/reads a temp file via system fn 70. //
|
||||
// Target directory = disk_dir (set from the UI disk //
|
||||
// selector); falls back to the RAM disk /tmp0/1. //
|
||||
// NB: sequential rewrite of one file mostly measures the //
|
||||
// FS/cache throughput, not raw platter speed. //
|
||||
//========================================================//
|
||||
|
||||
#define REF_DWRITE 5000 // 50.00 MB/s
|
||||
#define REF_DREAD 5000
|
||||
#define REF_RWRITE 1000 // 10.00 MB/s (random 4K is much slower)
|
||||
#define REF_RREAD 1000
|
||||
#define REF_FS 50000 // 500.00 op/s (create/delete metadata ops)
|
||||
#define FS_N 10 // files + folders created per unit
|
||||
#define DISK_CHUNK CPY_BYTES // 2 MB per op (reuses buf_a - content irrelevant)
|
||||
#define DISK_MB 2
|
||||
#define RND_FILE 8388608 // 8 MB test file for random ops
|
||||
#define RND_BLK 4096 // 4 KB random block
|
||||
#define RND_NBLK 2048 // RND_FILE / RND_BLK
|
||||
|
||||
dword buf_disk = 0;
|
||||
dword disk_dir = 0; // -> selected target dir (set by the UI)
|
||||
dword rnd_state; // LCG state for random offsets
|
||||
char dk_op[32];
|
||||
char dk_file[80];
|
||||
|
||||
dword FileWrite(dword name, data, size)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 2; // subfn 2 = create/rewrite
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX; // 0 = ok
|
||||
}
|
||||
|
||||
dword FileRead(dword name, data, size)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 0; // subfn 0 = read
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword dk_delete(dword name)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 8; // subfn 8 = delete
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = 0;
|
||||
ESDWORD[#dk_op+16] = 0;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword dk_mkdir(dword name)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 9; // subfn 9 = create folder
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = 0;
|
||||
ESDWORD[#dk_op+16] = 0;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword FileWriteAt(dword name, data, size, pos) // fn70.3 = write at position
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 3;
|
||||
ESDWORD[#dk_op+4] = pos;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword FileReadAt(dword name, data, size, pos) // fn70.0 = read at position
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 0;
|
||||
ESDWORD[#dk_op+4] = pos;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
void DiskPath() // dk_file = <disk_dir>/bbtst.tmp
|
||||
{
|
||||
if (disk_dir) strcpy(#dk_file, disk_dir);
|
||||
else strcpy(#dk_file, "/tmp0/1");
|
||||
strcat(#dk_file, "/bbtst.tmp");
|
||||
}
|
||||
|
||||
// create/fill an 8 MB file so random 4K offsets are valid and physically written
|
||||
void EnsureRandFile()
|
||||
{
|
||||
dword pos;
|
||||
FileWrite(#dk_file, buf_disk, DISK_CHUNK); // create
|
||||
pos = DISK_CHUNK;
|
||||
while (pos < RND_FILE) {
|
||||
FileWriteAt(#dk_file, buf_disk, DISK_CHUNK, pos);
|
||||
pos += DISK_CHUNK;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the temp file (called by the UI when a run finishes)
|
||||
void Disk_Cleanup()
|
||||
{
|
||||
DiskPath();
|
||||
dk_delete(#dk_file);
|
||||
}
|
||||
|
||||
dword t_disk_write()
|
||||
{
|
||||
dword c=0, r;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
BenchBegin();
|
||||
do {
|
||||
r = FileWrite(#dk_file, buf_disk, DISK_CHUNK);
|
||||
if (r) return BB_SKIP; // write error: no result, not a slow one
|
||||
c += DISK_MB;
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
dword t_disk_read()
|
||||
{
|
||||
dword c=0, r;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
// Seq Write may be unticked - create the test file if it is missing
|
||||
r = FileRead(#dk_file, buf_disk, RND_BLK);
|
||||
if (r!=0) && (r!=6) FileWrite(#dk_file, buf_disk, DISK_CHUNK);
|
||||
BenchBegin();
|
||||
do {
|
||||
r = FileRead(#dk_file, buf_disk, DISK_CHUNK);
|
||||
if (r!=0) && (r!=6) return BB_SKIP; // 6 = EOF (still ok)
|
||||
c += DISK_MB;
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
// random 4K write at random offsets in the 8 MB file
|
||||
dword t_disk_rwrite()
|
||||
{
|
||||
dword c=0, pos, r;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
EnsureRandFile();
|
||||
rnd_state = 0x12345678; // fixed seed: same offsets on every run
|
||||
BenchBegin();
|
||||
do {
|
||||
rnd_state = rnd_state * 1103515245; rnd_state = rnd_state + 12345;
|
||||
r = rnd_state >> 8; r = r % RND_NBLK;
|
||||
pos = r * RND_BLK;
|
||||
if (FileWriteAt(#dk_file, buf_disk, RND_BLK, pos)) return BB_SKIP;
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
r = muldiv(c, 40000, BenchTicks()); // KiB/s x100 (c * 4 KiB)
|
||||
return r / 1024; // -> MB/s x100 (MiB, same as Seq)
|
||||
}
|
||||
|
||||
// random 4K read at random offsets (NB: hits the FS cache - can't be dropped)
|
||||
dword t_disk_rread()
|
||||
{
|
||||
dword c=0, pos, r, res;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
EnsureRandFile();
|
||||
rnd_state = 0x12345678; // fixed seed: same offsets on every run
|
||||
BenchBegin();
|
||||
do {
|
||||
rnd_state = rnd_state * 1103515245; rnd_state = rnd_state + 12345;
|
||||
r = rnd_state >> 8; r = r % RND_NBLK;
|
||||
pos = r * RND_BLK;
|
||||
res = FileReadAt(#dk_file, buf_disk, RND_BLK, pos);
|
||||
if (res!=0) && (res!=6) return BB_SKIP;
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
r = muldiv(c, 40000, BenchTicks()); // KiB/s x100 (c * 4 KiB)
|
||||
return r / 1024; // -> MB/s x100 (MiB, same as Seq)
|
||||
}
|
||||
|
||||
//---- File system: create/delete files and folders (metadata ops) ----
|
||||
char fs_fn[FS_N*64]; // pre-built file names (no sprintf in the loop)
|
||||
char fs_dn[FS_N*64]; // pre-built folder names
|
||||
|
||||
// names via strcpy/strcat only - the same primitives DiskWritable/DiskPath
|
||||
// use successfully on every disk (no sprintf/itoa in the path building).
|
||||
// No trailing slash anywhere: folder path is "<dir>/bbd_0".
|
||||
void fs_names_build()
|
||||
{
|
||||
dword i, p, dir;
|
||||
char dig[2];
|
||||
dir = disk_dir;
|
||||
if (!dir) dir = "/tmp0/1";
|
||||
dig[1] = 0;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
dig[0] = i + '0';
|
||||
p = i*64; p = p + #fs_fn;
|
||||
strcpy(p, dir); strcat(p, "/bbf_"); strcat(p, #dig); strcat(p, ".tmp");
|
||||
p = i*64; p = p + #fs_dn;
|
||||
strcpy(p, dir); strcat(p, "/bbd_"); strcat(p, #dig);
|
||||
}
|
||||
}
|
||||
|
||||
void fs_wipe() // best-effort removal (also leftovers of a crash)
|
||||
{
|
||||
dword i, p;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
p = i*64; p = p + #fs_fn; dk_delete(p);
|
||||
p = i*64; p = p + #fs_dn; dk_delete(p);
|
||||
}
|
||||
}
|
||||
|
||||
// one unit = create FS_N files (512 B) + FS_N folders, then delete them all.
|
||||
// BOTH phases are probed before timing and degrade independently: not every
|
||||
// writable FS driver implements every fn70 op (e.g. exFAT has no
|
||||
// CreateFolder at all). 0 only when neither files nor folders work.
|
||||
dword t_disk_fs()
|
||||
{
|
||||
dword c=0, i, p, r;
|
||||
byte files_ok, dirs_ok, ok;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
fs_names_build();
|
||||
fs_wipe();
|
||||
files_ok = 0; // probe file create+delete
|
||||
r = FileWrite(#fs_fn, buf_disk, 512);
|
||||
if (r==0) {
|
||||
r = dk_delete(#fs_fn);
|
||||
if (r==0) files_ok = 1;
|
||||
}
|
||||
dirs_ok = 0; // probe folder create+delete
|
||||
r = dk_mkdir(#fs_dn);
|
||||
if (r==0) {
|
||||
r = dk_delete(#fs_dn);
|
||||
if (r==0) dirs_ok = 1;
|
||||
}
|
||||
if (!files_ok) && (!dirs_ok) return BB_SKIP;
|
||||
BenchBegin();
|
||||
do {
|
||||
if (files_ok) {
|
||||
ok = 1;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
p = i*64; p = p + #fs_fn;
|
||||
if (FileWrite(p, buf_disk, 512)) ok = 0;
|
||||
}
|
||||
for (i=0; i<FS_N; i++) { p = i*64; p = p + #fs_fn; dk_delete(p); }
|
||||
if (ok) c += 20; // 2 * FS_N file operations
|
||||
else files_ok = 0; // driver refused mid-run - stop trying
|
||||
}
|
||||
if (dirs_ok) {
|
||||
ok = 1;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
p = i*64; p = p + #fs_dn;
|
||||
if (dk_mkdir(p)) ok = 0;
|
||||
}
|
||||
for (i=0; i<FS_N; i++) { p = i*64; p = p + #fs_dn; dk_delete(p); }
|
||||
if (ok) c += 20; // 2 * FS_N folder operations
|
||||
else dirs_ok = 0;
|
||||
}
|
||||
if (!files_ok) && (!dirs_ok) break;
|
||||
} while (BenchTicks() < 100);
|
||||
fs_wipe(); // belt and braces: no leftovers
|
||||
return PerSecX100(c); // op/s
|
||||
}
|
||||
|
||||
void Register_DISK()
|
||||
{
|
||||
RegisterTest(SECT_DISK, "Seq Write", "MB/s", REF_DWRITE, #t_disk_write);
|
||||
RegisterTest(SECT_DISK, "Seq Read", "MB/s", REF_DREAD, #t_disk_read);
|
||||
RegisterTest(SECT_DISK, "Rand Write", "MB/s", REF_RWRITE, #t_disk_rwrite);
|
||||
RegisterTest(SECT_DISK, "Rand Read", "MB/s", REF_RREAD, #t_disk_rread);
|
||||
RegisterTest(SECT_DISK, "File System", "op/s", REF_FS, #t_disk_fs);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,204 @@
|
||||
#ifndef INCLUDE_TESTS_GPU_H
|
||||
#define INCLUDE_TESTS_GPU_H
|
||||
//========================================================//
|
||||
// Graphics (2D) test module. //
|
||||
// Tests draw into the window canvas (GCV_* region). //
|
||||
//========================================================//
|
||||
|
||||
#define REF_FILL 20000 // 200.00 MPix/s
|
||||
#define REF_BLIT 10000 // 100.00 MPix/s
|
||||
#define REF_READ 5000 // 50.00 MPix/s (VRAM read is slow)
|
||||
#define REF_VWRITE 100000 // 1000.00 MB/s (direct LFB write, WC-dependent)
|
||||
#define REF_LINE 50000 // 500.00 Kline/s
|
||||
#define REF_TEXT 30000 // 300.00 Kchar/s
|
||||
|
||||
char gpu_text_str[] = "BirdBench benchmark 0123456789ab";
|
||||
|
||||
void DrawLine38(dword x1,y1,x2,y2,color)
|
||||
{
|
||||
EAX = 38;
|
||||
EBX = x1<<16+x2;
|
||||
ECX = y1<<16+y2;
|
||||
EDX = color;
|
||||
$int 0x40;
|
||||
}
|
||||
|
||||
//--- Fill rate: solid bars (fn 13) ---
|
||||
dword t_gpu_fill()
|
||||
{
|
||||
dword c=0, col=0, mp;
|
||||
BenchBegin();
|
||||
do {
|
||||
DrawBar(GCV_X, GCV_Y, GCV_W, GCV_H, col);
|
||||
col += 0x0A0C0E;
|
||||
col = col & 0xFFFFFF; // bit 31 = gradient-fill flag (fn13 0x80RRGGBB)!
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
mp = muldiv(c, GCV_W*GCV_H, 1000000); // total mega-pixels
|
||||
return PerSecX100(mp); // MPix/s
|
||||
}
|
||||
|
||||
//--- Blit rate: 24bpp image (fn 7) ---
|
||||
dword t_gpu_blit()
|
||||
{
|
||||
dword c=0, mp;
|
||||
BenchBegin();
|
||||
do { PutImage(GCV_X, GCV_Y, GCV_W, GCV_H, buf_canvas); c++; }
|
||||
while (BenchTicks() < 100);
|
||||
mp = muldiv(c, GCV_W*GCV_H, 1000000);
|
||||
return PerSecX100(mp); // MPix/s
|
||||
}
|
||||
|
||||
//--- Line rate: 1000 lines (fn 38) per unit ---
|
||||
dword t_gpu_lines()
|
||||
{
|
||||
dword c=0, i, col=0x3388CC;
|
||||
dword x1,y1,x2,y2;
|
||||
BenchBegin();
|
||||
do {
|
||||
for (i=0; i<1000; i++) {
|
||||
x1 = i*7; x1 = x1 % GCV_W; x1 = x1 + GCV_X;
|
||||
y1 = i*13; y1 = y1 % GCV_H; y1 = y1 + GCV_Y;
|
||||
x2 = i*29; x2 = x2 % GCV_W; x2 = x2 + GCV_X;
|
||||
y2 = i*23; y2 = y2 % GCV_H; y2 = y2 + GCV_Y;
|
||||
DrawLine38(x1,y1,x2,y2,col);
|
||||
col += 0x0A1420;
|
||||
col = col & 0xFFFFFF; // bit 24 = inversed-line flag (fn38 0x01xxxxxx)!
|
||||
}
|
||||
c++; // 1000 lines = 1 Kline
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // Kline/s
|
||||
}
|
||||
|
||||
//--- Text rate: WriteText (fn 4), both kernel fonts (6x9 + 8x16) ---
|
||||
// Subpixel smoothing is forced for the duration (the heaviest glyph
|
||||
// path), then the user's setting is restored.
|
||||
dword t_gpu_text()
|
||||
{
|
||||
dword c=0, i, y, len, kc, sm;
|
||||
len = strlen(#gpu_text_str);
|
||||
EAX = 48; EBX = 9; $int 0x40; // SSF_GET_FONT_SMOOTH
|
||||
sm = EAX;
|
||||
EAX = 48; EBX = 10; ECX = 2; $int 0x40; // SSF_SET_FONT_SMOOTH: subpixel
|
||||
BenchBegin();
|
||||
do {
|
||||
for (i=0; i<25; i++) {
|
||||
y = i*11; y = y % GCV_H; y = y + GCV_Y;
|
||||
WriteText(GCV_X, y, 0x80, 0x101010, #gpu_text_str); // 6x9
|
||||
}
|
||||
for (i=0; i<25; i++) {
|
||||
y = i*17; y = y % GCV_H; y = y + GCV_Y;
|
||||
WriteText(GCV_X, y, 0x90, 0x101010, #gpu_text_str); // 8x16
|
||||
}
|
||||
c++; // 50 * len chars
|
||||
} while (BenchTicks() < 100);
|
||||
EAX = 48; EBX = 10; ECX = sm; $int 0x40; // restore user's smoothing
|
||||
kc = muldiv(c, 50*len, 1000); // total kilo-chars
|
||||
return PerSecX100(kc); // Kchar/s
|
||||
}
|
||||
|
||||
//--- VRAM read-back: read screen area to RAM (fn 36) ---
|
||||
dword t_gpu_read()
|
||||
{
|
||||
dword c=0, mp, sx, sy;
|
||||
proc_info p;
|
||||
GetProcessInfo(#p, SelfInfo); // fn 36 uses absolute screen coords
|
||||
sx = p.left; sx = sx + 5; sx = sx + GCV_X;
|
||||
sy = p.top; sy = sy + skin_h; sy = sy + GCV_Y;
|
||||
BenchBegin();
|
||||
do {
|
||||
CopyScreen(buf_canvas, sx, sy, GCV_W, GCV_H); // 24bpp -> buf
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
mp = muldiv(c, GCV_W*GCV_H, 1000000);
|
||||
return PerSecX100(mp); // MPix/s
|
||||
}
|
||||
|
||||
//--- direct VRAM write: rep stosd into the LFB via the GS selector ---
|
||||
// The kernel gives apps a writable GS whose base = LFBAddress (see kernel.asm:
|
||||
// "Set base of graphic segment to linear address of LFB"). rep stosd needs
|
||||
// ES:EDI, so we copy GS into ES for the duration, then restore it. No window
|
||||
// clipping here - we fill only our own on-screen rectangle, scanline by scanline.
|
||||
// GS segment limit via LSL (unprivileged). If the video mode has no LFB
|
||||
// (banked VGA), the selector window is tiny and writing past it would #GP.
|
||||
dword gs_limit()
|
||||
{
|
||||
asm {
|
||||
XOR EAX, EAX
|
||||
MOV AX, GS
|
||||
LSL EAX, EAX
|
||||
}
|
||||
}
|
||||
|
||||
inline fastcall vram_fill_gs(EDI, ESI, EDX, ECX) // ofs, dwords/row, row-advance, rows
|
||||
{
|
||||
asm {
|
||||
push es
|
||||
push ebx
|
||||
mov ebx, ecx // ebx = row count
|
||||
mov ax, gs
|
||||
mov es, ax // es -> LFB
|
||||
cld
|
||||
kvw1:
|
||||
mov ecx, esi // dwords per scanline
|
||||
mov eax, 0x00336699 // fill pattern
|
||||
rep stosd
|
||||
add edi, edx // -> next scanline
|
||||
dec ebx
|
||||
jnz kvw1
|
||||
pop ebx
|
||||
pop es
|
||||
}
|
||||
}
|
||||
|
||||
dword t_gpu_vramwrite()
|
||||
{
|
||||
dword c=0, sx, sy, cols, rows, pitch, bpp, bpp_b, off, rowbytes, rowdw, rowadv, bpf, mb, t, lim;
|
||||
proc_info p;
|
||||
GetProcessInfo(#p, SelfInfo);
|
||||
EAX = 61; EBX = 3; $int 0x40; pitch = EAX; // bytes per scanline
|
||||
EAX = 61; EBX = 2; $int 0x40; bpp = EAX; // bits per pixel
|
||||
bpp_b = bpp + 7; bpp_b = bpp_b / 8;
|
||||
if (bpp_b < 1) bpp_b = 4;
|
||||
sx = p.left; sx = sx + 5; sx = sx + GCV_X;
|
||||
sy = p.top; sy = sy + skin_h; sy = sy + GCV_Y;
|
||||
// clamp to the screen - writing past the LFB would #GP (small screens!)
|
||||
if (sx >= screen.w) return BB_SKIP;
|
||||
if (sy >= screen.h) return BB_SKIP;
|
||||
cols = GCV_W; t = sx + cols; if (t > screen.w) cols = screen.w - sx;
|
||||
rows = GCV_H; t = sy + rows; if (t > screen.h) rows = screen.h - sy;
|
||||
rowbytes = cols * bpp_b;
|
||||
if (pitch < rowbytes) pitch = rowbytes;
|
||||
// stosd moves whole dwords: trim the row to a multiple of 4 so rowadv and
|
||||
// the reported byte count match what was actually written (at 24/16 bpp
|
||||
// rowbytes is not divisible by 4 and every row would drift by 1-2 bytes)
|
||||
rowdw = rowbytes / 4;
|
||||
rowbytes = rowdw * 4;
|
||||
rowadv = pitch - rowbytes;
|
||||
t = sx * bpp_b;
|
||||
off = sy * pitch; off = off + t;
|
||||
// last byte we will touch must be inside the GS segment (no-LFB safety)
|
||||
t = rows - 1; t = t * pitch; t = t + off; t = t + rowbytes; t = t - 1;
|
||||
lim = gs_limit();
|
||||
if (t > lim) return BB_SKIP;
|
||||
BenchBegin();
|
||||
do {
|
||||
vram_fill_gs(off, rowdw, rowadv, rows);
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
bpf = rowbytes * rows; // bytes per frame
|
||||
mb = muldiv(c, bpf, 1048576); // total MB written (MiB, like the disk tests)
|
||||
return PerSecX100(mb); // MB/s
|
||||
}
|
||||
|
||||
void Register_GPU()
|
||||
{
|
||||
RegisterTest(SECT_GPU, "Fill Rate", "MPix/s", REF_FILL, #t_gpu_fill);
|
||||
RegisterTest(SECT_GPU, "Blit (f7)", "MPix/s", REF_BLIT, #t_gpu_blit);
|
||||
RegisterTest(SECT_GPU, "VRAM Read (f36)","MPix/s", REF_READ, #t_gpu_read);
|
||||
RegisterTest(SECT_GPU, "VRAM Write (gs)","MB/s", REF_VWRITE, #t_gpu_vramwrite);
|
||||
RegisterTest(SECT_GPU, "Lines (f38)", "Kline/s", REF_LINE, #t_gpu_lines);
|
||||
RegisterTest(SECT_GPU, "Text (f4)", "Kchar/s", REF_TEXT, #t_gpu_text);
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user