Compare commits

..

6 Commits

Author SHA1 Message Date
601829bd29 Increase length of benchmark strings 2024-11-10 16:29:44 +00:00
4131af3912 Assign match result to volatile in benchmarks
This is needed to avoid the compiler eliding the call in
highly-optimised builds.
2024-11-10 16:28:38 +00:00
97529fdd2b Write some matching benchmarks 2024-11-10 15:22:28 +00:00
e4d3b08bf2 Create benchmarking library 2024-11-10 15:22:28 +00:00
15a6195bf0 Use entr's -c flag in script 2024-11-10 15:17:00 +00:00
b7737fba39 Tweak README 2024-11-03 13:20:26 +00:00
7 changed files with 214 additions and 4 deletions

View File

@@ -19,3 +19,4 @@ endfunction()
add_subdirectory(lib) add_subdirectory(lib)
add_subdirectory(tests) add_subdirectory(tests)
add_subdirectory(demo) add_subdirectory(demo)
add_subdirectory(benchmarks)

6
README
View File

@@ -7,8 +7,8 @@ so here we are.
Grammar Grammar
This engine is not going to be strictly supporting any standard The engine does not support any specific standard's syntax, unless by
syntax; the expression syntax I intend to support follows. coincidence. The grammar I've implemented for expressions is:
regex ::= sequence ( '|' sequence )* regex ::= sequence ( '|' sequence )*
sequence ::= term+ sequence ::= term+
@@ -23,7 +23,7 @@ syntax; the expression syntax I intend to support follows.
The build uses CMake. There are two scripts, build.sh and test.sh, The build uses CMake. There are two scripts, build.sh and test.sh,
which will (much to everybody's shock) build the project and run the which will (much to everybody's shock) build the project and run the
tests. I use Clang but the code is ISO C11, it should compile just tests. I use Clang but the code is ISO C11 so it should compile just
fine with GCC. You might need to faff with CMakeLists.txt to get it fine with GCC. You might need to faff with CMakeLists.txt to get it
to work with another compiler due to command-line flag nonsense. to work with another compiler due to command-line flag nonsense.

20
benchmarks/CMakeLists.txt Normal file
View File

@@ -0,0 +1,20 @@
add_library(benchmarking benchmarking.c)
set_default_target_options(benchmarking)
target_include_directories(benchmarking PUBLIC include)
function(add_benchmark_suite source)
string(REGEX REPLACE ".c$" "" name ${source})
add_executable(${name} ${source})
set_default_target_options(${name})
target_link_libraries(${name} PRIVATE lib benchmarking m)
endfunction()
function(add_benchmark_suites)
foreach(source ${ARGN})
add_benchmark_suite(${source})
endforeach()
endfunction()
add_benchmark_suites(
matching_benchmarks.c
)

85
benchmarks/benchmarking.c Normal file
View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) Camden Dixie O'Brien
* SPDX-License-Identifier: AGPL-3.0-only
*/
#include "benchmarking.h"
#include <assert.h>
#include <math.h>
#include <stdio.h>
#define SWAP(x, y) \
do { \
const double tmp = x; \
x = y; \
y = tmp; \
} while (0)
clock_t benchmark_start, benchmark_end;
static void sort(double *xs, int n)
{
if (n <= 0)
return;
const double pivot = xs[(n - 1) / 2];
int lt = 0;
int eq = 0;
int gt = n - 1;
while (eq <= gt) {
if (xs[eq] < pivot) {
SWAP(xs[eq], xs[lt]);
++lt;
++eq;
} else if (xs[eq] > pivot) {
SWAP(xs[eq], xs[gt]);
--gt;
} else {
++eq;
}
}
sort(xs, lt);
sort(xs + gt + 1, n - (gt + 1));
}
void benchmark_summarise(double *res, int reps, benchmark_summary_t *out)
{
assert(reps > 0);
sort(res, reps);
const double median = res[reps / 2];
double sum = 0;
for (int i = 0; i < reps; ++i)
sum += res[i];
const double mean = sum / reps;
double diff_sum = 0;
for (int i = 0; i < reps; ++i)
diff_sum += pow(res[i] - mean, 2);
const double variance = diff_sum / (reps - 1);
out->reps = reps;
out->total = sum;
out->median = median;
out->mean = mean;
out->min = res[0];
out->max = res[reps - 1];
out->stddev = sqrt(variance);
}
void benchmark_print_header(void)
{
printf(
"%-12s %13s %13s %13s %13s %12s\n", "benchmark", "median (µs)",
"mean (µs)", "min (µs)", "max (µs)", "stddev");
}
void benchmark_print(const char *name, const benchmark_summary_t *s)
{
printf(
"%-12s %12.2f %12.2f %12.2f %12.2f %12.2f\n", name, s->median,
s->mean, s->min, s->max, s->stddev);
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Camden Dixie O'Brien
* SPDX-License-Identifier: AGPL-3.0-only
*/
#ifndef BENCHMARKING_H
#define BENCHMARKING_H
#include <time.h>
typedef struct {
int reps;
double total, median, mean, min, max, stddev;
} benchmark_summary_t;
#define CLOCK_MICROS(c) (1000000 * (double)c / CLOCKS_PER_SEC)
#define BENCHMARKING_BEGIN() benchmark_print_header()
#define BENCHMARKING_END() 0
#define START_CLOCK() \
do { \
benchmark_start = clock(); \
} while (0)
#define STOP_CLOCK() \
do { \
benchmark_end = clock(); \
} while (0)
#define RUN_BENCHMARK(reps, name, fn, ...) \
do { \
double res[reps]; \
for (int i = 0; i < reps; ++i) { \
fn(__VA_ARGS__); \
res[i] = CLOCK_MICROS(benchmark_end) \
- CLOCK_MICROS(benchmark_start); \
} \
benchmark_summary_t summary; \
benchmark_summarise(res, reps, &summary); \
benchmark_print(name, &summary); \
} while (0)
extern clock_t benchmark_start, benchmark_end;
void benchmark_summarise(double *res, int reps, benchmark_summary_t *out);
void benchmark_print_header(void);
void benchmark_print(const char *name, const benchmark_summary_t *summary);
#endif

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) Camden Dixie O'Brien
* SPDX-License-Identifier: AGPL-3.0-only
*/
#include "benchmarking.h"
#include "compile.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#define LEN 1000
#define RANGE_FIRST 'a'
#define RANGE_LAST 'z'
#define CLAMP_CHAR(x) (RANGE_FIRST + x % (RANGE_LAST - RANGE_FIRST + 1))
#define RUN_MATCHING_BENCHMARK(reps, name, regex) \
do { \
fsa_t fsa; \
compile(regex, strlen(regex), &fsa); \
RUN_BENCHMARK(reps, name, matching_benchmark, &fsa); \
fsa_free(&fsa); \
} while (0)
static void matching_benchmark(const fsa_t *fsa)
{
char s[LEN];
for (int j = 0; j < LEN; ++j)
s[j] = CLAMP_CHAR(rand());
volatile bool match;
START_CLOCK();
match = fsa_accepts(fsa, s, LEN);
STOP_CLOCK();
(void)match;
}
int main(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
BENCHMARKING_BEGIN();
RUN_MATCHING_BENCHMARK(10000, "foo or bar", ".*(foo|bar).*");
RUN_MATCHING_BENCHMARK(10000, "regex #1", ".*(abc!?)*|dd+.*");
RUN_MATCHING_BENCHMARK(10000, "regex #2", ".*(l|wh)?[aeiou]+.*");
return BENCHMARKING_END();
}

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
cd "$(git rev-parse --show-toplevel)" cd "$(git rev-parse --show-toplevel)"
find . -not \( -path './.git' -prune \) -not \( -path './build' -prune \) \ find . -not \( -path './.git' -prune \) -not \( -path './build' -prune \) \
| entr -s 'clear && scripts/build.sh && scripts/test.sh' | entr -cs 'scripts/build.sh && scripts/test.sh'