Compare commits

..

11 Commits

Author SHA1 Message Date
Daniel Lemire 875d9279a3 not great 2026-04-13 19:18:24 -04:00
Daniel Lemire a9daa3c5eb new file 2026-04-13 17:56:53 -04:00
Daniel Lemire eb493c485d update 2026-04-12 21:43:04 -04:00
Daniel Lemire 5900ac42d9 better 2026-04-12 18:44:31 -04:00
Daniel Lemire 5bb9c8eed5 inlining. 2026-04-12 18:38:03 -04:00
Daniel Lemire 122bea262a proto 2026-04-12 18:13:58 -04:00
Daniel Lemire e48f7bf98b init 2026-04-12 18:13:58 -04:00
Daniel Lemire 4ec44e88c9 adding memory-file mapping to Windows + better doc (#2676)
* adding memory-file mapping to Windows.

* making memory-file mapping optional under windows, as it is fragile

* removing non-ascii

* saving.

* bumping up

* take 2
2026-04-12 18:12:31 -04:00
Daniel Lemire ede3129081 modernizing the benchmarks (#2679) 2026-04-12 18:09:46 -04:00
Daniel Lemire 8ea4c1c2e6 being more precise 2026-04-12 17:06:27 -04:00
Daniel Lemire 486b2a3828 minor update 2026-04-11 14:45:38 -04:00
42 changed files with 1507 additions and 1744 deletions
+9 -7
View File
@@ -13,18 +13,20 @@ jobs:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo}
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF}
# Exercise the opt-in Windows memory-file mapping path at least once in CI.
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON}
steps:
- name: checkout
uses: actions/checkout@v4
- name: Configure
run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=${{matrix.memory_map}} -B build
- name: Build Debug
run: cmake --build build --config ${{matrix.build_type}} --verbose
- name: Run tests
+35
View File
@@ -75,6 +75,41 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
)
endif()
# padded_memory_map is always available on POSIX. On Windows it is disabled
# by default because it depends on the `CreateFileMapping2` / `MapViewOfFile3`
# APIs, which require Windows 10 version 1803 or later and are exported via
# onecore.lib rather than the default kernel32.lib. Turn this option ON to
# opt into the feature on Windows; simdjson will then set the appropriate
# Windows version macros and link onecore, so everything that links
# simdjson picks up both the compile-time declarations and the import
# library automatically. The option is a no-op on POSIX (where the feature
# is unconditionally enabled).
option(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
"Enable simdjson::padded_memory_map on Windows (requires Windows 10 \
version 1803 or later). Always enabled on POSIX." OFF)
if(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS)
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1
)
if(WIN32)
# Raise the Windows version floor so that <windows.h> declares the
# modern memory-mapping APIs, and link the import library that
# actually exports them. _WIN32_WINNT / WINVER / NTDDI_VERSION together
# tell <sdkddkver.h> which APIs to light up.
simdjson_add_props(
target_compile_definitions PUBLIC
_WIN32_WINNT=0x0A00
WINVER=0x0A00
NTDDI_VERSION=0x0A000006 # NTDDI_WIN10_RS5, Windows 10 version 1809
)
simdjson_add_props(
target_link_libraries PUBLIC
onecore
)
endif()
endif()
if(is_top_project)
option(SIMDJSON_INSTALL "Enable target install" ON)
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
+2 -1
View File
@@ -1,9 +1,10 @@
add_subdirectory(dom)
include_directories( . linux )
include_directories( . )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
link_libraries(counters)
if(SIMDJSON_STATIC_REFLECTION)
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
endif(SIMDJSON_STATIC_REFLECTION)
File diff suppressed because it is too large Load Diff
+1
View File
@@ -40,6 +40,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
#include "json2msgpack/boostjson.h"
#include "partial_tweets/simdjson_ondemand.h"
#include "partial_tweets/simdjson_ondemand_key_selector.h"
#include "partial_tweets/simdjson_dom.h"
#include "partial_tweets/yyjson.h"
#if SIMDJSON_COMPETITION_ONDEMAND_SAJSON
+2 -39
View File
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <cassert>
#include <cctype>
@@ -25,7 +26,6 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
@@ -204,12 +204,8 @@ struct feature_benchmarker {
}
// Rate of 1-7-structural misses per 8-structural flip
double struct1_7_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped);
#endif
}
// Extra cost of an 8-15 structural block over a 1-7 structural block
double struct8_15_cost(BenchmarkStage stage) const {
@@ -221,12 +217,8 @@ struct feature_benchmarker {
}
// Rate of 8-15-structural misses per 8-structural flip
double struct8_15_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped);
#endif
}
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
@@ -239,12 +231,8 @@ struct feature_benchmarker {
}
// Rate of 16-structural misses per 16-structural flip
double struct16_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped);
#endif
}
@@ -258,12 +246,8 @@ struct feature_benchmarker {
}
// Rate of UTF-8 misses per UTF-8 flip
double utf8_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped);
#endif
}
// Extra cost of having escapes in a block
double escape_cost(BenchmarkStage stage) const {
@@ -275,12 +259,8 @@ struct feature_benchmarker {
}
// Rate of escape misses per escape flip
double escape_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped);
#endif
}
@@ -378,22 +358,6 @@ struct feature_benchmarker {
}
};
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
double calc_misses = features.calc_expected_misses(stage, results);
double calc_miss_cost = features.calc_expected_miss_cost(stage, results);
printf(" | %-8s ", benchmark_stage_name(stage));
printf("| %-15s ", filename);
printf("| %8.3g ", features.calc_expected_feature_cost(stage, results));
printf("| %8.3g ", calc_miss_cost);
printf("| %8.3g ", calc);
printf("| %8.3g ", actual);
printf("| %+8.3g ", actual - calc);
printf("| %13llu ", (long long unsigned)(calc_misses));
}
#else
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
@@ -417,7 +381,6 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
}
printf("|\n");
}
#endif
int main(int argc, char *argv[]) {
// Read options
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef _BENCHMARK_H_
#define _BENCHMARK_H_
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
/*
* Prints the best number of operations per cycle where
+4 -11
View File
@@ -1,7 +1,8 @@
#ifndef __BENCHMARKER_H
#define __BENCHMARKER_H
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include "simdjson.h"
#include <cassert>
@@ -28,11 +29,9 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
#include "simdjson.h"
#include <functional>
@@ -423,18 +422,12 @@ struct benchmarker {
stage.instructions() / static_cast<double>(stats->structurals),
stage.instructions() / static_cast<double>(stage.cycles())
);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
// NOTE: removed cycles/miss because it is a somewhat misleading stat
printf("%s%-13s: %7.0f branch misses (%6.2f%%) - %.0f cache misses (%6.2f%%) - %.2f cache references\n",
printf("%s%-13s: %7.0f branch misses (%6.2f%%)\n",
prefix,
"Misses",
stage.branch_misses(),
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
stage.cache_misses(),
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
stage.cache_references()
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses())
);
#endif
}
}
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <random>
#include <vector>
+3 -2
View File
@@ -1,11 +1,12 @@
include_directories( .. ../linux )
include_directories( .. )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
link_libraries(counters)
add_executable(perfdiff perfdiff.cpp)
add_executable(parse parse.cpp)
add_executable(parse_stream parse_stream.cpp)
add_executable(statisticalmodel statisticalmodel.cpp)
add_executable(parse_noutf8validation parse.cpp)
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
+2 -2
View File
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <cassert>
#include <cctype>
@@ -24,7 +25,6 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
-207
View File
@@ -1,207 +0,0 @@
#include <iostream>
#include <unistd.h>
#include "simdjson.h"
#ifdef __linux__
#include "linux-perf-events.h"
#endif
size_t count_nonasciibytes(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += input[i] >> 7;
}
return count;
}
size_t count_backslash(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += (input[i] == '\\') ? 1 : 0;
}
return count;
}
struct stat_s {
size_t integer_count;
size_t float_count;
size_t string_count;
size_t backslash_count;
size_t non_ascii_byte_count;
size_t object_count;
size_t array_count;
size_t null_count;
size_t true_count;
size_t false_count;
size_t byte_count;
size_t structural_indexes_count;
bool valid;
};
using stat_t = struct stat_s;
simdjson_inline void simdjson_process_atom(stat_t &s,
simdjson::dom::element element) {
if (element.is<int64_t>()) {
s.integer_count++;
} else if(element.is<std::string_view>()) {
s.string_count++;
} else if(element.is<double>()) {
s.float_count++;
} else if (element.is<bool>()) {
bool v;
simdjson::error_code error;
if ((error = element.get(v))) { std::cerr << error << std::endl; abort(); }
if (v) {
s.true_count++;
} else {
s.false_count++;
}
} else if (element.is_null()) {
s.null_count++;
}
}
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
simdjson::error_code error;
if (element.is<simdjson::dom::array>()) {
s.array_count++;
simdjson::dom::array array;
if ((error = element.get(array))) { std::cerr << error << std::endl; abort(); }
for (auto child : array) {
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
simdjson_recurse(s, child);
} else {
simdjson_process_atom(s, child);
}
}
} else if (element.is<simdjson::dom::object>()) {
s.object_count++;
simdjson::dom::object object;
if ((error = element.get(object))) { std::cerr << error << std::endl; abort(); }
for (auto field : object) {
s.string_count++; // for key
if (field.value.is<simdjson::dom::array>() || field.value.is<simdjson::dom::object>()) {
simdjson_recurse(s, field.value);
} else {
simdjson_process_atom(s, field.value);
}
}
} else {
simdjson_process_atom(s, element);
}
}
stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
stat_t answer{};
simdjson::dom::parser parser;
simdjson::dom::element doc;
auto error = parser.parse(p).get(doc);
if (error) {
answer.valid = false;
return answer;
}
answer.valid = true;
answer.backslash_count =
count_backslash(reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.non_ascii_byte_count = count_nonasciibytes(
reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.byte_count = p.size();
answer.structural_indexes_count = parser.implementation->n_structural_indexes;
simdjson_recurse(answer, doc);
return answer;
}
int main(int argc, char *argv[]) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
abort();
}
}
#else
int optind = 1;
#endif
if (optind >= argc) {
std::cerr << "Reads json, prints stats. " << std::endl;
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
exit(1);
}
const char *filename = argv[optind];
if (optind + 1 < argc) {
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
<< std::endl;
}
simdjson::padded_string p;
auto error = simdjson::padded_string::load(filename).get(p);
if (error) {
std::cerr << "Could not load the file " << filename << std::endl;
return EXIT_FAILURE;
}
stat_t s = simdjson_compute_stats(p);
if (!s.valid) {
std::cerr << "not a valid JSON" << std::endl;
return EXIT_FAILURE;
}
printf("# integer_count float_count string_count backslash_count "
"non_ascii_byte_count object_count array_count null_count true_count "
"false_count byte_count structural_indexes_count ");
#ifdef __linux__
printf(" stage1_cycle_count stage1_instruction_count stage2_cycle_count "
" stage2_instruction_count stage3_cycle_count "
"stage3_instruction_count ");
#else
printf("(you are not under linux, so perf counters are disaabled)");
#endif
printf("\n");
printf("%zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu ", s.integer_count,
s.float_count, s.string_count, s.backslash_count,
s.non_ascii_byte_count, s.object_count, s.array_count, s.null_count,
s.true_count, s.false_count, s.byte_count, s.structural_indexes_count);
#ifdef __linux__
simdjson::dom::parser parser;
simdjson::error_code alloc_error = parser.allocate(p.size());
if (alloc_error) {
std::cerr << alloc_error << std::endl;
return EXIT_FAILURE;
}
const uint32_t iterations = p.size() < 1 * 1000 * 1000 ? 1000 : 50;
std::vector<int> evts;
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
LinuxEvents<PERF_TYPE_HARDWARE> unified(evts);
unsigned long cy1 = 0, cy2 = 0;
unsigned long cl1 = 0, cl2 = 0;
std::vector<unsigned long long> results;
results.resize(evts.size());
for (uint32_t i = 0; i < iterations; i++) {
unified.start();
// The default template is simdjson::architecture::NATIVE.
bool isok = (parser.implementation->stage1((const uint8_t *)p.data(), p.size(), simdjson::stage1_mode::regular) == simdjson::SUCCESS);
unified.end(results);
cy1 += results[0];
cl1 += results[1];
unified.start();
isok = isok && (parser.implementation->stage2(parser.doc) == simdjson::SUCCESS);
unified.end(results);
cy2 += results[0];
cl2 += results[1];
if (!isok) {
std::cerr << "failure?" << std::endl;
}
}
printf("%f %f %f %f ", static_cast<double>(cy1) / static_cast<double>(iterations), static_cast<double>(cl1) / static_cast<double>(iterations),
static_cast<double>(cy2) / static_cast<double>(iterations), static_cast<double>(cl2) / static_cast<double>(iterations));
#endif // __linux__
printf("\n");
return EXIT_SUCCESS;
}
-201
View File
@@ -1,201 +0,0 @@
#ifndef __EVENT_COUNTER_H
#define __EVENT_COUNTER_H
#ifndef SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
#ifdef __aarch64__
// on ARM, we use just cycles and instructions
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 1
#else
// elsewhere, we try to use four counters.
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 0
#endif
#endif
#include <cassert>
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#ifdef __linux__
#include "linux-perf-events.h"
#include <libgen.h>
#endif
#if __APPLE__ && __aarch64__
#include "apple/apple_arm_events.h"
#endif
#include "simdjson.h"
using std::string;
using std::vector;
using std::chrono::steady_clock;
using std::chrono::time_point;
using std::chrono::duration;
struct event_count {
duration<double> elapsed;
vector<unsigned long long> event_counts;
event_count() : elapsed(0), event_counts{0,0,0,0,0} {}
event_count(const duration<double> _elapsed, const vector<unsigned long long> _event_counts) : elapsed(_elapsed), event_counts(_event_counts) {}
event_count(const event_count& other): elapsed(other.elapsed), event_counts(other.event_counts) { }
// The types of counters (so we can read the getter more easily)
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS
};
#else
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS,
BRANCH_MISSES,
CACHE_REFERENCES,
CACHE_MISSES
};
#endif
double elapsed_sec() const { return duration<double>(elapsed).count(); }
double elapsed_ns() const { return duration<double, std::nano>(elapsed).count(); }
double cycles() const { return static_cast<double>(event_counts[CPU_CYCLES]); }
double instructions() const { return static_cast<double>(event_counts[INSTRUCTIONS]); }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return static_cast<double>(event_counts[BRANCH_MISSES]); }
double cache_references() const { return static_cast<double>(event_counts[CACHE_REFERENCES]); }
double cache_misses() const { return static_cast<double>(event_counts[CACHE_MISSES]); }
#endif
event_count& operator=(const event_count& other) {
this->elapsed = other.elapsed;
this->event_counts = other.event_counts;
return *this;
}
event_count operator+(const event_count& other) const {
return event_count(elapsed+other.elapsed, {
event_counts[0]+other.event_counts[0],
event_counts[1]+other.event_counts[1],
event_counts[2]+other.event_counts[2],
event_counts[3]+other.event_counts[3],
event_counts[4]+other.event_counts[4],
});
}
void operator+=(const event_count& other) {
*this = *this + other;
}
};
struct event_aggregate {
int iterations = 0;
event_count total{};
event_count best{};
event_count worst{};
event_aggregate() {}
void operator<<(const event_count& other) {
if (iterations == 0 || other.elapsed < best.elapsed) {
best = other;
}
if (iterations == 0 || other.elapsed > worst.elapsed) {
worst = other;
}
iterations++;
total += other;
}
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
double total_elapsed_ns() const { return total.elapsed_ns(); }
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
double cycles() const { return total.cycles() / iterations; }
double instructions() const { return total.instructions() / iterations; }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return total.branch_misses() / iterations; }
double cache_references() const { return total.cache_references() / iterations; }
double cache_misses() const { return total.cache_misses() / iterations; }
#endif
};
struct event_collector {
event_count count{};
time_point<steady_clock> start_clock{};
#if defined(__linux__)
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
event_collector() : linux_events(vector<int>{
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
#else
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
PERF_COUNT_HW_BRANCH_MISSES,
PERF_COUNT_HW_CACHE_REFERENCES,
PERF_COUNT_HW_CACHE_MISSES
#endif
}) {}
bool has_events() {
return linux_events.is_working();
}
#elif __APPLE__ && __aarch64__
AppleEvents apple_events;
performance_counters diff;
event_collector() : diff(0) {
apple_events.setup_performance_counters();
}
bool has_events() {
return apple_events.setup_performance_counters();
}
#else
event_collector() {}
bool has_events() {
return false;
}
#endif
simdjson_inline void start() {
#if defined(__linux)
linux_events.start();
#elif __APPLE__ && __aarch64__
if(has_events()) { diff = apple_events.get_counters(); }
#endif
start_clock = steady_clock::now();
}
simdjson_inline event_count& end() {
time_point<steady_clock> end_clock = steady_clock::now();
#if defined(__linux)
linux_events.end(count.event_counts);
#elif __APPLE__ && __aarch64__
if(has_events()) {
performance_counters end = apple_events.get_counters();
diff = end - diff;
}
count.event_counts[0] = diff.cycles;
count.event_counts[1] = diff.instructions;
count.event_counts[2] = diff.missed_branches;
count.event_counts[3] = 0;
count.event_counts[4] = 0;
#endif
count.elapsed = end_clock - start_clock;
return count;
}
};
#endif
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef BENCHMARK_HELPERS_H
#define BENCHMARK_HELPERS_H
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <atomic>
event_collector collector;
+2 -13
View File
@@ -1,7 +1,8 @@
#pragma once
#include "simdjson.h"
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <iostream>
namespace json_benchmark {
@@ -58,11 +59,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) {
state.counters["instructions"] = events.instructions();
state.counters["cycles"] = events.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["branch_miss"] = events.branch_misses();
state.counters["cache_miss"] = events.cache_misses();
state.counters["cache_ref"] = events.cache_references();
#endif
state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration());
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration());
@@ -70,11 +67,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
state.counters["best_instructions"] = events.best.instructions();
state.counters["best_cycles"] = events.best.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["best_branch_miss"] = events.best.branch_misses();
state.counters["best_cache_miss"] = events.best.cache_misses();
state.counters["best_cache_ref"] = events.best.cache_references();
#endif
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration());
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
@@ -95,11 +88,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) {
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
#endif
}
label << " items=" << setw(10) << bench.items_per_iteration() << setw(0);
@@ -1,6 +1,7 @@
#pragma once
#include "json_benchmark/string_runner.h"
#include <fstream>
#include <map>
#include <string>
-105
View File
@@ -1,105 +0,0 @@
#pragma once
#ifdef __linux__
#include <asm/unistd.h> // for __NR_perf_event_open
#include <linux/perf_event.h> // for perf event constants
#include <sys/ioctl.h> // for ioctl
#include <unistd.h> // for syscall
#include <cerrno> // for errno
#include <cstring> // for memset
#include <stdexcept>
#include <iostream>
#include <vector>
template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
int fd;
bool working;
perf_event_attr attribs{};
size_t num_events{};
std::vector<uint64_t> temp_result_vec{};
std::vector<uint64_t> ids{};
public:
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
memset(&attribs, 0, sizeof(attribs));
attribs.type = TYPE;
attribs.size = sizeof(attribs);
attribs.disabled = 1;
attribs.exclude_kernel = 1;
attribs.exclude_hv = 1;
attribs.sample_period = 0;
attribs.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
const int pid = 0; // the current process
const int cpu = -1; // all CPUs
const unsigned long flags = 0;
int group = -1; // no group
num_events = config_vec.size();
ids.resize(config_vec.size());
uint32_t i = 0;
for (auto config : config_vec) {
attribs.config = config;
int _fd = static_cast<int>(syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags));
if (_fd == -1) {
report_error("perf_event_open");
}
ioctl(_fd, PERF_EVENT_IOC_ID, &ids[i++]);
if (group == -1) {
group = _fd;
fd = _fd;
}
}
temp_result_vec.resize(num_events * 2 + 1);
}
~LinuxEvents() { if (fd != -1) { close(fd); } }
inline void start() {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_RESET)");
}
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
}
}
}
inline void end(std::vector<unsigned long long> &results) {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
}
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
report_error("read");
}
}
// our actual results are in slots 1,3,5, ... of this structure
for (uint32_t i = 1; i < temp_result_vec.size(); i += 2) {
results[i / 2] = temp_result_vec[i];
}
for (uint32_t i = 2; i < temp_result_vec.size(); i += 2) {
if(ids[i/2-1] != temp_result_vec[i]) {
report_error("event mismatch");
}
}
}
bool is_working() {
return working;
}
private:
void report_error(const std::string &) {
working = false;
}
};
#endif
@@ -1,6 +1,7 @@
#ifndef BENCHMARK_HELPER_HPP
#define BENCHMARK_HELPER_HPP
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <atomic>
inline event_collector &get_collector() {
+8
View File
@@ -20,6 +20,14 @@ if(SIMDJSON_GOOGLE_BENCHMARKS)
)
endif()
CPMAddPackage(
NAME counters
URL https://github.com/lemire/counters/archive/refs/tags/v3.1.0.zip
OPTIONS
"COUNTERS_BUILD_TESTS OFF"
"COUNTERS_INSTALL OFF"
)
CPMAddPackage(
NAME simdjson-data
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
+34 -3
View File
@@ -269,10 +269,35 @@ ondemand::document doc = parser.iterate(simdjson::pad(json));
We recommend against creating many `std::string` or many `std::padded_string` instances in your application to store your JSON data.
Consider reusing the same buffers and limiting memory allocations.
**Memory-file mapping (non-Windows).** You can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk:
**Memory-file mapping.** You can use `simdjson::padded_memory_map` to create a
`simdjson::padded_string_view` from a file on disk. On POSIX systems (Linux,
macOS, BSD, ...) it uses `mmap` for true zero-copy access and is always
available. On Windows it is an **opt-in** feature because it relies on the
`CreateFileMapping2` / `MapViewOfFile3` APIs (Windows 10, version 1803 or
later) which are exported from `onecore.lib` rather than the default
`kernel32.lib`. To enable it, you must satisfy **all** of the following:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before including simdjson, in every translation
unit that uses `padded_memory_map`.
The Windows implementation then uses `CreateFileMapping2` / `MapViewOfFile3`
for true zero-copy access whenever possible, with a transparent
buffered-read fallback for files that end too close to a page boundary.
The availability of the class can be tested with the preprocessor macro
`SIMDJSON_HAS_PADDED_MEMORY_MAP`.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view();
@@ -305,7 +330,7 @@ Some users may want to browse code along with the compiled assembly:
| `simdjson::pad(std::string&)` | Adds padding if needed | Returns `padded_string_view` pointing to the (possibly resized) string | References original string | Recommended to silence sanitizers when using `std::string`. |
| `padded_string(data, length)` or `padded_string(std::string)` | Automatic (copies into padded buffer) | Explicit copy into owned padded buffer | Owned by `padded_string` | Safe when you want full ownership and padding guaranteed. |
| `padded_string_view` (manual) | User guarantees `SIMDJSON_PADDING` extra bytes after the viewed length | User provides pointer + length + capacity | Non-owning view | Low-level; requires careful buffer management. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping (non-Windows only) | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Advanced; efficient for large files on Linux/macOS/etc. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping / padded read | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Always available on POSIX (zero-copy `mmap`). On Windows, opt-in via `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON` (requires Windows 10 1803+ and links `onecore.lib`) and `#include <windows.h>` before simdjson; uses `CreateFileMapping2` + `MapViewOfFile3`. |
Documents are iterators
@@ -1415,6 +1440,8 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
that are not made by Toyota.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
### 3. Using static reflection (C++26)
@@ -1491,6 +1518,10 @@ void f() {
}
```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
#### Special cases
However, there are instances where the construction cannot
+26 -3
View File
@@ -127,11 +127,31 @@ codepage, and they may call SetFileApisToOEM accordingly.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view`
from a file on disk without copying the file contents into your own buffer.
On POSIX systems (Linux, macOS, BSD, ...) it uses `mmap` for true zero-copy
access. On Windows it is available as an **opt-in** feature and requires:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`.
When enabled on Windows, the implementation uses `CreateFileMapping2` and
`MapViewOfFile3` for true zero-copy mapping whenever the file does not end
within `SIMDJSON_PADDING` bytes of a page boundary; otherwise it falls back
to reading the file into a padded heap buffer. If those requirements are
not met, the class is not declared and the code below will fail to compile.
```cpp
// if the macro _WIN32 is defined, this will not work since we do not support Windows
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(TWITTER_JSON);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
@@ -770,6 +790,9 @@ void basics_treewalk_1() {
}
```
Notice that we do not include `dom::element_type::BIGINT` in this example
as `dom::element_type::BIGINT` type is only generated if the parser was
set to support big integers (`parser.number_as_string(true)`).
Reusing the parser for maximum efficiency
+79 -5
View File
@@ -22,6 +22,7 @@ Contents
- [Threads](#threads)
- [Support](#support)
- [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams)
@@ -156,13 +157,79 @@ for (auto doc : docs) {
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most efficient
way to feed `iterate_many` is to use `simdjson::padded_memory_map`. It returns
a `padded_string_view` with the right amount of trailing padding, so you can
hand it straight to `iterate_many` without ever copying the file contents into
your own buffer.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::ondemand::parser parser;
simdjson::ondemand::document_stream stream;
auto error = parser.iterate_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.iterate(...)`:
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
@@ -512,6 +579,9 @@ Otherwise you may use this longer version for explicit handling of errors:
}
```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
C++26 features (static reflection)
-----------------------------------
@@ -662,3 +732,7 @@ for (auto doc : stream) {
In every case, the user-defined type (`Car` here) does not need a hand-written
`tag_invoke` overload: the library generates the deserialization code from the
type's public data members at compile time.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
+73 -6
View File
@@ -18,6 +18,7 @@ Contents
- [How it works](#how-it-works)
- [Support](#support)
- [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams)
@@ -218,17 +219,83 @@ got full document at 29
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most
efficient way to feed `parse_many` is to use `simdjson::padded_memory_map`.
It returns a `padded_string_view` with the right amount of trailing padding,
so you can pass it directly to `parse_many` without copying the file content
into your own buffer first.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.parse(...)`:
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
simdjson::dom::element doc = parser.parse(view); // parse the JSON
```
Incomplete streams
+7 -1
View File
@@ -208,7 +208,7 @@ You can still make sure of this capability in your code if you are an expert
programmer and you are willing to silence sanitizer warnings.
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view` or a C-style string with length. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
Example usage:
@@ -216,6 +216,12 @@ Example usage:
std::string_view json = get_json_data();
simdjson::padded_input input(json); // Automatically pads if needed
auto result = parser.parse(input);
// Also works with std::string, considering capacity
std::string json_str = get_json_string();
json_str.reserve(json_str.size() + 100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
auto result2 = parser.parse(input2);
```
This simplifies padding management compared to manually checking and allocating.
+9
View File
@@ -59,6 +59,15 @@ concept appendable_containers =
details::supports_add<T> || details::supports_append<T> ||
details::supports_insert<T>) && !string_view_keyed_map<T>;
/// Check if T is a key_selector type for efficient JSON field lookup.
/// T must expose a compile-time N (number of keys) and static match_raw that
/// returns [0, N) on hit or N on miss.
template <typename T>
concept key_selector_type = requires {
{ T::size() } -> std::same_as<std::size_t>;
{ T::N } -> std::convertible_to<std::size_t>;
};
/// Insert into the container however possible
template <appendable_containers T, typename... Args>
constexpr decltype(auto) emplace_one(T &vec, Args &&...args) {
+2
View File
@@ -22,6 +22,8 @@ enum class element_type {
STRING = '"', ///< std::string_view
BOOL = 't', ///< bool
NULL_VALUE = 'n', ///< null
/// The BIGINT type is for integers that do not fit in 64 bits. It is only present
// if you set parser.number_as_string(true).
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
};
+6
View File
@@ -181,6 +181,9 @@ inline simdjson_result<document_stream> parser::parse_many(const std::string &s,
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size) noexcept {
return parse_many(s.data(), s.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size) noexcept {
return parse_many(v.data(), v.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
@@ -217,6 +220,9 @@ inline simdjson_result<document_stream> parser::parse_many(const std::string &s,
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept {
return parse_many(v.data(), v.length(), batch_size, format);
}
simdjson_inline size_t parser::capacity() const noexcept {
return implementation ? implementation->capacity() : 0;
+12
View File
@@ -490,6 +490,16 @@ public:
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
*
* Because padded_string_view guarantees SIMDJSON_PADDING trailing bytes, this
* overload is safe to use with buffers that the caller owns elsewhere (for
* example, a padded_memory_map), with no extra copy. Without this overload,
* passing a padded_string_view would silently bind to the padded_string
* overload via an implicit conversion, allocating and copying the input, and
* because that temporary is destroyed at the end of the full-expression
* leaving the returned document_stream pointing at freed memory. */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
/** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
@@ -510,6 +520,8 @@ public:
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept;
/**
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
@@ -20,6 +20,7 @@
#include "simdjson/generic/ondemand/document.h"
#include "simdjson/generic/ondemand/document_stream.h"
#include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/key_selector.h"
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/serialization.h"
@@ -39,6 +40,7 @@
#include "simdjson/generic/ondemand/logger-inl.h"
#include "simdjson/generic/ondemand/object-inl.h"
#include "simdjson/generic/ondemand/object_iterator-inl.h"
#include "simdjson/generic/ondemand/key_selector_iterator.h"
#include "simdjson/generic/ondemand/parser-inl.h"
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
#include "simdjson/generic/ondemand/token_iterator-inl.h"
+4
View File
@@ -39,6 +39,10 @@ class raw_json_string;
class token_iterator;
class value;
class value_iterator;
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector> class selector_iterator;
template <typename Selector> class selector_range;
#endif
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
@@ -0,0 +1,435 @@
#ifndef SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
#define SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#include "simdjson/base.h"
#include "simdjson/common_defs.h"
#include "simdjson/constevalutil.h"
#include "simdjson/generic/ondemand/raw_json_string.h"
#endif
#include <array>
#include <string_view>
#include <cstddef>
#include <cstdint>
#include <cstring>
#if defined(__aarch64__) || defined(__ARM_NEON)
#include <arm_neon.h>
#define SIMDJSON_KEY_SELECTOR_HAS_NEON 1
#else
#define SIMDJSON_KEY_SELECTOR_HAS_NEON 0
#endif
#if defined(__SSE2__)
#include <emmintrin.h>
#define SIMDJSON_KEY_SELECTOR_HAS_SSE2 1
#else
#define SIMDJSON_KEY_SELECTOR_HAS_SSE2 0
#endif
#if SIMDJSON_SUPPORTS_CONCEPTS
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
namespace key_selector_detail {
inline constexpr std::size_t MAX_POSITIONS = 4;
inline constexpr std::size_t MAX_TABLE_SIZE = 256;
inline constexpr std::uint8_t POS_LAST_CHAR = 0xFF;
inline constexpr std::uint8_t SENTINEL_KEY = 0xFF;
// All PHF tables live inside this structural type; a single instance becomes a
// static constexpr member of key_selector<Keys...>, so every field below is a
// compile-time constant at every call site.
template <std::size_t N, std::size_t TableSize, std::size_t MaxKeyLen>
struct phf_data {
std::array<std::array<std::uint8_t, 256>, MAX_POSITIONS> asso_values{};
std::array<std::uint8_t, MAX_POSITIONS> positions{};
std::uint8_t num_positions{};
std::array<std::uint8_t, TableSize> slot_to_key{};
// slot_key_bytes[s] holds the key stored at slot s, zero-padded to MaxKeyLenPadded.
std::array<std::array<char, ((MaxKeyLen + 31) / 32) * 32>, TableSize> slot_key_bytes{};
std::array<std::uint8_t, TableSize> slot_key_len{};
};
constexpr std::size_t next_pow2(std::size_t n) noexcept {
std::size_t p = 1;
while (p < n) p <<= 1;
return p;
}
// Returns the chosen TableSize (power of two >= N, up to MAX_TABLE_SIZE).
template <std::size_t N>
constexpr std::size_t pick_table_size() noexcept {
std::size_t t = next_pow2(N);
if (t < 2) t = 2;
return t;
}
template <std::size_t N>
constexpr std::size_t char_at(std::string_view key, std::uint8_t pos) noexcept {
if (pos == POS_LAST_CHAR) {
return key.empty() ? 256 : static_cast<unsigned char>(key.back());
}
return (pos < key.size()) ? static_cast<unsigned char>(key[pos]) : 256;
}
// Try one gperf-style PHF configuration. Returns true if a perfect assignment was found.
template <std::size_t N, std::size_t TableSize>
constexpr bool try_phf(
const std::array<std::string_view, N>& keys,
std::array<std::array<std::uint8_t, 256>, MAX_POSITIONS>& asso,
std::array<std::uint8_t, MAX_POSITIONS>& positions,
std::uint8_t& num_positions,
std::array<std::uint8_t, TableSize>& slot_to_key) noexcept
{
// Helper: reset mapping.
auto reset = [&]() {
for (std::size_t i = 0; i < TableSize; ++i) slot_to_key[i] = SENTINEL_KEY;
};
// Attempt 1: length-only.
reset();
{
bool ok = true;
for (std::size_t i = 0; i < N && ok; ++i) {
std::size_t slot = keys[i].size() % TableSize;
if (slot_to_key[slot] != SENTINEL_KEY) { ok = false; break; }
slot_to_key[slot] = static_cast<std::uint8_t>(i);
}
if (ok) { num_positions = 0; return true; }
}
// Attempt 2: single position (0), vary offset.
for (std::size_t offset = 0; offset < TableSize; ++offset) {
reset();
for (std::size_t c = 0; c < 256; ++c)
asso[0][c] = static_cast<std::uint8_t>((c + offset) % TableSize);
positions[0] = 0;
num_positions = 1;
bool ok = true;
for (std::size_t i = 0; i < N && ok; ++i) {
std::size_t h = keys[i].size();
std::size_t ch = char_at<N>(keys[i], 0);
if (ch < 256) h += asso[0][ch];
std::size_t slot = h % TableSize;
if (slot_to_key[slot] != SENTINEL_KEY) { ok = false; break; }
slot_to_key[slot] = static_cast<std::uint8_t>(i);
}
if (ok) return true;
}
// Attempt 3: positions {0, last_char}.
for (std::size_t o1 = 0; o1 < TableSize; ++o1) {
for (std::size_t o2 = 0; o2 < TableSize; ++o2) {
reset();
for (std::size_t c = 0; c < 256; ++c) {
asso[0][c] = static_cast<std::uint8_t>((c + o1) % TableSize);
asso[1][c] = static_cast<std::uint8_t>((c + o2) % TableSize);
}
positions[0] = 0;
positions[1] = POS_LAST_CHAR;
num_positions = 2;
bool ok = true;
for (std::size_t i = 0; i < N && ok; ++i) {
std::size_t h = keys[i].size();
std::size_t c1 = char_at<N>(keys[i], 0);
if (c1 < 256) h += asso[0][c1];
std::size_t c2 = char_at<N>(keys[i], POS_LAST_CHAR);
if (c2 < 256) h += asso[1][c2];
std::size_t slot = h % TableSize;
if (slot_to_key[slot] != SENTINEL_KEY) { ok = false; break; }
slot_to_key[slot] = static_cast<std::uint8_t>(i);
}
if (ok) return true;
}
}
return false;
}
template <std::size_t N, std::size_t TableSize, std::size_t MaxKeyLen>
consteval phf_data<N, TableSize, MaxKeyLen>
compute_phf(const std::array<std::string_view, N>& keys) {
// Validate.
for (std::size_t i = 0; i < N; ++i) {
if (keys[i].empty()) throw "empty keys are not allowed in key_selector";
if (keys[i].size() > MaxKeyLen) throw "key length exceeds MaxKeyLen";
for (char c : keys[i]) {
if (c == '\\') throw "backslash not allowed in key_selector keys";
if (c == '"') throw "quote not allowed in key_selector keys";
if (c == '\0') throw "null byte not allowed in key_selector keys";
}
for (std::size_t j = i + 1; j < N; ++j)
if (keys[i] == keys[j]) throw "duplicate keys in key_selector";
}
phf_data<N, TableSize, MaxKeyLen> out{};
for (std::size_t s = 0; s < TableSize; ++s) out.slot_to_key[s] = SENTINEL_KEY;
if (!try_phf<N, TableSize>(keys, out.asso_values, out.positions,
out.num_positions, out.slot_to_key))
throw "key_selector PHF generation failed";
// Populate slot key bytes (zero-padded) and lengths.
for (std::size_t s = 0; s < TableSize; ++s) {
std::uint8_t ki = out.slot_to_key[s];
if (ki < N) {
auto k = keys[ki];
out.slot_key_len[s] = static_cast<std::uint8_t>(k.size());
for (std::size_t c = 0; c < k.size(); ++c)
out.slot_key_bytes[s][c] = k[c];
} else {
out.slot_key_len[s] = 0; // sentinel: no length can match
}
}
return out;
}
// --- SIMD primitives --------------------------------------------------------
// Scan for the terminating '"' starting at p. Returns its byte offset (= key length).
// Reads at most 16 bytes (if MaxKeyLen <= 15) else up to MaxKeyLen+1 bytes.
// Caller guarantees SIMDJSON_PADDING bytes past the JSON buffer, so the load is safe.
template <std::size_t MaxKeyLen>
simdjson_really_inline std::size_t scan_key_length(const char* p) noexcept {
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
uint8x16_t v0 = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
uint8x16_t cmp0 = vceqq_u8(v0, vdupq_n_u8('"'));
uint64_t m0 = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp0), 4)), 0);
if constexpr (MaxKeyLen < 16) {
// Only the first 16 bytes are relevant.
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctzll(m0)) >> 2;
return MaxKeyLen + 1;
} else {
uint8x16_t v1 = vld1q_u8(reinterpret_cast<const uint8_t*>(p) + 16);
uint8x16_t cmp1 = vceqq_u8(v1, vdupq_n_u8('"'));
uint64_t m1 = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp1), 4)), 0);
// Combine into a single 128-bit-ish mask. If m0 != 0, first-byte lives there.
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctzll(m0)) >> 2;
if (m1 != 0) return 16 + (std::size_t(__builtin_ctzll(m1)) >> 2);
return MaxKeyLen + 1;
}
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
__m128i v0 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
__m128i cmp0 = _mm_cmpeq_epi8(v0, _mm_set1_epi8('"'));
unsigned m0 = static_cast<unsigned>(_mm_movemask_epi8(cmp0));
if constexpr (MaxKeyLen < 16) {
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctz(m0));
return MaxKeyLen + 1;
} else {
__m128i v1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16));
__m128i cmp1 = _mm_cmpeq_epi8(v1, _mm_set1_epi8('"'));
unsigned m1 = static_cast<unsigned>(_mm_movemask_epi8(cmp1));
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctz(m0));
if (m1 != 0) return 16 + std::size_t(__builtin_ctz(m1));
return MaxKeyLen + 1;
}
#else
for (std::size_t i = 0; i <= MaxKeyLen; ++i)
if (p[i] == '"') return i;
return MaxKeyLen + 1;
#endif
}
// Byte-equal of p[0..len) against stored[0..len). stored is zero-padded past `len`.
// Input is read over 16 or 32 bytes (padded JSON buffer guaranteed).
template <std::size_t MaxKeyLen>
simdjson_really_inline bool compare_key_bytes(
const char* p, const char* stored, std::size_t len) noexcept
{
alignas(16) static constexpr uint8_t idx16[16] =
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
if constexpr (MaxKeyLen <= 16) {
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
uint8x16_t vp = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
uint8x16_t vs = vld1q_u8(reinterpret_cast<const uint8_t*>(stored));
uint8x16_t mask = vcltq_u8(vld1q_u8(idx16), vdupq_n_u8(static_cast<uint8_t>(len)));
uint8x16_t diff = veorq_u8(vandq_u8(vp, mask), vs);
return vmaxvq_u8(diff) == 0;
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
__m128i vp = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
__m128i vs = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored));
__m128i idx = _mm_load_si128(reinterpret_cast<const __m128i*>(idx16));
__m128i mask = _mm_cmplt_epi8(idx, _mm_set1_epi8(static_cast<char>(len)));
__m128i eq = _mm_cmpeq_epi8(_mm_and_si128(vp, mask), vs);
return _mm_movemask_epi8(eq) == 0xFFFF;
#else
for (std::size_t i = 0; i < len; ++i)
if (p[i] != stored[i]) return false;
return true;
#endif
} else if constexpr (MaxKeyLen <= 32) {
// Two 16-byte lanes. JSON buffer is padded so the second load is safe.
alignas(16) static constexpr uint8_t idx32_hi[16] =
{16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
uint8x16_t vp_lo = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
uint8x16_t vp_hi = vld1q_u8(reinterpret_cast<const uint8_t*>(p) + 16);
uint8x16_t vs_lo = vld1q_u8(reinterpret_cast<const uint8_t*>(stored));
uint8x16_t vs_hi = vld1q_u8(reinterpret_cast<const uint8_t*>(stored) + 16);
uint8x16_t lenv = vdupq_n_u8(static_cast<uint8_t>(len));
uint8x16_t m_lo = vcltq_u8(vld1q_u8(idx16), lenv);
uint8x16_t m_hi = vcltq_u8(vld1q_u8(idx32_hi), lenv);
uint8x16_t d_lo = veorq_u8(vandq_u8(vp_lo, m_lo), vs_lo);
uint8x16_t d_hi = veorq_u8(vandq_u8(vp_hi, m_hi), vs_hi);
return vmaxvq_u8(vorrq_u8(d_lo, d_hi)) == 0;
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
__m128i vp_lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
__m128i vp_hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16));
__m128i vs_lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored));
__m128i vs_hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored + 16));
__m128i lenv = _mm_set1_epi8(static_cast<char>(len));
__m128i m_lo = _mm_cmplt_epi8(_mm_load_si128(reinterpret_cast<const __m128i*>(idx16)), lenv);
__m128i m_hi = _mm_cmplt_epi8(_mm_load_si128(reinterpret_cast<const __m128i*>(idx32_hi)), lenv);
__m128i eq_lo = _mm_cmpeq_epi8(_mm_and_si128(vp_lo, m_lo), vs_lo);
__m128i eq_hi = _mm_cmpeq_epi8(_mm_and_si128(vp_hi, m_hi), vs_hi);
return (_mm_movemask_epi8(eq_lo) & _mm_movemask_epi8(eq_hi)) == 0xFFFF;
#else
for (std::size_t i = 0; i < len; ++i)
if (p[i] != stored[i]) return false;
return true;
#endif
} else {
// MaxKeyLen > 32: byte loop.
for (std::size_t i = 0; i < len; ++i)
if (p[i] != stored[i]) return false;
return true;
}
}
template <std::size_t N>
constexpr std::size_t compute_max_key_len(const std::array<std::string_view, N>& keys) noexcept {
std::size_t m = 0;
for (std::size_t i = 0; i < N; ++i) if (keys[i].size() > m) m = keys[i].size();
return m;
}
} // namespace key_selector_detail
/**
* Stateless, compile-time key selector.
*
* Usage:
* using sel_t = decltype(make_key_selector<"id", "text", "user">());
* std::size_t i = sel_t::match_raw(raw_key); // returns sel_t::size() on miss
*
* All PHF tables are static constexpr the compiler sees them as compile-time
* constants at every call site and fully unrolls compute_hash / compare.
*/
template <constevalutil::fixed_string... Keys>
struct key_selector {
static constexpr std::size_t N = sizeof...(Keys);
static_assert(N > 0, "key_selector requires at least one key");
static_assert(N <= 100,"key_selector supports at most 100 keys");
static constexpr std::array<std::string_view, N> keys{ Keys.view()... };
static constexpr std::size_t table_size = key_selector_detail::pick_table_size<N>();
static constexpr std::size_t max_key_len = key_selector_detail::compute_max_key_len<N>(keys);
static_assert(max_key_len <= SIMDJSON_PADDING,
"key longer than SIMDJSON_PADDING is not supported");
static constexpr auto phf =
key_selector_detail::compute_phf<N, table_size, max_key_len>(keys);
static constexpr std::size_t size() noexcept { return N; }
static constexpr std::uint8_t tbl_masks[17][16] = {
{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,8,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,8,9,0x80,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,8,9,10,0x80,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,8,9,10,11,0x80,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,8,9,10,11,12,0x80,0x80,0x80},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,0x80,0x80},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,0x80},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
};
/**
* Look up a JSON key. rjs must point just after an opening quote in a padded
* simdjson buffer. Returns the selector index in [0, N) on match, or N on miss.
*/
static simdjson_really_inline std::size_t match_raw(raw_json_string rjs) noexcept {
const char* p = rjs.raw();
//std::size_t len = key_selector_detail::scan_key_length<max_key_len>(p);
uint8x16_t v0 = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
uint8x16_t v1 = vld1q_u8(reinterpret_cast<const uint8_t*>(p)+16);
uint8x16_t cmp0 = vceqq_u8(v0, vdupq_n_u8('"'));
uint8x16_t cmp1 = vceqq_u8(v1, vdupq_n_u8('"'));
uint64_t m0 = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp0), 4)), 0);
uint64_t m1 = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp1), 4)), 0);
size_t len = m0 ? std::size_t(__builtin_ctzll(m0)) >> 2 : (m1 ? 16 + (std::size_t(__builtin_ctzll(m1)) >> 2) : max_key_len + 1);
if (len == 0 || len > max_key_len) return N;
// Compute hash. positions / num_positions / asso_values are compile-time
// constants, so this fully unrolls.
std::size_t h = len;
//printf("len=%zu\n", len);
for (std::uint8_t i = 0; i < phf.num_positions; ++i) {
std::uint8_t pos = phf.positions[i];
std::size_t idx = (pos == key_selector_detail::POS_LAST_CHAR)
? (len - std::size_t{1})
: static_cast<std::size_t>(pos);
std::size_t has = static_cast<std::size_t>(idx < len);
std::size_t mask = std::size_t{0} - has;
std::size_t safe_idx = idx & mask;
unsigned char b = static_cast<unsigned char>(p[safe_idx]);
h += static_cast<std::size_t>(phf.asso_values[i][b]) & mask;
}
std::size_t slot = h & (table_size - 1);
//printf("len=%zu phf.slot_key_len[slot]=%zu\n", len, phf.slot_key_len[slot]);
//if(phf.slot_key_len[slot] != len) return N;
size_t len1 = len <= 16 ? len : 16;
uint8x16_t input1 = vqtbl1q_u8(v0, vld1q_u8(tbl_masks[len1]));
//std::size_t tail_len = len > 16 ? len - 16 : 0;
//uint8x16_t input2 = vqtbl1q_u8(v1, vld1q_u8(tbl_masks[tail_len]));
std::uint8_t ki = phf.slot_to_key[slot];
if (ki >= N) return N;
uint8x16_t k0 = vld1q_u8(reinterpret_cast<const uint8_t*>(phf.slot_key_bytes[slot].data()));
//uint8x16_t k1 = vld1q_u8(reinterpret_cast<const uint8_t*>(phf.slot_key_bytes[slot].data())+16);
uint8x16_t cmpk0 = veorq_u8(input1, k0);
//uint8x16_t cmpk1 = veorq_u8(input2, k1);
//uint8x16_t cmpk = vorrq_u8(cmpk0, cmpk1);
uint8x16_t cmpk = cmpk0;
if((vmaxvq_u32(cmpk) != 0) | ( (phf.slot_key_len[slot] != len))) return N;
return ki;
}
/** Return the key text at selector index i (i in [0, N)). */
static constexpr std::string_view key_at(std::size_t i) noexcept {
return keys[i];
}
};
/**
* Factory for readability, matching make_perfect_set in ConstexprCore.
*/
template <constevalutil::fixed_string... Keys>
consteval auto make_key_selector() noexcept {
return key_selector<Keys...>{};
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
@@ -0,0 +1,131 @@
#ifndef SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_ITERATOR_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_ITERATOR_H
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/ondemand/key_selector.h"
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/value.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <array>
#include <cstddef>
#include <utility>
#if SIMDJSON_SUPPORTS_CONCEPTS
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
/**
* Forward iterator over selector matches in an object.
*
* Walks the JSON object once, yielding each (selector_index, value) pair whose
* key matches one of the selector's keys, in JSON order. Duplicate matches of
* the same key are silently skipped; iteration ends when every selector key
* has matched OR the object ends.
*
* for (auto [i, val] : obj.select<sel_t>()) {
* switch (i) { case 0: ...; case 1: ...; }
* }
*/
template <typename Selector>
class selector_iterator {
public:
// Yield ondemand::value directly (not simdjson_result<value>); the caller
// uses iterator state (error()) to check for errors after iteration.
using value_type = std::pair<std::size_t, value>;
struct end_sentinel {};
simdjson_inline selector_iterator() noexcept = default;
simdjson_inline explicit selector_iterator(object obj) noexcept
: obj_{std::move(obj)} {
auto begin_res = obj_.begin();
if (begin_res.error()) { done_ = true; last_error_ = begin_res.error(); return; }
it_ = begin_res.value();
advance();
}
simdjson_inline value_type operator*() noexcept {
return { current_index_, std::move(current_value_) };
}
simdjson_inline selector_iterator& operator++() noexcept { advance(); return *this; }
simdjson_inline bool operator==(end_sentinel) const noexcept { return done_; }
simdjson_inline bool operator!=(end_sentinel) const noexcept { return !done_; }
/** Error code set if iteration was terminated by an error. */
simdjson_inline error_code error() const noexcept { return last_error_; }
/** Number of unique selector-key matches produced so far. */
simdjson_inline std::size_t matched_count() const noexcept { return matched_; }
private:
object obj_{};
object_iterator it_{};
std::array<bool, Selector::size()> seen_{};
std::size_t matched_{0};
std::size_t current_index_{Selector::size()};
value current_value_{};
error_code last_error_{SUCCESS};
bool done_{false};
bool primed_{false};
simdjson_inline void advance() noexcept {
if (done_) return;
if (primed_) { ++it_; primed_ = false; }
if (matched_ >= Selector::size()) { done_ = true; return; }
object_iterator end{};
while (it_ != end) {
auto f_res = *it_;
if (f_res.error()) { last_error_ = f_res.error(); done_ = true; return; }
field f = f_res.value_unsafe();
std::size_t idx = Selector::match_raw(f.key());
if (idx < Selector::size() && !seen_[idx]) {
seen_[idx] = true;
++matched_;
current_index_ = idx;
current_value_ = std::move(f.value());
primed_ = true;
return;
}
++it_;
}
done_ = true;
}
};
/**
* Range adapter returned by object::select<Selector>(). Satisfies the range-for
* loop requirements (begin() / end()).
*/
template <typename Selector>
class selector_range {
public:
simdjson_inline explicit selector_range(object obj) noexcept
: obj_{std::move(obj)} {}
simdjson_inline selector_iterator<Selector> begin() noexcept {
return selector_iterator<Selector>{std::move(obj_)};
}
simdjson_inline typename selector_iterator<Selector>::end_sentinel end() const noexcept {
return {};
}
private:
object obj_;
};
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_ITERATOR_H
@@ -63,6 +63,17 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
return value(iter.child());
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector>
simdjson_inline selector_range<Selector> object::select() & noexcept {
return selector_range<Selector>{*this};
}
template <typename Selector>
simdjson_inline selector_range<Selector> object::select() && noexcept {
return selector_range<Selector>{std::move(*this)};
}
#endif
simdjson_inline simdjson_result<object> object::start(value_iterator &iter) noexcept {
SIMDJSON_TRY( iter.start_object().error() );
return object(iter);
@@ -334,6 +345,22 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(key);
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector>
simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::select() & noexcept {
// On error, construct a range over a default (invalid) object; iteration will
// yield the stored error at first dereference via the underlying iterator path.
return first.template select<Selector>();
}
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector>
simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::select() && noexcept {
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).template select<Selector>();
}
#endif
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
return first.at_pointer(json_pointer);
@@ -5,6 +5,7 @@
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/implementation_simdjson_result_base.h"
#include "simdjson/generic/ondemand/value_iterator.h"
#include "simdjson/generic/ondemand/key_selector.h"
#include <vector>
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
@@ -122,6 +123,27 @@ public:
/** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) & noexcept; */
simdjson_inline simdjson_result<value> operator[](std::string_view key) && noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
/**
* Iterate over this object, yielding every field whose key is in the compile-time
* key_selector Selector. Yields std::pair<std::size_t, simdjson_result<value>>
* (selector_index, value) in JSON order. Duplicate keys in the JSON are skipped
* (first occurrence wins). Iteration ends when all Selector::size() keys have
* matched or the object ends.
*
* Usage:
* using sel_t = decltype(make_key_selector<"id", "text", "user">());
* for (auto [i, v] : obj.select<sel_t>()) { ... }
*
* @tparam Selector A stateless key_selector type (see key_selector.h).
*/
template <typename Selector>
simdjson_inline selector_range<Selector> select() & noexcept;
/** @overload */
template <typename Selector>
simdjson_inline selector_range<Selector> select() && noexcept;
#endif
/**
* Get the value associated with the given JSON pointer. We use the RFC 6901
* https://tools.ietf.org/html/rfc6901 standard, interpreting the current node
@@ -324,6 +346,12 @@ public:
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field(std::string_view key) && noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) & noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) && noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector> select() & noexcept;
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector> select() && noexcept;
#endif
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) & noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) && noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
+136 -3
View File
@@ -10,13 +10,18 @@
#include <climits>
#include <cwchar>
#ifndef _WIN32
#if SIMDJSON_HAS_UNISTD_H
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
// On Windows, `padded_memory_map` (when it is enabled) depends on types and
// functions declared in <windows.h>. We deliberately do NOT include that
// header here: users of simdjson who want `padded_memory_map` on Windows
// must include <windows.h> themselves *before* including this header. See
// padded_string.h for the detection logic.
namespace simdjson {
namespace internal {
@@ -385,7 +390,9 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
}
#ifndef _WIN32
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#if SIMDJSON_HAS_UNISTD_H
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
int fd = open(filename, O_RDONLY);
@@ -421,7 +428,132 @@ simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
munmap(const_cast<char *>(data), size + simdjson::SIMDJSON_PADDING);
}
}
#elif defined(_WIN32)
// Windows zero-copy implementation using placeholder virtual memory.
//
// We use the modern Windows memory APIs (VirtualAlloc2, CreateFileMapping2,
// MapViewOfFile3 — available since Windows 10 1803) to map the file into a
// contiguous virtual address range that includes at least SIMDJSON_PADDING
// zero bytes after the file content, with no data copies.
//
// Strategy:
// 1. If rounding the file size up to the allocation granularity already
// exceeds file_size + SIMDJSON_PADDING, the OS page zero-fill provides
// the padding and we use a simple MapViewOfFile3 call.
// 2. Otherwise we reserve a contiguous placeholder region via VirtualAlloc2,
// split it at the granularity-aligned file boundary, map the file into
// the first part, and commit zero pages for the second part (padding).
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
HANDLE file_handle = ::CreateFileA(
filename, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return;
}
LARGE_INTEGER file_size_li;
if (!::GetFileSizeEx(file_handle, &file_size_li) || file_size_li.QuadPart < 0) {
::CloseHandle(file_handle);
return;
}
#if SIMDJSON_IS_32BITS
if (static_cast<unsigned long long>(file_size_li.QuadPart) >
static_cast<unsigned long long>(SIZE_MAX - simdjson::SIMDJSON_PADDING)) {
::CloseHandle(file_handle);
return;
}
#endif
size = static_cast<size_t>(file_size_li.QuadPart);
if (size == 0) {
::CloseHandle(file_handle);
return;
}
HANDLE section = ::CreateFileMapping2(
file_handle, NULL, FILE_MAP_READ, PAGE_READONLY,
0, 0, NULL, NULL, 0);
::CloseHandle(file_handle);
if (section == NULL) {
return;
}
SYSTEM_INFO si;
::GetSystemInfo(&si);
const size_t granularity = static_cast<size_t>(si.dwAllocationGranularity);
const size_t file_region = (size + granularity - 1) & ~(granularity - 1);
const size_t total_needed = size + simdjson::SIMDJSON_PADDING;
if (file_region >= total_needed) {
// The zero-fill in the last page already covers the padding.
PVOID view = ::MapViewOfFile3(
section, ::GetCurrentProcess(), NULL, 0, 0,
0, PAGE_READONLY, NULL, 0);
::CloseHandle(section);
if (view != NULL) {
data = static_cast<const char *>(view);
}
return;
}
// We need extra zero pages beyond the file region. Use the placeholder API
// to get a contiguous virtual address range spanning both the file mapping
// and the zero-filled padding.
const size_t padding_region =
((total_needed - file_region) + granularity - 1) & ~(granularity - 1);
const size_t reserve_size = file_region + padding_region;
// Reserve a contiguous placeholder.
PVOID placeholder = ::VirtualAlloc2(
::GetCurrentProcess(), NULL, reserve_size,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, NULL, 0);
if (placeholder == NULL) {
::CloseHandle(section);
return;
}
// Split into two placeholders at the file_region boundary.
if (!::VirtualFree(placeholder, file_region,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
::VirtualFree(placeholder, 0, MEM_RELEASE);
::CloseHandle(section);
return;
}
// Map the file into the first placeholder.
PVOID file_view = ::MapViewOfFile3(
section, ::GetCurrentProcess(), placeholder, 0, file_region,
MEM_REPLACE_PLACEHOLDER, PAGE_READONLY, NULL, 0);
::CloseHandle(section);
if (file_view == NULL) {
::VirtualFree(placeholder, 0, MEM_RELEASE);
::VirtualFree(static_cast<char *>(placeholder) + file_region,
0, MEM_RELEASE);
return;
}
// Commit zero pages in the second placeholder (the padding).
void *pad = static_cast<char *>(placeholder) + file_region;
PVOID padding_ptr = ::VirtualAlloc2(
::GetCurrentProcess(), pad, padding_region,
MEM_REPLACE_PLACEHOLDER | MEM_COMMIT, PAGE_READONLY, NULL, 0);
if (padding_ptr == NULL) {
::UnmapViewOfFile(file_view);
::VirtualFree(pad, 0, MEM_RELEASE);
return;
}
data = static_cast<const char *>(file_view);
padding_view_ = padding_ptr;
}
simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
if (data == nullptr) { return; }
::UnmapViewOfFile(data);
if (padding_view_ != nullptr) {
::VirtualFree(padding_view_, 0, MEM_RELEASE);
}
}
#endif // POSIX or _WIN32
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
if(!is_valid()) {
@@ -433,7 +565,8 @@ simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noe
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
return data != nullptr;
}
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
} // namespace simdjson
+30 -7
View File
@@ -277,11 +277,26 @@ inline std::ostream& operator<<(std::ostream& out, const padded_string& s) { ret
inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string> &s) noexcept(false) { return out << s.value(); }
#endif
#ifndef _WIN32
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
/**
* A class representing a memory-mapped file with padding.
* It is only available on non-Windows platforms, as Windows has different APIs for memory mapping.
*
* On POSIX systems (Linux, macOS, BSD, ...), this uses `mmap` to map the file
* contents directly into memory, which is efficient for large files (no copy).
*
* On Windows, this class is disabled by default and must be opted into at
* build time by defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`. When
* enabled, `<windows.h>` must also be included before `<simdjson.h>` and
* the compilation must target Windows 10, version 1803 or later. The
* Windows implementation uses the modern memory APIs (`VirtualAlloc2`,
* `CreateFileMapping2`, `MapViewOfFile3`) with the placeholder virtual
* memory mechanism to always achieve true zero-copy mapping with
* contiguous zero-filled padding.
*
* Either way, the resulting `padded_string_view` carries at least
* `SIMDJSON_PADDING` bytes of accessible zero-filled padding after the file
* content, so it can be consumed directly by the simdjson parsers (including
* `parse_many` / `iterate_many`).
*/
class padded_memory_map {
public:
@@ -289,9 +304,11 @@ public:
* Create a new padded memory map for the given file.
* After creating the memory map, you can call view() to get a padded_string_view of the file content.
* The memory map will be automatically released when the padded_memory_map instance is destroyed.
* Note that the file content is not copied, so this is efficient for large files. However,
* the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found,
* permission denied, etc.), the memory map will be invalid and view() will return an empty view.
* On POSIX systems, the file content is not copied, so this is efficient for large files.
* On Windows, the file is mapped into memory via `MapViewOfFile3` (zero-copy).
* In all cases, the file must remain unchanged while the memory map is in use.
* In case of error (e.g., file not found, permission denied, etc.), the memory map will be
* invalid and view() will return an empty view.
* You can check if the memory map is valid by calling is_valid() before using view().
*
* @param filename the path to the file to memory-map.
@@ -328,8 +345,14 @@ private:
padded_memory_map &operator=(const padded_memory_map &) = delete;
const char *data{nullptr};
size_t size{0};
#ifdef _WIN32
// When the file ends near an allocation-granularity boundary, we use the
// placeholder API to append zero-filled padding pages. This pointer tracks
// that region so the destructor can release it with VirtualFree.
void *padding_view_{nullptr};
#endif
};
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
+2 -2
View File
@@ -7,7 +7,7 @@
#include <cstring> /* memcmp */
// for page size computation.
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#if SIMDJSON_HAS_UNISTD_H
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/sysctl.h>
@@ -113,7 +113,7 @@ inline uint32_t get_page_size() noexcept {
return static_cast<std::uint32_t>(si.dwPageSize);
}();
return cached;
#elif defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#elif SIMDJSON_HAS_UNISTD_H
static const uint32_t cached = []() -> uint32_t {
long page_size = sysconf(_SC_PAGESIZE);
if (page_size > 0) {
+48
View File
@@ -285,5 +285,53 @@ using std::size_t;
#endif
#endif
#ifndef SIMDJSON_HAS_UNISTD_H
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#define SIMDJSON_HAS_UNISTD_H 1
#else
#define SIMDJSON_HAS_UNISTD_H 0
#endif
#endif
// padded_memory_map availability.
//
// On POSIX platforms the class is always available: the implementation uses
// `mmap` (and a trailing anonymous page for padding) from <sys/mman.h>.
//
// On Windows the class is disabled by default and must be explicitly
// opted into by defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`. Enabling
// it requires:
// 1. `<windows.h>` has been included *before* `<simdjson.h>` (so that
// this header can see the Win32 types and the `_WINDOWS_` include
// guard),
// 2. the compilation targets Windows 10, version 1803 or later
// (i.e. `NTDDI_VERSION >= NTDDI_WIN10_RS4`, `0x0A000005`). This is
// required because the implementation relies on the modern memory
// APIs introduced with that version (`CreateFileMapping2` /
// `MapViewOfFile3`),
// 3. the link step pulls in an import library that exports those APIs,
// typically `onecore.lib` (or `mincore.lib`).
//
// The `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS` CMake option arranges (1)-(3)
// automatically when building simdjson with its own CMake. Consumers using
// simdjson as a pre-built library are responsible for setting the macro,
// the Windows version macros, and the link library themselves.
//
// If the opt-in conditions are not met on Windows, `padded_memory_map`
// simply does not exist — any attempt to use it fails at compile time
// with an "unknown identifier" diagnostic rather than silently degrading.
//
// The SIMDJSON_HAS_PADDED_MEMORY_MAP macro reflects whether the class is
// available in the current translation unit. Users may test this macro to
// conditionally compile code that depends on padded_memory_map.
#ifndef SIMDJSON_HAS_PADDED_MEMORY_MAP
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#elif defined(_WINDOWS_) && defined(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS) && SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#else
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 0
#endif
#endif
#endif // SIMDJSON_PORTABILITY_H
+203 -7
View File
@@ -4356,10 +4356,55 @@ inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string
#endif
#ifndef _WIN32
// padded_memory_map availability.
//
// On POSIX platforms the class is always available: the implementation uses
// `mmap` (and a trailing anonymous page for padding) from <sys/mman.h>.
//
// On Windows the class is only available when all of the following hold:
// 1. <windows.h> has been included *before* <simdjson.h> (so that this
// header can see the Win32 types and the `_WINDOWS_` include guard),
// 2. the compilation targets Windows 11 or later (NTDDI_VERSION
// >= NTDDI_WIN10_CO, 0x0A00000B). This is required because the
// implementation relies on the modern memory APIs introduced with
// that version (CreateFileMapping2 / MapViewOfFile3).
//
// If those conditions are not met on Windows, `padded_memory_map` simply
// does not exist — any attempt to use it fails at compile time with an
// "unknown identifier" diagnostic rather than silently degrading.
//
// The SIMDJSON_HAS_PADDED_MEMORY_MAP macro reflects whether the class is
// available in the current translation unit. Users may test this macro to
// conditionally compile code that depends on padded_memory_map.
#ifndef SIMDJSON_HAS_PADDED_MEMORY_MAP
#if !defined(_WIN32)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#elif defined(_WINDOWS_) && defined(NTDDI_VERSION) && (NTDDI_VERSION >= 0x0A00000B /* NTDDI_WIN10_CO — Windows 11 */)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#else
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 0
#endif
#endif
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
/**
* A class representing a memory-mapped file with padding.
* It is only available on non-Windows platforms, as Windows has different APIs for memory mapping.
*
* On POSIX systems (Linux, macOS, BSD, ...), this uses `mmap` to map the file
* contents directly into memory, which is efficient for large files (no copy).
*
* On Windows, this class is only available when `<windows.h>` is included
* before `<simdjson.h>` and the compilation targets Windows 11 or later
* (NTDDI_VERSION >= NTDDI_WIN10_CO). The Windows implementation uses the
* modern memory APIs (`CreateFileMapping2` / `MapViewOfFile3`) to map the
* file with true zero-copy semantics whenever the last page of the file
* provides enough trailing zero-fill for SIMDJSON_PADDING bytes; otherwise
* it falls back to a heap-allocated padded buffer populated with `ReadFile`.
*
* Either way, the resulting `padded_string_view` carries at least
* `SIMDJSON_PADDING` bytes of accessible zero-filled padding after the file
* content, so it can be consumed directly by the simdjson parsers (including
* `parse_many` / `iterate_many`).
*/
class padded_memory_map {
public:
@@ -4367,9 +4412,12 @@ public:
* Create a new padded memory map for the given file.
* After creating the memory map, you can call view() to get a padded_string_view of the file content.
* The memory map will be automatically released when the padded_memory_map instance is destroyed.
* Note that the file content is not copied, so this is efficient for large files. However,
* the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found,
* permission denied, etc.), the memory map will be invalid and view() will return an empty view.
* On POSIX systems, the file content is not copied, so this is efficient for large files.
* On Windows, the file is mapped into memory via `MapViewOfFile3` whenever possible
* (zero-copy) and otherwise read into a heap-allocated padded buffer.
* In all cases, the file must remain unchanged while the memory map is in use.
* In case of error (e.g., file not found, permission denied, etc.), the memory map will be
* invalid and view() will return an empty view.
* You can check if the memory map is valid by calling is_valid() before using view().
*
* @param filename the path to the file to memory-map.
@@ -4406,8 +4454,14 @@ private:
padded_memory_map &operator=(const padded_memory_map &) = delete;
const char *data{nullptr};
size_t size{0};
#ifdef _WIN32
// On Windows the underlying storage may either be a memory-mapped view
// (released with UnmapViewOfFile) or a heap-allocated padded buffer
// (released with delete[]). This flag distinguishes the two cases.
bool owns_heap_buffer_{false};
#endif
};
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
@@ -4700,6 +4754,11 @@ inline padded_string_view pad_with_reserve(std::string& s) noexcept {
#include <sys/stat.h>
#include <unistd.h>
#endif
// On Windows, `padded_memory_map` (when it is enabled) depends on types and
// functions declared in <windows.h>. We deliberately do NOT include that
// header here: users of simdjson who want `padded_memory_map` on Windows
// must include <windows.h> themselves *before* including this header. See
// padded_string.h for the detection logic.
namespace simdjson {
namespace internal {
@@ -5068,6 +5127,8 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
}
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#ifndef _WIN32
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
@@ -5104,7 +5165,141 @@ simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
munmap(const_cast<char *>(data), size + simdjson::SIMDJSON_PADDING);
}
}
#else // _WIN32
// Windows 11+ implementation.
//
// We use the modern Windows memory APIs (CreateFileMapping2 + MapViewOfFile3,
// available since Windows 10 1803 and gated on Windows 11 in our build) to
// map the file directly into the process address space with zero copies.
//
// Windows guarantees that after a file view is mapped, any bytes in the
// trailing partial page beyond the end of the file are zero-filled. As long
// as the file does not end exactly on (or within SIMDJSON_PADDING bytes of)
// a page boundary, we therefore get SIMDJSON_PADDING accessible zero bytes
// for free at the tail of the view. In the rare edge cases where the tail
// is not large enough (about 1.5% of file sizes if sizes were uniformly
// distributed), we fall back to reading the file into a heap-allocated
// padded buffer. That fallback is still correct — it just performs one
// memory copy instead of a zero-copy mapping.
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
HANDLE file_handle = ::CreateFileA(
filename, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return; // file not found or cannot be opened
}
LARGE_INTEGER file_size_li;
if (!::GetFileSizeEx(file_handle, &file_size_li) || file_size_li.QuadPart < 0) {
::CloseHandle(file_handle);
return; // failed to get file size
}
#if SIMDJSON_IS_32BITS
if (static_cast<unsigned long long>(file_size_li.QuadPart) >
static_cast<unsigned long long>(SIZE_MAX - simdjson::SIMDJSON_PADDING)) {
::CloseHandle(file_handle);
return; // file too large to map on a 32-bit system
}
#endif
size = static_cast<size_t>(file_size_li.QuadPart);
// Fast zero-copy path: only usable when the last partial page of the file
// gives us at least SIMDJSON_PADDING bytes of zero-filled slack.
if (size > 0) {
SYSTEM_INFO sys_info;
::GetSystemInfo(&sys_info);
const size_t page_size = static_cast<size_t>(sys_info.dwPageSize);
const size_t tail_in_page = size % page_size;
const size_t tail_zero_fill = (tail_in_page == 0)
? size_t{0}
: (page_size - tail_in_page);
if (tail_zero_fill >= simdjson::SIMDJSON_PADDING) {
// Create the section with the new CreateFileMapping2 API.
HANDLE mapping = ::CreateFileMapping2(
file_handle, /*SecurityAttributes=*/ NULL,
/*DesiredAccess=*/ FILE_MAP_READ,
/*PageProtection=*/ PAGE_READONLY,
/*AllocationAttributes=*/ 0,
/*MaximumSize=*/ 0, // 0 => entire file
/*Name=*/ NULL,
/*ExtendedParameters=*/ NULL, /*ParameterCount=*/ 0);
if (mapping != NULL) {
// Map the view with the new MapViewOfFile3 API.
PVOID view_ptr = ::MapViewOfFile3(
mapping, ::GetCurrentProcess(),
/*BaseAddress=*/ NULL,
/*Offset=*/ 0,
/*ViewSize=*/ size,
/*AllocationType=*/ 0,
/*PageProtection=*/ PAGE_READONLY,
/*ExtendedParameters=*/ NULL, /*ParameterCount=*/ 0);
::CloseHandle(mapping);
if (view_ptr != NULL) {
::CloseHandle(file_handle);
data = static_cast<const char *>(view_ptr);
owns_heap_buffer_ = false;
return;
}
}
// Fall through to the buffered-read fallback if the mapping failed.
}
}
// Fallback path: the file ends too close to a page boundary (or the
// mapping APIs refused) — read the file contents into a heap-allocated
// padded buffer. This preserves the class' padding invariant at the cost
// of one copy.
size_t total_size = size + simdjson::SIMDJSON_PADDING;
if (total_size < size) { // overflow guard
::CloseHandle(file_handle);
size = 0;
return;
}
char *buffer = new (std::nothrow) char[total_size];
if (buffer == nullptr) {
::CloseHandle(file_handle);
size = 0;
return;
}
size_t total_read = 0;
while (total_read < size) {
size_t remaining = size - total_read;
const size_t chunk_limit = static_cast<size_t>(0x40000000UL); // 1 GiB per call
DWORD to_read = remaining > chunk_limit
? static_cast<DWORD>(chunk_limit)
: static_cast<DWORD>(remaining);
DWORD bytes_read = 0;
if (!::ReadFile(file_handle, buffer + total_read, to_read, &bytes_read, NULL)) {
delete[] buffer;
::CloseHandle(file_handle);
size = 0;
return;
}
if (bytes_read == 0) {
// Unexpected EOF: the file shrank while we were reading it.
delete[] buffer;
::CloseHandle(file_handle);
size = 0;
return;
}
total_read += bytes_read;
}
std::memset(buffer + size, 0, simdjson::SIMDJSON_PADDING);
data = buffer;
owns_heap_buffer_ = true;
::CloseHandle(file_handle);
}
simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
if (data == nullptr) { return; }
if (owns_heap_buffer_) {
delete[] const_cast<char *>(data);
} else {
::UnmapViewOfFile(data);
}
}
#endif // _WIN32
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
if(!is_valid()) {
@@ -5116,7 +5311,8 @@ simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noe
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
return data != nullptr;
}
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
} // namespace simdjson
+102 -8
View File
@@ -1,13 +1,29 @@
// On Windows, padded_memory_map is an opt-in feature gated on the
// SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS macro. When that macro is set, the
// consumer must also include <windows.h> before <simdjson.h>. We include
// the Win32 header here so that -- in configurations that turned the
// feature on -- the test actually exercises the Windows path.
#ifdef _WIN32
#include <cstdlib>
// This test is not supported on Windows because it relies on POSIX APIs like
// mmap. Please run it on a POSIX-compliant system.
int main() { return EXIT_SUCCESS; }
#else
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include "simdjson.h"
#include "test_macros.h"
// When SIMDJSON_HAS_PADDED_MEMORY_MAP is 0 (e.g. Windows builds without
// SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS, or MinGW configurations that lack
// the required SDK gating), compile the test body out and make main()
// report success so the test suite still runs as a no-op. This is not a
// silent downgrade: users who want the Windows path must explicitly
// enable the CMake option `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS`.
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#if SIMDJSON_EXCEPTIONS
bool test_memory_map_exception() {
TEST_START();
@@ -37,11 +53,89 @@ bool test_memory_map_noexception() {
TEST_SUCCEED();
}
// Verifies that padded_memory_map can feed a streaming parser (iterate_many)
// with JSON documents read from a file. This exercises the API that parse_many
// / iterate_many users typically want: no extra copy on POSIX, portable fallback
// on Windows. The AMAZON_CELLPHONES_NDJSON resource is an NDJSON file so it is
// a realistic stress-test for streaming from a memory-mapped file.
bool test_memory_map_iterate_many() {
TEST_START();
simdjson::padded_memory_map map(AMAZON_CELLPHONES_NDJSON);
if (!map.is_valid()) {
std::cerr << "Failed to memory-map the file " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
simdjson::padded_string_view view = map.view();
simdjson::ondemand::parser parser;
simdjson::ondemand::document_stream stream;
ASSERT_SUCCESS( parser.iterate_many(view).get(stream) );
size_t count = 0;
for (auto doc : stream) {
ASSERT_SUCCESS( doc.error() );
count++;
}
if (count == 0) {
std::cerr << "Expected at least one document in " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
TEST_SUCCEED();
}
// Verifies that padded_memory_map also works with the DOM streaming parser
// (parse_many). Same rationale as the ondemand variant above.
bool test_memory_map_parse_many() {
TEST_START();
simdjson::padded_memory_map map(AMAZON_CELLPHONES_NDJSON);
if (!map.is_valid()) {
std::cerr << "Failed to memory-map the file " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
simdjson::padded_string_view view = map.view();
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
ASSERT_SUCCESS( parser.parse_many(view).get(stream) );
size_t count = 0;
for (auto doc : stream) {
ASSERT_SUCCESS( doc.error() );
count++;
}
if (count == 0) {
std::cerr << "Expected at least one document in " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
TEST_SUCCEED();
}
// Ensures that trying to memory-map a file that does not exist leaves the map
// in the "invalid" state rather than crashing. This is important on Windows
// where the underlying implementation path differs from POSIX.
bool test_memory_map_missing_file() {
TEST_START();
simdjson::padded_memory_map map("this_file_definitely_does_not_exist_123456789.json");
if (map.is_valid()) {
std::cerr << "Expected is_valid() == false for missing file" << std::endl;
return false;
}
TEST_SUCCEED();
}
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
int main() {
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
bool ok = true;
#if SIMDJSON_EXCEPTIONS
return (test_memory_map_exception() && test_memory_map_noexception()) ? EXIT_SUCCESS : EXIT_FAILURE;
ok = ok && test_memory_map_exception();
#endif
ok = ok && test_memory_map_noexception();
ok = ok && test_memory_map_iterate_many();
ok = ok && test_memory_map_parse_many();
ok = ok && test_memory_map_missing_file();
return ok ? EXIT_SUCCESS : EXIT_FAILURE;
#else
return test_memory_map_noexception() ? EXIT_SUCCESS : EXIT_FAILURE;
std::cout << "padded_memory_map is disabled in this configuration; "
"set SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON in CMake to "
"enable it on Windows. Test skipped." << std::endl;
return EXIT_SUCCESS;
#endif
}
#endif
+1
View File
@@ -25,6 +25,7 @@ add_cpp_test(ondemand_misc_tests LABELS ondemand acceptance
add_cpp_test(ondemand_number_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_number_in_string_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_object_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_object_find_field_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_object_error_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_ordering_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_parse_api_tests LABELS ondemand acceptance per_implementation)
@@ -173,6 +173,36 @@ namespace object_tests {
TEST_SUCCEED();
}
#if SIMDJSON_SUPPORTS_CONCEPTS
bool object_find_field_key_selector() {
TEST_START();
auto json = R"({ "name": "John", "age": 30, "city": "New York" })"_padded;
constexpr std::array<std::string_view, 3> keys = {"name", "age", "city"};
constexpr auto selector = ondemand::key_selector<3>(keys);
SUBTEST("ondemand::object with key_selector", test_ondemand_doc(json, [&](auto doc_result) {
ondemand::object object;
ASSERT_SUCCESS( doc_result.get(object) );
auto [index, value_result] = object.find_field(selector);
ASSERT_TRUE(index < 3);
ASSERT_SUCCESS(value_result);
std::string_view str_val;
ASSERT_SUCCESS(value_result.get(str_val));
ASSERT_EQUAL(str_val, "John");
// Test that we can find different keys
ASSERT_EQUAL(selector.index_of("name"), 0);
ASSERT_EQUAL(selector.index_of("age"), 1);
ASSERT_EQUAL(selector.index_of("city"), 2);
ASSERT_EQUAL(selector.index_of("invalid"), 3); // Not found
return true;
}));
TEST_SUCCEED();
}
#endif
bool run() {
return
object_find_field_unordered() &&
@@ -181,6 +211,9 @@ namespace object_tests {
object_find_field() &&
document_object_find_field() &&
value_object_find_field() &&
#if SIMDJSON_SUPPORTS_CONCEPTS
object_find_field_key_selector() &&
#endif
true;
}