2024-03-03 12:46:35 +00:00
|
|
|
#define _CRT_SECURE_NO_WARNINGS
|
2024-03-03 11:33:57 +00:00
|
|
|
#include <stdbool.h>
|
2024-03-03 11:37:43 +00:00
|
|
|
#include <stdio.h>
|
2024-03-03 11:33:57 +00:00
|
|
|
#include <Windows.h>
|
2024-03-03 12:46:35 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <io.h>
|
|
|
|
#include <sys/stat.h>
|
2024-03-03 11:33:57 +00:00
|
|
|
|
|
|
|
#include "base.h"
|
2024-03-05 10:45:07 +00:00
|
|
|
#include "listing_0108_platform_metrics.cpp"
|
2024-03-03 11:33:57 +00:00
|
|
|
#include "base.c"
|
2024-03-04 06:06:26 +00:00
|
|
|
#include "repetition_tester.h"
|
2024-03-03 11:33:57 +00:00
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
// NOTE: Allocate //////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Str8 AllocTypeStr8(AllocType type)
|
|
|
|
{
|
|
|
|
Str8 result = {};
|
|
|
|
switch (type) {
|
|
|
|
case AllocType_None: result = STR8("Pre-Allocated"); break;
|
|
|
|
case AllocType_VirtualAlloc: result = STR8("VirtualAlloc"); break;
|
|
|
|
case AllocType_Malloc: result = STR8("Malloc"); break;
|
|
|
|
case AllocType_Count: break;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestAlloc(ReadArgs *args, Buffer *buffer)
|
|
|
|
{
|
|
|
|
switch (args->alloc_type) {
|
|
|
|
case AllocType_None: break;
|
|
|
|
case AllocType_VirtualAlloc: {
|
|
|
|
buffer->data = VirtualAlloc(/*LPVOID lpAddress*/ NULL,
|
|
|
|
/*SIZE_T dwSize*/ buffer->size,
|
|
|
|
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
|
|
|
|
/*DWORD flProtect*/ PAGE_READWRITE);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case AllocType_Malloc: {
|
|
|
|
buffer->data = malloc(buffer->size);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case AllocType_Count: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestDealloc(ReadArgs *args, Buffer *buffer)
|
|
|
|
{
|
|
|
|
switch (args->alloc_type) {
|
|
|
|
case AllocType_None: break;
|
|
|
|
case AllocType_Malloc: free(buffer->data); break;
|
|
|
|
case AllocType_VirtualAlloc: VirtualFree(buffer->data, 0, MEM_RELEASE); break;
|
|
|
|
case AllocType_Count: break;
|
|
|
|
}
|
|
|
|
}
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
// NOTE: RepTester /////////////////////////////////////////////////////////////////////////////////
|
2024-03-03 12:46:35 +00:00
|
|
|
void RepTester_Error(RepTester *tester, Str8 msg)
|
|
|
|
{
|
|
|
|
tester->mode = RepTesterMode_Error;
|
|
|
|
fprintf(stderr, "ERROR: %.*s\n", STR8_FMT(msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RepTester_BeginTime(RepTester *tester)
|
|
|
|
{
|
|
|
|
tester->open_block_count++;
|
2024-03-05 10:45:07 +00:00
|
|
|
tester->accumulated_on_this_test.e[RepTesterValueType_CPUTimer] -= ReadCPUTimer();
|
|
|
|
tester->accumulated_on_this_test.e[RepTesterValueType_MemPageFaults] -= ReadOSPageFaultCount();
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RepTester_EndTime(RepTester *tester)
|
|
|
|
{
|
|
|
|
tester->close_block_count++;
|
2024-03-05 10:45:07 +00:00
|
|
|
tester->accumulated_on_this_test.e[RepTesterValueType_CPUTimer] += ReadCPUTimer();
|
|
|
|
tester->accumulated_on_this_test.e[RepTesterValueType_MemPageFaults] += ReadOSPageFaultCount();
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RepTester_CountBytes(RepTester *tester, size_t bytes_read)
|
|
|
|
{
|
2024-03-05 10:45:07 +00:00
|
|
|
tester->accumulated_on_this_test.e[RepTesterValueType_ByteCount] += bytes_read;
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void PrintTime(Str8 label, f64 cpu_time, u64 cpu_timer_freq, u64 byte_count)
|
|
|
|
{
|
|
|
|
printf("%.*s: %.0f", STR8_FMT(label), cpu_time);
|
|
|
|
if (cpu_timer_freq) {
|
|
|
|
f64 seconds = cpu_time / CAST(f64)cpu_timer_freq;
|
|
|
|
printf(" (%fms)", 1000.0f * seconds);
|
|
|
|
|
|
|
|
if (byte_count) {
|
|
|
|
f64 gigabyte = (1024.0f * 1024.0f * 1024.0f);
|
|
|
|
f64 best_bandwidth = byte_count / (gigabyte * seconds);
|
|
|
|
printf(" %fgb/s", best_bandwidth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
static void RepTester_PrintValue(Str8 label, RepTesterValue value, u64 cpu_timer_freq)
|
2024-03-03 12:46:35 +00:00
|
|
|
{
|
2024-03-05 10:45:07 +00:00
|
|
|
u64 test_count = value.e[RepTesterValueType_TestCount];
|
|
|
|
f64 divisor = test_count ? (f64)test_count : 1;
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
f64 avg_values[ARRAY_UCOUNT(value.e)];
|
|
|
|
for (u64 index = 0; index < ARRAY_UCOUNT(value.e); index++)
|
|
|
|
avg_values[index] = value.e[index] / divisor;
|
|
|
|
|
|
|
|
printf("%.*s: %.0f", STR8_FMT(label), avg_values[RepTesterValueType_CPUTimer]);
|
|
|
|
if (cpu_timer_freq) {
|
|
|
|
f64 seconds = avg_values[RepTesterValueType_CPUTimer] / cpu_timer_freq;
|
|
|
|
printf(" (%fms)", 1000.0f*seconds);
|
|
|
|
|
|
|
|
if (avg_values[RepTesterValueType_ByteCount] > 0) {
|
|
|
|
f64 gigabyte = (1024.0f * 1024.0f * 1024.0f);
|
|
|
|
f64 bandwidth = avg_values[RepTesterValueType_ByteCount] / (gigabyte * seconds);
|
|
|
|
printf(" %fgb/s", bandwidth);
|
|
|
|
}
|
|
|
|
}
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
if(avg_values[RepTesterValueType_MemPageFaults] > 0) {
|
|
|
|
printf(" PF: %0.4f (%0.4fk/fault)",
|
|
|
|
avg_values[RepTesterValueType_MemPageFaults],
|
|
|
|
avg_values[RepTesterValueType_ByteCount] / (avg_values[RepTesterValueType_MemPageFaults] * 1024.0));
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
static void RepTester_PrintResult(RepTesterResults results, u64 cpu_timer_freq)
|
|
|
|
{
|
|
|
|
RepTester_PrintValue(STR8("Min"), results.min, cpu_timer_freq);
|
|
|
|
printf("\n");
|
|
|
|
RepTester_PrintValue(STR8("Max"), results.max, cpu_timer_freq);
|
|
|
|
printf("\n");
|
|
|
|
RepTester_PrintValue(STR8("Avg"), results.total, cpu_timer_freq);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
2024-03-03 12:46:35 +00:00
|
|
|
bool RepTester_IsTesting(RepTester *tester)
|
|
|
|
{
|
|
|
|
if (tester->mode == RepTesterMode_Testing) {
|
|
|
|
u64 current_time = ReadCPUTimer();
|
|
|
|
if (tester->open_block_count) {
|
|
|
|
if (tester->open_block_count != tester->close_block_count)
|
|
|
|
RepTester_Error(tester, STR8("Unbalanced begin/end time"));
|
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
RepTesterValue accum = tester->accumulated_on_this_test;
|
|
|
|
if (accum.e[RepTesterValueType_ByteCount] != tester->desired_bytes_read)
|
2024-03-03 12:46:35 +00:00
|
|
|
RepTester_Error(tester, STR8("Processed byte count mismatch"));
|
|
|
|
|
|
|
|
if (tester->mode == RepTesterMode_Testing) {
|
2024-03-05 10:45:07 +00:00
|
|
|
RepTesterResults *results = &tester->results;
|
|
|
|
accum.e[RepTesterValueType_TestCount] = 1;
|
|
|
|
|
|
|
|
for (u64 index = 0; index < ARRAY_UCOUNT(accum.e); index++) {
|
|
|
|
results->total.e[index] += accum.e[index];
|
|
|
|
}
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
if (results->max.e[RepTesterValueType_CPUTimer] < accum.e[RepTesterValueType_CPUTimer]) {
|
|
|
|
results->max = accum;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (results->min.e[RepTesterValueType_CPUTimer] > accum.e[RepTesterValueType_CPUTimer]) {
|
|
|
|
results->min = accum;
|
2024-03-03 12:46:35 +00:00
|
|
|
|
|
|
|
// NOTE: Reset the trial time when new min time is achieved
|
|
|
|
tester->start_time = current_time;
|
2024-03-05 10:45:07 +00:00
|
|
|
RepTester_PrintValue(STR8("Min"), results->min, tester->cpu_timer_freq);
|
|
|
|
printf(" \r");
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
tester->open_block_count = 0;
|
|
|
|
tester->close_block_count = 0;
|
|
|
|
tester->accumulated_on_this_test = (RepTesterValue){};
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((current_time - tester->start_time) > tester->run_duration) {
|
|
|
|
tester->mode = RepTesterMode_Complete;
|
|
|
|
|
|
|
|
printf(" \r");
|
2024-03-05 10:45:07 +00:00
|
|
|
RepTester_PrintResult(tester->results, tester->cpu_timer_freq);
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool result = tester->mode == RepTesterMode_Testing;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
// NOTE: Read testing functions ////////////////////////////////////////////////////////////////////
|
2024-03-03 12:46:35 +00:00
|
|
|
static void ReadWithFRead(RepTester *tester, ReadArgs *args)
|
|
|
|
{
|
|
|
|
while (RepTester_IsTesting(tester)) {
|
|
|
|
FILE *file = fopen(args->file_name.data, "rb");
|
|
|
|
if (file) {
|
2024-03-04 06:06:26 +00:00
|
|
|
Buffer buffer = args->dest;
|
|
|
|
TestAlloc(args, &buffer);
|
|
|
|
|
2024-03-03 12:46:35 +00:00
|
|
|
RepTester_BeginTime(tester);
|
2024-03-04 06:06:26 +00:00
|
|
|
size_t result = fread(buffer.data, buffer.size, 1, file);
|
2024-03-03 12:46:35 +00:00
|
|
|
RepTester_EndTime(tester);
|
|
|
|
|
|
|
|
if (result == 1) {
|
2024-03-04 06:06:26 +00:00
|
|
|
RepTester_CountBytes(tester, buffer.size);
|
2024-03-03 12:46:35 +00:00
|
|
|
} else {
|
|
|
|
RepTester_Error(tester, STR8("fopen failed"));
|
|
|
|
}
|
2024-03-04 06:06:26 +00:00
|
|
|
TestDealloc(args, &buffer);
|
2024-03-03 12:46:35 +00:00
|
|
|
fclose(file);
|
|
|
|
} else {
|
|
|
|
RepTester_Error(tester, STR8("fopen failed"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ReadWithRead(RepTester *tester, ReadArgs *args)
|
|
|
|
{
|
|
|
|
while (RepTester_IsTesting(tester)) {
|
|
|
|
int file = _open(args->file_name.data, _O_BINARY | _O_RDONLY);
|
|
|
|
if (file != -1) {
|
2024-03-04 06:06:26 +00:00
|
|
|
Buffer buffer = args->dest;
|
|
|
|
TestAlloc(args, &buffer);
|
|
|
|
|
|
|
|
char *dest = buffer.data;
|
|
|
|
u64 space_remaining = buffer.size;
|
2024-03-03 12:46:35 +00:00
|
|
|
while (space_remaining) {
|
2024-03-04 13:17:14 +00:00
|
|
|
u32 read_size = INT_MAX;
|
2024-03-03 12:46:35 +00:00
|
|
|
if ((u64)read_size > space_remaining)
|
|
|
|
read_size = (u32)space_remaining;
|
|
|
|
|
|
|
|
RepTester_BeginTime(tester);
|
|
|
|
int result = _read(file, dest, read_size);
|
|
|
|
RepTester_EndTime(tester);
|
|
|
|
|
|
|
|
if (result == (int)read_size) {
|
|
|
|
RepTester_CountBytes(tester, read_size);
|
|
|
|
} else {
|
|
|
|
RepTester_Error(tester, STR8("_read failed"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
space_remaining -= read_size;
|
|
|
|
dest += read_size;
|
|
|
|
}
|
2024-03-04 06:06:26 +00:00
|
|
|
|
|
|
|
TestDealloc(args, &buffer);
|
2024-03-03 12:46:35 +00:00
|
|
|
_close(file);
|
|
|
|
} else {
|
|
|
|
RepTester_Error(tester, STR8("_open failed"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ReadWithReadFile(RepTester *tester, ReadArgs *args)
|
|
|
|
{
|
|
|
|
while (RepTester_IsTesting(tester)) {
|
2024-03-04 06:06:26 +00:00
|
|
|
HANDLE file = CreateFileA(args->file_name.data,
|
2024-03-03 12:46:35 +00:00
|
|
|
GENERIC_READ,
|
|
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
|
|
0,
|
|
|
|
OPEN_EXISTING,
|
|
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
|
|
0);
|
2024-03-04 06:06:26 +00:00
|
|
|
|
|
|
|
if (file != INVALID_HANDLE_VALUE) {
|
|
|
|
Buffer buffer = args->dest;
|
|
|
|
TestAlloc(args, &buffer);
|
|
|
|
|
|
|
|
char *dest = buffer.data;
|
|
|
|
u64 space_remaining = buffer.size;
|
2024-03-03 12:46:35 +00:00
|
|
|
while (space_remaining) {
|
|
|
|
u32 read_size = UINT32_MAX;
|
|
|
|
if ((u64)read_size > space_remaining)
|
|
|
|
read_size = (u32)space_remaining;
|
|
|
|
|
|
|
|
DWORD bytes_read = 0;
|
|
|
|
RepTester_BeginTime(tester);
|
2024-03-04 06:06:26 +00:00
|
|
|
BOOL result = ReadFile(file, dest, read_size, &bytes_read, 0);
|
2024-03-03 12:46:35 +00:00
|
|
|
RepTester_EndTime(tester);
|
|
|
|
|
|
|
|
if (result && (bytes_read == read_size))
|
|
|
|
RepTester_CountBytes(tester, read_size);
|
|
|
|
else
|
|
|
|
RepTester_Error(tester, STR8("ReadFile failed"));
|
|
|
|
|
|
|
|
space_remaining -= read_size;
|
|
|
|
dest += read_size;
|
|
|
|
}
|
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
TestDealloc(args, &buffer);
|
|
|
|
CloseHandle(file);
|
2024-03-03 12:46:35 +00:00
|
|
|
} else {
|
|
|
|
RepTester_Error(tester, STR8("CreateFileA failed"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
static void WriteToAllBytes(RepTester *tester, ReadArgs *args)
|
|
|
|
{
|
|
|
|
while (RepTester_IsTesting(tester)) {
|
|
|
|
Buffer buffer = args->dest;
|
|
|
|
TestAlloc(args, &buffer);
|
|
|
|
RepTester_BeginTime(tester);
|
|
|
|
for (u64 index = 0; index < buffer.size; index++)
|
|
|
|
buffer.data[index] = (u8)index;
|
|
|
|
RepTester_EndTime(tester);
|
|
|
|
RepTester_CountBytes(tester, buffer.size);
|
|
|
|
TestDealloc(args, &buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-03 12:46:35 +00:00
|
|
|
typedef void TestFuncPtr(RepTester *tester, ReadArgs *args);
|
|
|
|
typedef struct TestFunction {
|
|
|
|
Str8 name;
|
|
|
|
TestFuncPtr *func;
|
|
|
|
} TestFunction;
|
|
|
|
|
|
|
|
int main(int argc, char const **argv)
|
2024-03-03 11:33:57 +00:00
|
|
|
{
|
2024-03-04 06:06:26 +00:00
|
|
|
if (argc != 2) {
|
2024-03-03 12:46:35 +00:00
|
|
|
fprintf(stderr, "Usage: %s [existing filename]\n", argv[0]);
|
2024-03-04 06:06:26 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-04 13:17:14 +00:00
|
|
|
Str8 file_name = {.data = CAST(char *) argv[1], .size = strlen(argv[1])};
|
2024-03-03 12:46:35 +00:00
|
|
|
struct __stat64 stat;
|
|
|
|
_stat64(file_name.data, &stat);
|
|
|
|
|
|
|
|
ReadArgs args = {};
|
|
|
|
args.dest.data = VirtualAlloc(NULL, stat.st_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
|
|
args.dest.size = stat.st_size;
|
|
|
|
args.file_name = file_name;
|
|
|
|
|
|
|
|
if (stat.st_size <= 0) {
|
|
|
|
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!args.dest.data) {
|
|
|
|
fprintf(stderr, "ERROR: Failed to allocate %zu bytes\n", stat.st_size);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2024-03-05 10:45:07 +00:00
|
|
|
InitializeOSMetrics();
|
|
|
|
|
2024-03-03 12:46:35 +00:00
|
|
|
TestFunction test_functions[] = {
|
2024-03-05 10:45:07 +00:00
|
|
|
{STR8("WriteToAllBytes"), WriteToAllBytes},
|
|
|
|
{STR8("fread"), ReadWithFRead},
|
|
|
|
{STR8("_read"), ReadWithRead},
|
|
|
|
{STR8("ReadFile"), ReadWithReadFile},
|
2024-03-03 12:46:35 +00:00
|
|
|
};
|
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
RepTester testers[ARRAY_UCOUNT(test_functions)][AllocType_Count] = {};
|
|
|
|
u64 cpu_timer_freq = EstimateCPUTimerFreq();
|
2024-03-03 12:46:35 +00:00
|
|
|
for (u64 index = 0; index != UINT64_MAX; index++) {
|
2024-03-04 06:06:26 +00:00
|
|
|
for (u64 func_index = 0; func_index < ARRAY_UCOUNT(testers); func_index++) {
|
2024-03-05 10:45:07 +00:00
|
|
|
for (u64 alloc_index = 0; alloc_index < AllocType_Count; alloc_index++) {
|
2024-03-04 06:06:26 +00:00
|
|
|
RepTester *tester = &testers[func_index][alloc_index];
|
|
|
|
TestFunction test_func = test_functions[func_index];
|
|
|
|
|
|
|
|
printf("\n--- %.*s + %.*s ---\n",
|
|
|
|
STR8_FMT(AllocTypeStr8((AllocType)alloc_index)),
|
|
|
|
STR8_FMT(test_func.name));
|
|
|
|
|
|
|
|
if (tester->mode == RepTesterMode_Nil) {
|
2024-03-05 10:45:07 +00:00
|
|
|
tester->mode = RepTesterMode_Testing;
|
|
|
|
tester->desired_bytes_read = args.dest.size;
|
|
|
|
tester->cpu_timer_freq = cpu_timer_freq;
|
|
|
|
tester->results.min.e[RepTesterValueType_CPUTimer] = (u64)-1;
|
2024-03-04 06:06:26 +00:00
|
|
|
} else if (tester->mode == RepTesterMode_Complete) {
|
|
|
|
tester->mode = RepTesterMode_Testing;
|
|
|
|
|
|
|
|
if (tester->desired_bytes_read != args.dest.size)
|
|
|
|
RepTester_Error(tester, STR8("desired_bytes_read changed"));
|
|
|
|
if (tester->cpu_timer_freq != cpu_timer_freq)
|
|
|
|
RepTester_Error(tester, STR8("cpu frequency changed"));
|
|
|
|
}
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
tester->run_duration = /*seconds_to_try*/ 10 * cpu_timer_freq;
|
|
|
|
tester->start_time = ReadCPUTimer();
|
|
|
|
args.alloc_type = alloc_index;
|
2024-03-03 12:46:35 +00:00
|
|
|
|
2024-03-04 06:06:26 +00:00
|
|
|
test_func.func(tester, &args);
|
|
|
|
}
|
2024-03-03 12:46:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-03 11:33:57 +00:00
|
|
|
return 0;
|
|
|
|
}
|