Added benchmark skeleton, and at least stubs for all of the necessary functions

This commit is contained in:
Dan Hirsch 2012-11-02 17:16:53 -04:00
parent 8eececcac8
commit d0d9a94fd0
10 changed files with 239 additions and 13 deletions

View file

@ -25,17 +25,29 @@ PARSERS := \
attr_bool \
indirect
OUTPUTS := bitreader.o \
hammer.o \
bitwriter.o \
libhammer.a \
pprint.o \
allocator.o \
datastructures.o \
system_allocator.o \
BACKENDS := \
packrat
HAMMER_PARTS := \
bitreader.o \
hammer.o \
bitwriter.o \
pprint.o \
allocator.o \
datastructures.o \
system_allocator.o \
benchmark.o \
compile.o \
$(PARSERS:%=parsers/%.o) \
$(BACKENDS:%=backends/%.o)
TESTS := t_benchmark.o
OUTPUTS := libhammer.a \
test_suite.o \
test_suite \
$(PARSERS:%=parsers/%.o)
$(HAMMER_PARTS) \
$(TESTS)
TOPLEVEL := ../
@ -44,8 +56,7 @@ include ../common.mk
all: libhammer.a
libhammer.a: bitreader.o hammer.o pprint.o allocator.o datastructures.o bitwriter.o system_allocator.o \
$(PARSERS:%=parsers/%.o)
libhammer.a: $(HAMMER_PARTS)
bitreader.o: test_suite.h
hammer.o: hammer.h
@ -56,6 +67,6 @@ all: test_suite
test: test_suite
./test_suite -v
test_suite: test_suite.o libhammer.a
test_suite: test_suite.o $(TESTS) libhammer.a
$(call hush, "Linking $@") $(CC) -o $@ $^ $(LDFLAGS)
endif

15
src/backends/packrat.c Normal file
View file

@ -0,0 +1,15 @@
#include "../internal.h"
int h_packrat_compile(HAllocator* mm__, HParser* parser, const void* params) {
return 0; // No compilation necessary, and everything should work
// out of the box.
}
HParseResult *h_packrat_parse(HAllocator* mm__, HParser* parser, HParseState* parse_state) {
return NULL; // TODO: fill this in.
}
HParserBackendVTable h__packrat_backend_vtable = {
.compile = h_packrat_compile,
.parse = h_packrat_parse
};

88
src/benchmark.c Normal file
View file

@ -0,0 +1,88 @@
#include <stdio.h>
#include <time.h>
#include <string.h>
#include "hammer.h"
/*
Usage:
Create your parser (i.e., HParser*), and then call
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);
*/
HBenchmarkResults *h_benchmark(HParser* parser, HParserTestcase* testcases) {
// For now, just output the results to stderr
HParserTestcase* tc = testcases;
HParserBackend backend = PB_MIN;
for (backend = PB_MIN; backend < PB_MAX; backend++) {
fprintf(stderr, "Compiling for backend %d ... ", backend);
// Step 1: Compile grammar for given parser...
if (h_compile(parser, PB_MIN, NULL) == -1) {
// backend inappropriate for grammar...
fprintf(stderr, "failed\n");
continue;
}
int tc_failed = 0;
// Step 1: verify all test cases.
for (tc = testcases; tc->input != NULL; tc++) {
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++;
}
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;
}
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);
fprintf(stderr, "Case %d: %lld ns/parse\n", (int)(tc - testcases), time_diff / count);
}
}
return NULL;
}
void h_benchmark_report(FILE* stream, HBenchmarkResults* result) {
// TODO: fill in this function
}

15
src/compile.c Normal file
View file

@ -0,0 +1,15 @@
// This file contains functions related to managing multiple parse backends
#include "hammer.h"
#include "internal.h"
static HParserBackendVTable *backends[PB_MAX] = {
&h__packrat_backend_vtable,
};
int h_compile(HParser* parser, HParserBackend backend, const void* params) {
return h_compile__m(&system_allocator, parser, backend, params);
}
int h_compile__m(HAllocator* mm__, HParser* parser, HParserBackend backend, const void* params) {
return backends[backend]->compile(mm__, parser, params);
}

View file

@ -30,6 +30,12 @@ typedef int bool;
typedef struct HParseState_ HParseState;
typedef enum HParserBackend_ {
PB_MIN = 0,
PB_PACKRAT = PB_MIN, // PB_MIN is always the default.
PB_MAX
} HParserBackend;
typedef enum HTokenType_ {
TT_NONE,
TT_BYTES,
@ -112,6 +118,17 @@ typedef struct HParser_ {
void *env;
} HParser;
// {{{ Stuff for benchmarking
typedef struct HParserTestcase_ {
unsigned char* input;
size_t length;
char* output_unambiguous;
} HParserTestcase;
typedef struct HBenchmarkResults_ {
} HBenchmarkResults;
// }}}
// {{{ Preprocessor definitions
#define HAMMER_FN_DECL_NOARG(rtype_t, name) \
rtype_t name(void); \
@ -519,6 +536,15 @@ HAMMER_FN_DECL(char*, h_write_result_unamb, const HParsedToken* tok);
*/
HAMMER_FN_DECL(void, h_pprint, FILE* stream, const HParsedToken* tok, int indent, int delta);
/**
* Build parse tables for the given parser backend. See the
* documentation for the parser backend in question for information
* about the [params] parameter, or just pass in NULL for the defaults.
*
* Returns -1 if grammar cannot be compiled with the specified options; 0 otherwise.
*/
HAMMER_FN_DECL(int, h_compile, HParser* parser, HParserBackend backend, const void* params);
/**
* TODO: Document me
*/
@ -541,4 +567,10 @@ const uint8_t* h_bit_writer_get_buffer(HBitWriter* w, size_t *len);
*/
void h_bit_writer_free(HBitWriter* w);
// {{{ Benchmark functions
HBenchmarkResults *h_benchmark(HParser* parser, HParserTestcase* testcases);
void h_benchmark_report(FILE* stream, HBenchmarkResults* results);
void h_benchmark_dump_optimized_code(FILE* stream, HBenchmarkResults* results);
// }}}
#endif // #ifndef HAMMER_HAMMER__H

View file

@ -109,6 +109,12 @@ struct HParseState_ {
HHashTable *recursion_heads;
};
typedef struct HParserBackendVTable_ {
int (*compile)(HAllocator *mm__, HParser* parser, const void* params);
HParseResult* (*parse)(HAllocator *mm__, HParser* parser, HParseState* parse_state);
} HParserBackendVTable;
/* The (location, parser) tuple used to key the cache.
*/
@ -173,6 +179,10 @@ typedef struct HParserCacheValue_t {
};
} HParserCacheValue;
// Backends {{{
extern HParserBackendVTable h__packrat_backend_vtable;
// }}}
// TODO(thequux): Set symbol visibility for these functions so that they aren't exported.
long long h_read_bits(HInputStream* state, int count, char signed_p);

14
src/t_benchmark.c Normal file
View file

@ -0,0 +1,14 @@
// At this point, this is just a compile/link test.
#include "hammer.h"
HParserTestcase testcases[] = {
{NULL, 0, NULL}
};
void test_benchmark_1() {
HParser *parser = NULL; // TODO: fill this in.
HBenchmarkResults *res = h_benchmark(parser, testcases);
h_benchmark_report(stderr, res);
}