2012-11-02 17:16:53 -04:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include "hammer.h"
|
2012-11-09 01:50:07 -05:00
|
|
|
#include "internal.h"
|
2012-11-02 17:16:53 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Usage:
|
2012-11-06 00:20:00 -05:00
|
|
|
Create your parser (i.e., const HParser*), and an array of test cases
|
|
|
|
|
(i.e., HParserTestcase[], terminated by { NULL, 0, NULL }) and then call
|
2012-11-02 17:16:53 -04:00
|
|
|
|
|
|
|
|
HBenchmarkResults* results = h_benchmark(parser, testcases);
|
|
|
|
|
|
|
|
|
|
Then, you can format a report with:
|
|
|
|
|
|
|
|
|
|
h_benchmark_report(stdout, results);
|
|
|
|
|
|
|
|
|
|
or just generate code to make the parser run as fast as possible with:
|
|
|
|
|
|
|
|
|
|
h_benchmark_dump_optimized_code(stdout, results);
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2012-11-05 23:08:18 -05:00
|
|
|
HBenchmarkResults *h_benchmark(const HParser* parser, HParserTestcase* testcases) {
|
2012-11-09 01:50:07 -05:00
|
|
|
return h_benchmark__m(&system_allocator, parser, testcases);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HBenchmarkResults *h_benchmark__m(HAllocator* mm__, const HParser* parser, HParserTestcase* testcases) {
|
2012-11-02 17:16:53 -04:00
|
|
|
// For now, just output the results to stderr
|
|
|
|
|
HParserTestcase* tc = testcases;
|
|
|
|
|
HParserBackend backend = PB_MIN;
|
2012-11-09 01:50:07 -05:00
|
|
|
HBenchmarkResults *ret = h_new(HBenchmarkResults, 1);
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->len = PB_MAX-PB_MIN;
|
2012-11-09 01:50:07 -05:00
|
|
|
ret->results = h_new(HBackendResults, ret->len);
|
2012-11-02 17:16:53 -04:00
|
|
|
|
|
|
|
|
for (backend = PB_MIN; backend < PB_MAX; backend++) {
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].backend = backend;
|
2012-11-02 17:16:53 -04:00
|
|
|
// Step 1: Compile grammar for given parser...
|
|
|
|
|
if (h_compile(parser, PB_MIN, NULL) == -1) {
|
|
|
|
|
// backend inappropriate for grammar...
|
|
|
|
|
fprintf(stderr, "failed\n");
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].compile_success = false;
|
|
|
|
|
ret->results[backend].n_testcases = 0;
|
|
|
|
|
ret->results[backend].failed_testcases = 0;
|
|
|
|
|
ret->results[backend].cases = NULL;
|
2012-11-02 17:16:53 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].compile_success = true;
|
2012-11-02 17:16:53 -04:00
|
|
|
int tc_failed = 0;
|
|
|
|
|
// Step 1: verify all test cases.
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].n_testcases = 0;
|
|
|
|
|
ret->results[backend].failed_testcases = 0;
|
2012-11-02 17:16:53 -04:00
|
|
|
for (tc = testcases; tc->input != NULL; tc++) {
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].n_testcases++;
|
2012-11-02 17:16:53 -04:00
|
|
|
HParseResult *res = h_parse(parser, tc->input, tc->length);
|
|
|
|
|
char* res_unamb;
|
|
|
|
|
if (res != NULL) {
|
|
|
|
|
res_unamb = h_write_result_unamb(res->ast);
|
|
|
|
|
} else
|
|
|
|
|
res_unamb = NULL;
|
|
|
|
|
if ((res_unamb == NULL && tc->output_unambiguous == NULL)
|
|
|
|
|
|| (strcmp(res_unamb, tc->output_unambiguous) != 0)) {
|
|
|
|
|
// test case failed...
|
|
|
|
|
fprintf(stderr, "failed\n");
|
|
|
|
|
// We want to run all testcases, for purposes of generating a
|
|
|
|
|
// report. (eg, if users are trying to fix a grammar for a
|
|
|
|
|
// faster backend)
|
|
|
|
|
tc_failed++;
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].failed_testcases++;
|
2012-11-02 17:16:53 -04:00
|
|
|
}
|
|
|
|
|
h_parse_result_free(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tc_failed > 0) {
|
|
|
|
|
// Can't use this parser; skip to the next
|
|
|
|
|
fprintf(stderr, "Backend failed testcases; skipping benchmark\n");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-09 01:50:07 -05:00
|
|
|
ret->results[backend].cases = h_new(HCaseResult, ret->results[backend].n_testcases);
|
2012-11-08 01:36:19 -05:00
|
|
|
size_t cur_case = 0;
|
|
|
|
|
|
2012-11-02 17:16:53 -04:00
|
|
|
for (tc = testcases; tc->input != NULL; tc++) {
|
|
|
|
|
// The goal is to run each testcase for at least 50ms each
|
|
|
|
|
// TODO: replace this with a posix timer-based benchmark. (cf. timerfd_create, timer_create, setitimer)
|
|
|
|
|
int count = 1, cur;
|
|
|
|
|
struct timespec ts_start, ts_end;
|
|
|
|
|
long long time_diff;
|
|
|
|
|
do {
|
|
|
|
|
count *= 2; // Yes, this means that the first run will run the function twice. This is fine, as we want multiple runs anyway.
|
|
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts_start);
|
|
|
|
|
for (cur = 0; cur < count; cur++) {
|
|
|
|
|
h_parse_result_free(h_parse(parser, tc->input, tc->length));
|
|
|
|
|
}
|
|
|
|
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts_end);
|
|
|
|
|
|
|
|
|
|
// time_diff is in ns
|
|
|
|
|
time_diff = (ts_end.tv_sec - ts_start.tv_sec) * 1000000000 + (ts_end.tv_nsec - ts_start.tv_nsec);
|
|
|
|
|
} while (time_diff < 100000000);
|
2012-11-08 01:36:19 -05:00
|
|
|
ret->results[backend].cases[cur_case].parse_time = (time_diff / count);
|
|
|
|
|
cur_case++;
|
2012-11-02 17:16:53 -04:00
|
|
|
}
|
|
|
|
|
}
|
2012-11-09 01:50:07 -05:00
|
|
|
return ret;
|
2012-11-02 17:16:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void h_benchmark_report(FILE* stream, HBenchmarkResults* result) {
|
2012-11-09 01:50:07 -05:00
|
|
|
for (size_t i=0; i<result->len; ++i) {
|
|
|
|
|
fprintf(stream, "Backend %ld ... \n", i);
|
|
|
|
|
for (size_t j=0; j<result->results[i].n_testcases; ++j) {
|
|
|
|
|
fprintf(stream, "Case %ld: %ld ns/parse\n", j, result->results[i].cases[j].parse_time);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-11-02 17:16:53 -04:00
|
|
|
}
|