Skip to content

Commit f80668e

Browse files
authored
This removes the crazy alignment requirements. (simdjson#1073)
* This removes the crazy alignment requirements.
1 parent dcb5d47 commit f80668e

7 files changed

Lines changed: 66 additions & 111 deletions

File tree

benchmark/benchmarker.h

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,27 @@ struct progress_bar {
225225
}
226226
};
227227

228+
/**
229+
* The speed at which we can allocate memory is strictly system specific.
230+
* It depends on the OS and the runtime library. It is subject to various
231+
* system-specific knobs. It is not something that we can reasonably
232+
* benchmark with crude timings.
233+
* If someone wants to optimize how simdjson allocate memory, then it will
234+
* almost surely require a distinct benchmarking tool. What is meant by
235+
* "memory allocation" also requires a definition. Doing "new char[size]" can
236+
* do many different things depending on the system.
237+
*/
238+
228239
enum class BenchmarkStage {
229-
ALL,
240+
ALL, // This excludes allocation
230241
ALLOCATE,
231242
STAGE1,
232243
STAGE2
233244
};
234245

235246
const char* benchmark_stage_name(BenchmarkStage stage) {
236247
switch (stage) {
237-
case BenchmarkStage::ALL: return "All";
248+
case BenchmarkStage::ALL: return "All (Without Allocation)";
238249
case BenchmarkStage::ALLOCATE: return "Allocate";
239250
case BenchmarkStage::STAGE1: return "Stage 1";
240251
case BenchmarkStage::STAGE2: return "Stage 2";
@@ -253,8 +264,8 @@ struct benchmarker {
253264
// Statistics about the JSON file independent of its speed (amount of utf-8, structurals, etc.).
254265
// Loaded on first parse.
255266
json_stats* stats;
256-
// Speed and event summary for full parse (including allocation, stage 1 and stage 2)
257-
event_aggregate all_stages{};
267+
// Speed and event summary for full parse (stage 1 and stage 2, but *excluding* allocation)
268+
event_aggregate all_stages_without_allocation{};
258269
// Speed and event summary for stage 1
259270
event_aggregate stage1{};
260271
// Speed and event summary for stage 2
@@ -285,23 +296,24 @@ struct benchmarker {
285296

286297
const event_aggregate& operator[](BenchmarkStage stage) const {
287298
switch (stage) {
288-
case BenchmarkStage::ALL: return this->all_stages;
299+
case BenchmarkStage::ALL: return this->all_stages_without_allocation;
289300
case BenchmarkStage::STAGE1: return this->stage1;
290301
case BenchmarkStage::STAGE2: return this->stage2;
291302
case BenchmarkStage::ALLOCATE: return this->allocate_stage;
292-
default: exit_error("Unknown stage"); return this->all_stages;
303+
default: exit_error("Unknown stage"); return this->all_stages_without_allocation;
293304
}
294305
}
295306

296307
int iterations() const {
297-
return all_stages.iterations;
308+
return all_stages_without_allocation.iterations;
298309
}
299310

300311
really_inline void run_iteration(bool stage1_only, bool hotbuffers=false) {
301312
// Allocate dom::parser
302313
collector.start();
303314
dom::parser parser;
304-
error_code error = parser.allocate(json.size());
315+
// We always allocate at least 64KB. Smaller allocations may actually be slower under some systems.
316+
error_code error = parser.allocate(json.size() < 65536 ? 65536 : json.size());
305317
if (error) {
306318
exit_error(string("Unable to allocate_stage ") + to_string(json.size()) + " bytes for the JSON result: " + error_message(error));
307319
}
@@ -329,7 +341,7 @@ struct benchmarker {
329341
// Stage 2 (unified machine) and the rest
330342

331343
if (stage1_only) {
332-
all_stages << stage1_count;
344+
all_stages_without_allocation << stage1_count;
333345
} else {
334346
event_count stage2_count;
335347
collector.start();
@@ -339,7 +351,7 @@ struct benchmarker {
339351
}
340352
stage2_count = collector.end();
341353
stage2 << stage2_count;
342-
all_stages << allocate_count + stage1_count + stage2_count;
354+
all_stages_without_allocation << stage1_count + stage2_count;
343355
}
344356
// Calculate stats the first time we parse
345357
if (stats == NULL) {
@@ -386,7 +398,7 @@ struct benchmarker {
386398
prefix,
387399
"Speed",
388400
stage.elapsed_ns() / static_cast<double>(stats->blocks), // per block
389-
percent(stage.elapsed_sec(), all_stages.elapsed_sec()), // %
401+
percent(stage.elapsed_sec(), all_stages_without_allocation.elapsed_sec()), // %
390402
stage.elapsed_ns() / static_cast<double>(stats->bytes), // per byte
391403
stage.elapsed_ns() / static_cast<double>(stats->structurals), // per structural
392404
(static_cast<double>(json.size()) / 1000000000.0) / stage.elapsed_sec() // GB/s
@@ -397,7 +409,7 @@ struct benchmarker {
397409
prefix,
398410
"Cycles",
399411
stage.cycles() / static_cast<double>(stats->blocks),
400-
percent(stage.cycles(), all_stages.cycles()),
412+
percent(stage.cycles(), all_stages_without_allocation.cycles()),
401413
stage.cycles() / static_cast<double>(stats->bytes),
402414
stage.cycles() / static_cast<double>(stats->structurals),
403415
(stage.cycles() / stage.elapsed_sec()) / 1000000000.0
@@ -406,7 +418,7 @@ struct benchmarker {
406418
prefix,
407419
"Instructions",
408420
stage.instructions() / static_cast<double>(stats->blocks),
409-
percent(stage.instructions(), all_stages.instructions()),
421+
percent(stage.instructions(), all_stages_without_allocation.instructions()),
410422
stage.instructions() / static_cast<double>(stats->bytes),
411423
stage.instructions() / static_cast<double>(stats->structurals),
412424
stage.instructions() / static_cast<double>(stage.cycles())
@@ -417,9 +429,9 @@ struct benchmarker {
417429
prefix,
418430
"Misses",
419431
stage.branch_misses(),
420-
percent(stage.branch_misses(), all_stages.branch_misses()),
432+
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
421433
stage.cache_misses(),
422-
percent(stage.cache_misses(), all_stages.cache_misses()),
434+
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
423435
stage.cache_references()
424436
);
425437
}
@@ -456,14 +468,14 @@ struct benchmarker {
456468
allocate_stage.best.cycles() / static_cast<double>(json.size()),
457469
stage1.best.cycles() / static_cast<double>(json.size()),
458470
stage2.best.cycles() / static_cast<double>(json.size()),
459-
all_stages.best.cycles() / static_cast<double>(json.size()),
460-
gb / all_stages.best.elapsed_sec(),
471+
all_stages_without_allocation.best.cycles() / static_cast<double>(json.size()),
472+
gb / all_stages_without_allocation.best.elapsed_sec(),
461473
gb / stage1.best.elapsed_sec(),
462474
gb / stage2.best.elapsed_sec());
463475
} else {
464476
printf("\"%s\"\t\t\t\t\t%f\t%f\t%f\n",
465477
base,
466-
gb / all_stages.best.elapsed_sec(),
478+
gb / all_stages_without_allocation.best.elapsed_sec(),
467479
gb / stage1.best.elapsed_sec(),
468480
gb / stage2.best.elapsed_sec());
469481
}
@@ -490,10 +502,10 @@ struct benchmarker {
490502
stats->blocks_with_16_structurals_flipped, percent(stats->blocks_with_16_structurals_flipped, stats->blocks));
491503
}
492504
printf("\n");
493-
printf("All Stages\n");
494-
print_aggregate("| " , all_stages.best);
505+
printf("All Stages (excluding allocation)\n");
506+
print_aggregate("| " , all_stages_without_allocation.best);
495507
// frequently, allocation is a tiny fraction of the running time so we omit it
496-
if(allocate_stage.best.elapsed_sec() > 0.01 * all_stages.best.elapsed_sec()) {
508+
if(allocate_stage.best.elapsed_sec() > 0.01 * all_stages_without_allocation.best.elapsed_sec()) {
497509
printf("|- Allocation\n");
498510
print_aggregate("| ", allocate_stage.best);
499511
}
@@ -504,17 +516,16 @@ struct benchmarker {
504516
if (collector.has_events()) {
505517
double freq1 = (stage1.best.cycles() / stage1.best.elapsed_sec()) / 1000000000.0;
506518
double freq2 = (stage2.best.cycles() / stage2.best.elapsed_sec()) / 1000000000.0;
507-
double freqall = (all_stages.best.cycles() / all_stages.best.elapsed_sec()) / 1000000000.0;
519+
double freqall = (all_stages_without_allocation.best.cycles() / all_stages_without_allocation.best.elapsed_sec()) / 1000000000.0;
508520
double freqmin = min(freq1, freq2);
509521
double freqmax = max(freq1, freq2);
510522
if((freqall < 0.95 * freqmin) or (freqall > 1.05 * freqmax)) {
511523
printf("\nWarning: The processor frequency fluctuates in an expected way!!!\n"
512-
"Expect the overall speed not to match stage 1 and stage 2 speeds.\n"
513524
"Range for stage 1 and stage 2 : [%.3f GHz, %.3f GHz], overall: %.3f GHz.\n",
514525
freqmin, freqmax, freqall);
515526
}
516527
}
517-
printf("\n%.1f documents parsed per second (best)\n", 1.0/static_cast<double>(all_stages.best.elapsed_sec()));
528+
printf("\n%.1f documents parsed per second (best)\n", 1.0/static_cast<double>(all_stages_without_allocation.best.elapsed_sec()));
518529
}
519530
}
520531
};

benchmark/minifiercompetition.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ int main(int argc, char *argv[]) {
9393
std::cout << std::endl;
9494
}
9595
char *buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
96+
if(buffer == nullptr) {
97+
std::cerr << "Out of memory!" << std::endl;
98+
abort();
99+
}
96100
memcpy(buffer, p.data(), p.size());
97101
buffer[p.size()] = '\0';
98102

@@ -139,6 +143,10 @@ int main(int argc, char *argv[]) {
139143
!just_data);
140144

141145
char *mini_buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
146+
if(mini_buffer == nullptr) {
147+
std::cerr << "Out of memory" << std::endl;
148+
abort();
149+
}
142150
size_t minisize;
143151
auto minierror = minify(p.data(), p.size(),mini_buffer, minisize);
144152
if (!minierror) { std::cerr << minierror << std::endl; exit(1); }

include/simdjson/dom/parser.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,7 @@ class parser {
391391
/**
392392
* The loaded buffer (reused each time load() is called)
393393
*/
394-
#if defined(_MSC_VER) && _MSC_VER < 1910
395-
// older versions of Visual Studio lack proper support for unique_ptr.
396394
std::unique_ptr<char[]> loaded_bytes;
397-
#else
398-
std::unique_ptr<char[], decltype(&aligned_free_char)> loaded_bytes;
399-
#endif
400395

401396
/** Capacity of loaded_bytes buffer. */
402397
size_t _loaded_bytes_capacity{0};

include/simdjson/inline/padded_string.h

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,21 @@
1212
namespace simdjson {
1313
namespace internal {
1414

15-
// low-level function to allocate memory with padding so we can read past the
16-
// "length" bytes safely. if you must provide a pointer to some data, create it
17-
// with this function: length is the max. size in bytes of the string caller is
18-
// responsible to free the memory (free(...))
15+
// The allocate_padded_buffer function is a low-level function to allocate memory
16+
// with padding so we can read past the "length" bytes safely. It is used by
17+
// the padded_string class automatically. It returns nullptr in case
18+
// of error: the caller should check for a null pointer.
19+
// The length parameter is the maximum size in bytes of the string.
20+
// The caller is responsible to free the memory (e.g., delete[] (...)).
1921
inline char *allocate_padded_buffer(size_t length) noexcept {
20-
// we could do a simple malloc
21-
// return (char *) malloc(length + SIMDJSON_PADDING);
22-
// However, we might as well align to cache lines...
2322
size_t totalpaddedlength = length + SIMDJSON_PADDING;
24-
#if defined(_MSC_VER) && _MSC_VER < 1910
25-
// For legacy Visual Studio 2015 since it does not have proper C++11 support
26-
char *padded_buffer = new[totalpaddedlength];
27-
#else
28-
char *padded_buffer = aligned_malloc_char(64, totalpaddedlength);
29-
#endif
30-
#ifndef NDEBUG
23+
char *padded_buffer = new (std::nothrow) char[totalpaddedlength];
3124
if (padded_buffer == nullptr) {
3225
return nullptr;
3326
}
34-
#endif // NDEBUG
27+
// We write zeroes in the padded region to avoid having uninitized
28+
// garbage. If nothing else, garbage getting read might trigger a
29+
// warning in a memory checking.
3530
memset(padded_buffer + length, 0, totalpaddedlength - length);
3631
return padded_buffer;
3732
} // allocate_padded_buffer()
@@ -74,7 +69,7 @@ inline padded_string::padded_string(padded_string &&o) noexcept
7469
}
7570

7671
inline padded_string &padded_string::operator=(padded_string &&o) noexcept {
77-
aligned_free_char(data_ptr);
72+
delete[] data_ptr;
7873
data_ptr = o.data_ptr;
7974
viable_size = o.viable_size;
8075
o.data_ptr = nullptr; // we take ownership
@@ -92,7 +87,7 @@ inline void padded_string::swap(padded_string &o) noexcept {
9287
}
9388

9489
inline padded_string::~padded_string() noexcept {
95-
aligned_free_char(data_ptr);
90+
delete[] data_ptr;
9691
}
9792

9893
inline size_t padded_string::size() const noexcept { return viable_size; }

include/simdjson/inline/parser.h

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,10 @@ namespace dom {
1515
//
1616
// parser inline implementation
1717
//
18-
#if defined(_MSC_VER) && _MSC_VER < 1910
19-
// older versions of Visual Studio lack proper support for unique_ptr.
2018
really_inline parser::parser(size_t max_capacity) noexcept
2119
: _max_capacity{max_capacity},
2220
loaded_bytes(nullptr) {
2321
}
24-
#else
25-
really_inline parser::parser(size_t max_capacity) noexcept
26-
: _max_capacity{max_capacity},
27-
loaded_bytes(nullptr, &aligned_free_char) {
28-
}
29-
#endif
3022
really_inline parser::parser(parser &&other) noexcept = default;
3123
really_inline parser &parser::operator=(parser &&other) noexcept = default;
3224

@@ -101,19 +93,14 @@ inline simdjson_result<document_stream> parser::load_many(const std::string &pat
10193
inline simdjson_result<element> parser::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) & noexcept {
10294
error_code _error = ensure_capacity(len);
10395
if (_error) { return _error; }
96+
std::unique_ptr<uint8_t[]> tmp_buf;
10497

10598
if (realloc_if_needed) {
106-
const uint8_t *tmp_buf = buf;
107-
buf = (uint8_t *)internal::allocate_padded_buffer(len);
108-
if (buf == nullptr)
109-
return MEMALLOC;
110-
memcpy((void *)buf, tmp_buf, len);
111-
}
112-
113-
_error = implementation->parse(buf, len, doc);
114-
if (realloc_if_needed) {
115-
aligned_free((void *)buf); // must free before we exit
99+
tmp_buf.reset((uint8_t *)internal::allocate_padded_buffer(len));
100+
if (tmp_buf.get() == nullptr) { return MEMALLOC; }
101+
memcpy((void *)tmp_buf.get(), buf, len);
116102
}
103+
_error = implementation->parse(realloc_if_needed ? tmp_buf.get() : buf, len, doc);
117104
if (_error) { return _error; }
118105

119106
return doc.root();

include/simdjson/padded_string.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,12 @@ inline simdjson::padded_string operator "" _padded(const char *str, size_t len)
144144
namespace simdjson {
145145
namespace internal {
146146

147-
// low-level function to allocate memory with padding so we can read past the
148-
// "length" bytes safely. if you must provide a pointer to some data, create it
149-
// with this function: length is the max. size in bytes of the string caller is
150-
// responsible to free the memory (free(...))
147+
// The allocate_padded_buffer function is a low-level function to allocate memory
148+
// with padding so we can read past the "length" bytes safely. It is used by
149+
// the padded_string class automatically. It returns nullptr in case
150+
// of error: the caller should check for a null pointer.
151+
// The length parameter is the maximum size in bytes of the string.
152+
// The caller is responsible to free the memory (e.g., delete[] (...)).
151153
inline char *allocate_padded_buffer(size_t length) noexcept;
152154

153155
} // namespace internal

include/simdjson/portability.h

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -174,48 +174,6 @@ use a 64-bit target such as x64 or 64-bit ARM.")
174174
#define simdjson_strncasecmp strncasecmp
175175
#endif
176176
177-
namespace simdjson {
178-
/** @private portable version of posix_memalign */
179-
static inline void *aligned_malloc(size_t alignment, size_t size) {
180-
void *p;
181-
#ifdef SIMDJSON_VISUAL_STUDIO
182-
p = _aligned_malloc(size, alignment);
183-
#elif defined(__MINGW32__) || defined(__MINGW64__)
184-
p = __mingw_aligned_malloc(size, alignment);
185-
#else
186-
// somehow, if this is used before including "x86intrin.h", it creates an
187-
// implicit defined warning.
188-
if (posix_memalign(&p, alignment, size) != 0) {
189-
return nullptr;
190-
}
191-
#endif
192-
return p;
193-
}
194-
195-
/** @private */
196-
static inline char *aligned_malloc_char(size_t alignment, size_t size) {
197-
return (char *)aligned_malloc(alignment, size);
198-
}
199-
200-
/** @private */
201-
static inline void aligned_free(void *mem_block) {
202-
if (mem_block == nullptr) {
203-
return;
204-
}
205-
#ifdef SIMDJSON_VISUAL_STUDIO
206-
_aligned_free(mem_block);
207-
#elif defined(__MINGW32__) || defined(__MINGW64__)
208-
__mingw_aligned_free(mem_block);
209-
#else
210-
free(mem_block);
211-
#endif
212-
}
213-
214-
/** @private */
215-
static inline void aligned_free_char(char *mem_block) {
216-
aligned_free((void *)mem_block);
217-
}
218-
219177
#ifdef NDEBUG
220178
221179
#ifdef SIMDJSON_VISUAL_STUDIO
@@ -233,5 +191,4 @@ static inline void aligned_free_char(char *mem_block) {
233191
234192
#endif
235193
236-
} // namespace simdjson
237194
#endif // SIMDJSON_PORTABILITY_H

0 commit comments

Comments
 (0)