Skip to content

Commit f97b655

Browse files
authored
Instead of emulating the whole parsing as stage 1 + stage 2, let us benchmark the real thing. (simdjson#441)
* Instead of emulating the whole parsing as stage 1 + stage 2, let us benchmark the real thing. * Adding explicit constructor. * Adding warning to the benchmark user. * Making re-running optional.
1 parent 1498b78 commit f97b655

4 files changed

Lines changed: 89 additions & 27 deletions

File tree

benchmark/benchmarker.h

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ struct benchmarker {
280280
return all_stages.iterations;
281281
}
282282

283-
really_inline void run_iteration(bool stage1_only=false) {
283+
really_inline void run_iteration(bool stage1_only, bool rerunbothstages) {
284284
// Allocate ParsedJson
285285
collector.start();
286286
ParsedJson pj;
@@ -303,34 +303,50 @@ struct benchmarker {
303303
exit_error(string("Failed to parse ") + filename + " during stage 1: " + pj.get_error_message());
304304
}
305305

306-
// Stage 2 (unified machine)
307-
event_count stage2_count;
308-
if (!stage1_only || stats == NULL) {
309-
if (!stage1_only) {
310-
collector.start();
311-
}
312-
result = parser.stage2((const uint8_t *)json.data(), json.size(), pj);
313-
if (!stage1_only) {
314-
stage2_count = collector.end();
315-
stage2 << stage2_count;
316-
}
306+
// Stage 2 (unified machine) and the rest
317307

308+
if (stage1_only) {
309+
all_stages << stage1_count;
310+
} else {
311+
event_count stage2_count;
312+
collector.start();
313+
result = parser.stage2((const uint8_t *)json.data(), json.size(), pj);
318314
if (result != simdjson::SUCCESS) {
319-
exit_error(string("Failed to parse ") + filename + " during stage 2: " + pj.get_error_message());
315+
exit_error(string("Failed to parse ") + filename + " during stage 2 parsing " + pj.get_error_message());
316+
}
317+
stage2_count = collector.end();
318+
stage2 << stage2_count;
319+
if(rerunbothstages) {
320+
// You would think that the entire processing is just stage 1 + stage 2, but
321+
// empirically, that's not true! Not even close to be true in some instances.
322+
event_count allstages_count;
323+
collector.start();
324+
result = parser.parse((const uint8_t *)json.data(), json.size(), pj);
325+
if (result != simdjson::SUCCESS) {
326+
exit_error(string("Failed to parse ") + filename + " during overall parsing " + pj.get_error_message());
327+
}
328+
allstages_count = collector.end();
329+
all_stages << allstages_count;
330+
} else {
331+
// we are optimistic
332+
all_stages << stage1_count + stage2_count;
320333
}
321334
}
322-
323-
all_stages << (stage1_count + stage2_count);
324-
325335
// Calculate stats the first time we parse
326336
if (stats == NULL) {
337+
if (stage1_only) { // we need stage 2 once
338+
result = parser.stage2((const uint8_t *)json.data(), json.size(), pj);
339+
if (result != simdjson::SUCCESS) {
340+
printf("Warning: failed to parse during stage 2. Unable to acquire statistics.\n");
341+
}
342+
}
327343
stats = new json_stats(json, pj);
328344
}
329345
}
330346

331-
really_inline void run_iterations(size_t iterations, bool stage1_only=false) {
347+
really_inline void run_iterations(size_t iterations, bool stage1_only, bool rerunbothstages) {
332348
for (size_t i = 0; i<iterations; i++) {
333-
run_iteration(stage1_only);
349+
run_iteration(stage1_only, rerunbothstages);
334350
}
335351
}
336352

@@ -439,6 +455,19 @@ struct benchmarker {
439455
print_aggregate("| ", stage1.best);
440456
printf("|- Stage 2\n");
441457
print_aggregate("| ", stage2.best);
458+
if (collector.has_events()) {
459+
double freq1 = (stage1.best.cycles() / stage1.best.elapsed_sec()) / 1000000000.0;
460+
double freq2 = (stage2.best.cycles() / stage2.best.elapsed_sec()) / 1000000000.0;
461+
double freqall = (all_stages.best.cycles() / all_stages.best.elapsed_sec()) / 1000000000.0;
462+
double freqmin = std::min(freq1, freq2);
463+
double freqmax = std::max(freq1, freq2);
464+
if((freqall < 0.95 * freqmin) or (freqall > 1.05 * freqmax)) {
465+
printf("\nWarning: The processor frequency fluctuates in an expected way!!!\n"
466+
"Expect the overall speed not to match stage 1 and stage 2 speeds.\n"
467+
"Range for stage 1 and stage 2 : [%.3f GHz, %.3f GHz], overall: %.3f GHz.\n",
468+
freqmin, freqmax, freqall);
469+
}
470+
}
442471
}
443472
}
444473
};

benchmark/event_counter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ struct event_collector {
128128
return linux_events.is_working();
129129
}
130130
#else
131+
event_collector() {}
131132
bool has_events() {
132133
return false;
133134
}

benchmark/json_parser.h

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ using std::string;
4343

4444
using stage2_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj);
4545
using stage1_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj);
46+
using jsonparse_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj, bool streaming);
4647

4748
stage1_functype* get_stage1_func(const Architecture architecture) {
4849
switch (architecture) {
@@ -83,30 +84,52 @@ stage2_functype* get_stage2_func(const Architecture architecture) {
8384
}
8485
}
8586

87+
jsonparse_functype* get_jsonparse_func(const Architecture architecture) {
88+
switch (architecture) {
89+
#ifdef IS_X86_64
90+
case Architecture::HASWELL:
91+
return &json_parse_implementation<Architecture::HASWELL>;
92+
break;
93+
case Architecture::WESTMERE:
94+
return &json_parse_implementation<Architecture::WESTMERE>;
95+
break;
96+
#endif
97+
#ifdef IS_ARM64
98+
case Architecture::ARM64:
99+
return &json_parse_implementation<Architecture::ARM64>;
100+
break;
101+
#endif
102+
default:
103+
std::cerr << "The processor is not supported by simdjson." << std::endl;
104+
exit(EXIT_FAILURE);
105+
}
106+
}
107+
86108
struct json_parser {
87109
const Architecture architecture;
88110
const stage1_functype *stage1_func;
89111
const stage2_functype *stage2_func;
112+
const jsonparse_functype *jsonparse_func;
90113

91114
json_parser(const Architecture _architecture) : architecture(_architecture) {
92115
this->stage1_func = get_stage1_func(architecture);
93116
this->stage2_func = get_stage2_func(architecture);
117+
this->jsonparse_func = get_jsonparse_func(architecture);
94118
}
95119
json_parser() : json_parser(find_best_supported_architecture()) {}
96120

97121
int stage1(const uint8_t *buf, const size_t len, ParsedJson &pj) const {
98122
return this->stage1_func(buf, len, pj);
99123
}
124+
100125
int stage2(const uint8_t *buf, const size_t len, ParsedJson &pj) const {
101126
return this->stage2_func(buf, len, pj);
102127
}
103128

104129
int parse(const uint8_t *buf, const size_t len, ParsedJson &pj) const {
105-
int result = this->stage1(buf, len, pj);
106-
if (result == SUCCESS) {
107-
result = this->stage2(buf, len, pj);
108-
}
109-
return result;
130+
// yes, you can construct jsonparse from stage 1 and stage 2,
131+
// but why emulate it when we have the real thing?
132+
return this->jsonparse_func(buf, len, pj, false);
110133
}
111134
};
112135

benchmark/parse.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,14 @@ void print_usage(ostream& out) {
6868
out << "-t - Tabbed data output" << endl;
6969
out << "-v - Verbose output." << endl;
7070
out << "-s STAGE - Stop after the given stage." << endl;
71-
out << " -s stage1 - Stop after find_structural_bits." << endl;
72-
out << " -s all - Run all stages." << endl;
71+
out << " -s stage1 - Stop after find_structural_bits." << endl;
72+
out << " -s all - Run all stages." << endl;
73+
out << " -s allfast - Run all stages." << endl;
74+
7375
out << "-a ARCH - Use the parser with the designated architecture (HASWELL, WESTMERE" << endl;
7476
out << " or ARM64). By default, detects best supported architecture." << endl;
77+
out << "-o - Estimate the overall speed as stage 1 + stage 2 instead of a rerun of both" << endl;
78+
7579
}
7680

7781
void exit_usage(string message) {
@@ -91,6 +95,7 @@ struct option_struct {
9195

9296
bool verbose = false;
9397
bool tabbed_output = false;
98+
bool rerunbothstages = true;
9499

95100
option_struct(int argc, char **argv) {
96101
#ifndef _MSC_VER
@@ -121,6 +126,10 @@ struct option_struct {
121126
stage1_only = true;
122127
} else if (!strcmp(optarg, "all")) {
123128
stage1_only = false;
129+
rerunbothstages = true; // for safety
130+
} else if (!strcmp(optarg, "allfast")) {
131+
stage1_only = false;
132+
rerunbothstages = false;
124133
} else {
125134
exit_usage(string("Unsupported option value -s ") + optarg + ": expected -s stage1 or all");
126135
}
@@ -195,7 +204,7 @@ int main(int argc, char *argv[]) {
195204
// Benchmark each file once per iteration
196205
for (size_t f=0; f<options.files.size(); f++) {
197206
verbose() << "[verbose] " << benchmarkers[f]->filename << " iterations #" << iteration << "-" << (iteration+options.iteration_step-1) << endl;
198-
benchmarkers[f]->run_iterations(options.iteration_step, true);
207+
benchmarkers[f]->run_iterations(options.iteration_step, true, false);
199208
}
200209
}
201210
} else {
@@ -204,7 +213,7 @@ int main(int argc, char *argv[]) {
204213
// Benchmark each file once per iteration
205214
for (size_t f=0; f<options.files.size(); f++) {
206215
verbose() << "[verbose] " << benchmarkers[f]->filename << " iterations #" << iteration << "-" << (iteration+options.iteration_step-1) << endl;
207-
benchmarkers[f]->run_iterations(options.iteration_step, false);
216+
benchmarkers[f]->run_iterations(options.iteration_step, false, options.rerunbothstages);
208217
}
209218
}
210219
}

0 commit comments

Comments
 (0)