mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 875d9279a3 | |||
| a9daa3c5eb | |||
| eb493c485d | |||
| 5900ac42d9 | |||
| 5bb9c8eed5 | |||
| 122bea262a | |||
| e48f7bf98b | |||
| 4ec44e88c9 | |||
| ede3129081 | |||
| 8ea4c1c2e6 | |||
| 486b2a3828 |
@@ -13,18 +13,20 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release}
|
- {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}
|
- {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}
|
- {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}
|
- {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}
|
- {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}
|
- {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:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: |
|
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
|
- name: Build Debug
|
||||||
run: cmake --build build --config ${{matrix.build_type}} --verbose
|
run: cmake --build build --config ${{matrix.build_type}} --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|||||||
@@ -75,6 +75,41 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
|
|||||||
)
|
)
|
||||||
endif()
|
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)
|
if(is_top_project)
|
||||||
option(SIMDJSON_INSTALL "Enable target install" ON)
|
option(SIMDJSON_INSTALL "Enable target install" ON)
|
||||||
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
|
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
add_subdirectory(dom)
|
add_subdirectory(dom)
|
||||||
|
|
||||||
|
|
||||||
include_directories( . linux )
|
include_directories( . )
|
||||||
link_libraries(simdjson-windows-headers test-data)
|
link_libraries(simdjson-windows-headers test-data)
|
||||||
link_libraries(simdjson)
|
link_libraries(simdjson)
|
||||||
|
link_libraries(counters)
|
||||||
if(SIMDJSON_STATIC_REFLECTION)
|
if(SIMDJSON_STATIC_REFLECTION)
|
||||||
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
|
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
|
||||||
endif(SIMDJSON_STATIC_REFLECTION)
|
endif(SIMDJSON_STATIC_REFLECTION)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
|||||||
#include "json2msgpack/boostjson.h"
|
#include "json2msgpack/boostjson.h"
|
||||||
|
|
||||||
#include "partial_tweets/simdjson_ondemand.h"
|
#include "partial_tweets/simdjson_ondemand.h"
|
||||||
|
#include "partial_tweets/simdjson_ondemand_key_selector.h"
|
||||||
#include "partial_tweets/simdjson_dom.h"
|
#include "partial_tweets/simdjson_dom.h"
|
||||||
#include "partial_tweets/yyjson.h"
|
#include "partial_tweets/yyjson.h"
|
||||||
#if SIMDJSON_COMPETITION_ONDEMAND_SAJSON
|
#if SIMDJSON_COMPETITION_ONDEMAND_SAJSON
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -25,7 +26,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "linux-perf-events.h"
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -204,12 +204,8 @@ struct feature_benchmarker {
|
|||||||
}
|
}
|
||||||
// Rate of 1-7-structural misses per 8-structural flip
|
// Rate of 1-7-structural misses per 8-structural flip
|
||||||
double struct1_7_miss_rate(BenchmarkStage stage) const {
|
double struct1_7_miss_rate(BenchmarkStage stage) const {
|
||||||
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
if (!has_events()) { return 1; }
|
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);
|
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
|
// Extra cost of an 8-15 structural block over a 1-7 structural block
|
||||||
double struct8_15_cost(BenchmarkStage stage) const {
|
double struct8_15_cost(BenchmarkStage stage) const {
|
||||||
@@ -221,12 +217,8 @@ struct feature_benchmarker {
|
|||||||
}
|
}
|
||||||
// Rate of 8-15-structural misses per 8-structural flip
|
// Rate of 8-15-structural misses per 8-structural flip
|
||||||
double struct8_15_miss_rate(BenchmarkStage stage) const {
|
double struct8_15_miss_rate(BenchmarkStage stage) const {
|
||||||
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
if (!has_events()) { return 1; }
|
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);
|
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!)
|
// 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
|
// Rate of 16-structural misses per 16-structural flip
|
||||||
double struct16_miss_rate(BenchmarkStage stage) const {
|
double struct16_miss_rate(BenchmarkStage stage) const {
|
||||||
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
if (!has_events()) { return 1; }
|
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);
|
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
|
// Rate of UTF-8 misses per UTF-8 flip
|
||||||
double utf8_miss_rate(BenchmarkStage stage) const {
|
double utf8_miss_rate(BenchmarkStage stage) const {
|
||||||
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
if (!has_events()) { return 1; }
|
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);
|
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
|
// Extra cost of having escapes in a block
|
||||||
double escape_cost(BenchmarkStage stage) const {
|
double escape_cost(BenchmarkStage stage) const {
|
||||||
@@ -275,12 +259,8 @@ struct feature_benchmarker {
|
|||||||
}
|
}
|
||||||
// Rate of escape misses per escape flip
|
// Rate of escape misses per escape flip
|
||||||
double escape_miss_rate(BenchmarkStage stage) const {
|
double escape_miss_rate(BenchmarkStage stage) const {
|
||||||
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
if (!has_events()) { return 1; }
|
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);
|
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) {
|
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 actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
|
||||||
double calc = features.calc_expected(stage, results);
|
double calc = features.calc_expected(stage, results);
|
||||||
@@ -417,7 +381,6 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
|
|||||||
}
|
}
|
||||||
printf("|\n");
|
printf("|\n");
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
// Read options
|
// Read options
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#ifndef _BENCHMARK_H_
|
#ifndef _BENCHMARK_H_
|
||||||
#define _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
|
* Prints the best number of operations per cycle where
|
||||||
|
|||||||
+4
-11
@@ -1,7 +1,8 @@
|
|||||||
#ifndef __BENCHMARKER_H
|
#ifndef __BENCHMARKER_H
|
||||||
#define __BENCHMARKER_H
|
#define __BENCHMARKER_H
|
||||||
|
|
||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
#include "simdjson.h"
|
#include "simdjson.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@@ -28,11 +29,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "linux-perf-events.h"
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#endif
|
#endif
|
||||||
#include "simdjson.h"
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
@@ -423,18 +422,12 @@ struct benchmarker {
|
|||||||
stage.instructions() / static_cast<double>(stats->structurals),
|
stage.instructions() / static_cast<double>(stats->structurals),
|
||||||
stage.instructions() / static_cast<double>(stage.cycles())
|
stage.instructions() / static_cast<double>(stage.cycles())
|
||||||
);
|
);
|
||||||
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
printf("%s%-13s: %7.0f branch misses (%6.2f%%)\n",
|
||||||
// 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",
|
|
||||||
prefix,
|
prefix,
|
||||||
"Misses",
|
"Misses",
|
||||||
stage.branch_misses(),
|
stage.branch_misses(),
|
||||||
percent(stage.branch_misses(), all_stages_without_allocation.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()
|
|
||||||
);
|
);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
include_directories( .. ../linux )
|
include_directories( .. )
|
||||||
link_libraries(simdjson-windows-headers test-data)
|
link_libraries(simdjson-windows-headers test-data)
|
||||||
link_libraries(simdjson)
|
link_libraries(simdjson)
|
||||||
|
link_libraries(counters)
|
||||||
|
|
||||||
add_executable(perfdiff perfdiff.cpp)
|
add_executable(perfdiff perfdiff.cpp)
|
||||||
add_executable(parse parse.cpp)
|
add_executable(parse parse.cpp)
|
||||||
add_executable(parse_stream parse_stream.cpp)
|
add_executable(parse_stream parse_stream.cpp)
|
||||||
add_executable(statisticalmodel statisticalmodel.cpp)
|
|
||||||
|
|
||||||
add_executable(parse_noutf8validation parse.cpp)
|
add_executable(parse_noutf8validation parse.cpp)
|
||||||
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
|
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -24,7 +25,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "linux-perf-events.h"
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#ifndef BENCHMARK_HELPERS_H
|
#ifndef BENCHMARK_HELPERS_H
|
||||||
#define BENCHMARK_HELPERS_H
|
#define BENCHMARK_HELPERS_H
|
||||||
|
|
||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
event_collector collector;
|
event_collector collector;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "simdjson.h"
|
#include "simdjson.h"
|
||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace json_benchmark {
|
namespace json_benchmark {
|
||||||
@@ -58,11 +59,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
|
|||||||
if (collector.has_events()) {
|
if (collector.has_events()) {
|
||||||
state.counters["instructions"] = events.instructions();
|
state.counters["instructions"] = events.instructions();
|
||||||
state.counters["cycles"] = events.cycles();
|
state.counters["cycles"] = events.cycles();
|
||||||
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
state.counters["branch_miss"] = events.branch_misses();
|
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_byte"] = events.instructions() / double(bench.bytes_per_iteration());
|
||||||
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
|
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
|
||||||
state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration());
|
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_instructions"] = events.best.instructions();
|
||||||
state.counters["best_cycles"] = events.best.cycles();
|
state.counters["best_cycles"] = events.best.cycles();
|
||||||
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
|
|
||||||
state.counters["best_branch_miss"] = events.best.branch_misses();
|
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_byte"] = events.best.instructions() / double(bench.bytes_per_iteration());
|
||||||
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
|
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()) {
|
if (collector.has_events()) {
|
||||||
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
|
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
|
||||||
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << 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 << " 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);
|
label << " items=" << setw(10) << bench.items_per_iteration() << setw(0);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "json_benchmark/string_runner.h"
|
#include "json_benchmark/string_runner.h"
|
||||||
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
#ifndef BENCHMARK_HELPER_HPP
|
||||||
#define BENCHMARK_HELPER_HPP
|
#define BENCHMARK_HELPER_HPP
|
||||||
#include "event_counter.h"
|
#include <counters/event_counter.h>
|
||||||
|
using namespace counters;
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
inline event_collector &get_collector() {
|
inline event_collector &get_collector() {
|
||||||
|
|||||||
Vendored
+8
@@ -20,6 +20,14 @@ if(SIMDJSON_GOOGLE_BENCHMARKS)
|
|||||||
)
|
)
|
||||||
endif()
|
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(
|
CPMAddPackage(
|
||||||
NAME simdjson-data
|
NAME simdjson-data
|
||||||
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
|
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
|
||||||
|
|||||||
+34
-3
@@ -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.
|
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.
|
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`
|
**Memory-file mapping.** You can use `simdjson::padded_memory_map` to create a
|
||||||
from a file on disk:
|
`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
|
```cpp
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
|
||||||
|
#endif
|
||||||
|
#include "simdjson.h"
|
||||||
|
// ...
|
||||||
simdjson::padded_memory_map map(myfilename);
|
simdjson::padded_memory_map map(myfilename);
|
||||||
if (!map.is_valid()) { /* handle error */ }
|
if (!map.is_valid()) { /* handle error */ }
|
||||||
simdjson::padded_string_view view = map.view();
|
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`. |
|
| `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(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. |
|
| `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
|
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.
|
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)
|
### 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
|
#### Special cases
|
||||||
|
|
||||||
However, there are instances where the construction cannot
|
However, there are instances where the construction cannot
|
||||||
|
|||||||
+26
-3
@@ -127,11 +127,31 @@ codepage, and they may call SetFileApisToOEM accordingly.
|
|||||||
|
|
||||||
|
|
||||||
**Advanced feature:**
|
**Advanced feature:**
|
||||||
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
|
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view`
|
||||||
from a file on disk.
|
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
|
```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);
|
simdjson::padded_memory_map map(TWITTER_JSON);
|
||||||
if (!map.is_valid()) { /* handle error */ }
|
if (!map.is_valid()) { /* handle error */ }
|
||||||
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
|
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
|
Reusing the parser for maximum efficiency
|
||||||
|
|||||||
+79
-5
@@ -22,6 +22,7 @@ Contents
|
|||||||
- [Threads](#threads)
|
- [Threads](#threads)
|
||||||
- [Support](#support)
|
- [Support](#support)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
|
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
|
||||||
- [Use cases](#use-cases)
|
- [Use cases](#use-cases)
|
||||||
- [Tracking your position](#tracking-your-position)
|
- [Tracking your position](#tracking-your-position)
|
||||||
- [Incomplete streams](#incomplete-streams)
|
- [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.
|
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
|
||||||
|
|
||||||
|
|
||||||
**Advanced feature:**
|
Streaming directly from a memory-mapped file
|
||||||
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
|
--------------------------------------------
|
||||||
from a file on disk.
|
|
||||||
|
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
|
```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);
|
simdjson::padded_memory_map map(myfilename);
|
||||||
if (!map.is_valid()) { /* handle error */ }
|
if (!map.is_valid()) { /* handle error */ }
|
||||||
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
|
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)
|
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
|
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
|
`tag_invoke` overload: the library generates the deserialization code from the
|
||||||
type's public data members at compile time.
|
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
@@ -18,6 +18,7 @@ Contents
|
|||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Support](#support)
|
- [Support](#support)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
|
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
|
||||||
- [Use cases](#use-cases)
|
- [Use cases](#use-cases)
|
||||||
- [Tracking your position](#tracking-your-position)
|
- [Tracking your position](#tracking-your-position)
|
||||||
- [Incomplete streams](#incomplete-streams)
|
- [Incomplete streams](#incomplete-streams)
|
||||||
@@ -218,17 +219,83 @@ got full document at 29
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Advanced feature:**
|
Streaming directly from a memory-mapped file
|
||||||
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
|
--------------------------------------------
|
||||||
from a file on disk.
|
|
||||||
|
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
|
```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);
|
simdjson::padded_memory_map map(myfilename);
|
||||||
if (!map.is_valid()) { /* handle error */ }
|
if (!map.is_valid()) { /* handle error */ }
|
||||||
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
|
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
|
Incomplete streams
|
||||||
|
|||||||
+7
-1
@@ -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.
|
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`.
|
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:
|
Example usage:
|
||||||
|
|
||||||
@@ -216,6 +216,12 @@ Example usage:
|
|||||||
std::string_view json = get_json_data();
|
std::string_view json = get_json_data();
|
||||||
simdjson::padded_input input(json); // Automatically pads if needed
|
simdjson::padded_input input(json); // Automatically pads if needed
|
||||||
auto result = parser.parse(input);
|
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.
|
This simplifies padding management compared to manually checking and allocating.
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ concept appendable_containers =
|
|||||||
details::supports_add<T> || details::supports_append<T> ||
|
details::supports_add<T> || details::supports_append<T> ||
|
||||||
details::supports_insert<T>) && !string_view_keyed_map<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
|
/// Insert into the container however possible
|
||||||
template <appendable_containers T, typename... Args>
|
template <appendable_containers T, typename... Args>
|
||||||
constexpr decltype(auto) emplace_one(T &vec, Args &&...args) {
|
constexpr decltype(auto) emplace_one(T &vec, Args &&...args) {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ enum class element_type {
|
|||||||
STRING = '"', ///< std::string_view
|
STRING = '"', ///< std::string_view
|
||||||
BOOL = 't', ///< bool
|
BOOL = 't', ///< bool
|
||||||
NULL_VALUE = 'n', ///< null
|
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
|
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
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);
|
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 {
|
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; }
|
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 {
|
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);
|
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 {
|
simdjson_inline size_t parser::capacity() const noexcept {
|
||||||
return implementation ? implementation->capacity() : 0;
|
return implementation ? implementation->capacity() : 0;
|
||||||
|
|||||||
@@ -490,6 +490,16 @@ public:
|
|||||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
/** @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 = dom::DEFAULT_BATCH_SIZE) noexcept;
|
||||||
inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe
|
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. */
|
/** @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;
|
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;
|
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) */
|
/** @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;
|
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
|
* 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.h"
|
||||||
#include "simdjson/generic/ondemand/document_stream.h"
|
#include "simdjson/generic/ondemand/document_stream.h"
|
||||||
#include "simdjson/generic/ondemand/field.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.h"
|
||||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||||
#include "simdjson/generic/ondemand/serialization.h"
|
#include "simdjson/generic/ondemand/serialization.h"
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
#include "simdjson/generic/ondemand/logger-inl.h"
|
#include "simdjson/generic/ondemand/logger-inl.h"
|
||||||
#include "simdjson/generic/ondemand/object-inl.h"
|
#include "simdjson/generic/ondemand/object-inl.h"
|
||||||
#include "simdjson/generic/ondemand/object_iterator-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/parser-inl.h"
|
||||||
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
|
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
|
||||||
#include "simdjson/generic/ondemand/token_iterator-inl.h"
|
#include "simdjson/generic/ondemand/token_iterator-inl.h"
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class raw_json_string;
|
|||||||
class token_iterator;
|
class token_iterator;
|
||||||
class value;
|
class value;
|
||||||
class value_iterator;
|
class value_iterator;
|
||||||
|
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||||
|
template <typename Selector> class selector_iterator;
|
||||||
|
template <typename Selector> class selector_range;
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace ondemand
|
} // namespace ondemand
|
||||||
} // namespace SIMDJSON_IMPLEMENTATION
|
} // 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());
|
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_inline simdjson_result<object> object::start(value_iterator &iter) noexcept {
|
||||||
SIMDJSON_TRY( iter.start_object().error() );
|
SIMDJSON_TRY( iter.start_object().error() );
|
||||||
return object(iter);
|
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);
|
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 {
|
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(); }
|
if (error()) { return error(); }
|
||||||
return first.at_pointer(json_pointer);
|
return first.at_pointer(json_pointer);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "simdjson/generic/ondemand/base.h"
|
#include "simdjson/generic/ondemand/base.h"
|
||||||
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
||||||
#include "simdjson/generic/ondemand/value_iterator.h"
|
#include "simdjson/generic/ondemand/value_iterator.h"
|
||||||
|
#include "simdjson/generic/ondemand/key_selector.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
|
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
|
||||||
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
|
#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; */
|
/** @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;
|
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
|
* 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
|
* 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(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;
|
||||||
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> 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;
|
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
|
||||||
|
|||||||
@@ -10,13 +10,18 @@
|
|||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#if SIMDJSON_HAS_UNISTD_H
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#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 simdjson {
|
||||||
namespace internal {
|
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 {
|
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
|
||||||
|
|
||||||
int fd = open(filename, O_RDONLY);
|
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);
|
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 {
|
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
|
||||||
if(!is_valid()) {
|
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 {
|
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
|
||||||
return data != nullptr;
|
return data != nullptr;
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
|
||||||
|
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
|
||||||
|
|
||||||
} // namespace simdjson
|
} // namespace simdjson
|
||||||
|
|
||||||
|
|||||||
@@ -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(); }
|
inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string> &s) noexcept(false) { return out << s.value(); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
|
||||||
#ifndef _WIN32
|
|
||||||
/**
|
/**
|
||||||
* A class representing a memory-mapped file with padding.
|
* 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 {
|
class padded_memory_map {
|
||||||
public:
|
public:
|
||||||
@@ -289,9 +304,11 @@ public:
|
|||||||
* Create a new padded memory map for the given file.
|
* 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.
|
* 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.
|
* 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,
|
* On POSIX systems, the file content is not copied, so this is efficient for large files.
|
||||||
* the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found,
|
* On Windows, the file is mapped into memory via `MapViewOfFile3` (zero-copy).
|
||||||
* permission denied, etc.), the memory map will be invalid and view() will return an empty view.
|
* 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().
|
* 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.
|
* @param filename the path to the file to memory-map.
|
||||||
@@ -328,8 +345,14 @@ private:
|
|||||||
padded_memory_map &operator=(const padded_memory_map &) = delete;
|
padded_memory_map &operator=(const padded_memory_map &) = delete;
|
||||||
const char *data{nullptr};
|
const char *data{nullptr};
|
||||||
size_t size{0};
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <cstring> /* memcmp */
|
#include <cstring> /* memcmp */
|
||||||
|
|
||||||
// for page size computation.
|
// for page size computation.
|
||||||
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
|
#if SIMDJSON_HAS_UNISTD_H
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
@@ -113,7 +113,7 @@ inline uint32_t get_page_size() noexcept {
|
|||||||
return static_cast<std::uint32_t>(si.dwPageSize);
|
return static_cast<std::uint32_t>(si.dwPageSize);
|
||||||
}();
|
}();
|
||||||
return cached;
|
return cached;
|
||||||
#elif defined(__unix__) || defined(__APPLE__) || defined(__linux__)
|
#elif SIMDJSON_HAS_UNISTD_H
|
||||||
static const uint32_t cached = []() -> uint32_t {
|
static const uint32_t cached = []() -> uint32_t {
|
||||||
long page_size = sysconf(_SC_PAGESIZE);
|
long page_size = sysconf(_SC_PAGESIZE);
|
||||||
if (page_size > 0) {
|
if (page_size > 0) {
|
||||||
|
|||||||
@@ -285,5 +285,53 @@ using std::size_t;
|
|||||||
#endif
|
#endif
|
||||||
#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
|
#endif // SIMDJSON_PORTABILITY_H
|
||||||
|
|||||||
+203
-7
@@ -4356,10 +4356,55 @@ inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string
|
|||||||
#endif
|
#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.
|
* 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 {
|
class padded_memory_map {
|
||||||
public:
|
public:
|
||||||
@@ -4367,9 +4412,12 @@ public:
|
|||||||
* Create a new padded memory map for the given file.
|
* 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.
|
* 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.
|
* 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,
|
* On POSIX systems, the file content is not copied, so this is efficient for large files.
|
||||||
* the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found,
|
* On Windows, the file is mapped into memory via `MapViewOfFile3` whenever possible
|
||||||
* permission denied, etc.), the memory map will be invalid and view() will return an empty view.
|
* (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().
|
* 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.
|
* @param filename the path to the file to memory-map.
|
||||||
@@ -4406,8 +4454,14 @@ private:
|
|||||||
padded_memory_map &operator=(const padded_memory_map &) = delete;
|
padded_memory_map &operator=(const padded_memory_map &) = delete;
|
||||||
const char *data{nullptr};
|
const char *data{nullptr};
|
||||||
size_t size{0};
|
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 <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#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 simdjson {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
@@ -5068,6 +5127,8 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
|
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);
|
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 {
|
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
|
||||||
if(!is_valid()) {
|
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 {
|
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
|
||||||
return data != nullptr;
|
return data != nullptr;
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
|
||||||
|
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
|
||||||
|
|
||||||
} // namespace simdjson
|
} // namespace simdjson
|
||||||
|
|
||||||
|
|||||||
+102
-8
@@ -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
|
#ifdef _WIN32
|
||||||
#include <cstdlib>
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
// This test is not supported on Windows because it relies on POSIX APIs like
|
#define WIN32_LEAN_AND_MEAN
|
||||||
// mmap. Please run it on a POSIX-compliant system.
|
#endif
|
||||||
int main() { return EXIT_SUCCESS; }
|
#ifndef NOMINMAX
|
||||||
#else
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "simdjson.h"
|
#include "simdjson.h"
|
||||||
#include "test_macros.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
|
#if SIMDJSON_EXCEPTIONS
|
||||||
bool test_memory_map_exception() {
|
bool test_memory_map_exception() {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
@@ -37,11 +53,89 @@ bool test_memory_map_noexception() {
|
|||||||
TEST_SUCCEED();
|
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() {
|
int main() {
|
||||||
|
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
|
||||||
|
bool ok = true;
|
||||||
#if SIMDJSON_EXCEPTIONS
|
#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
|
#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
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
@@ -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_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_number_in_string_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_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_object_error_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_ordering_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)
|
add_cpp_test(ondemand_parse_api_tests LABELS ondemand acceptance per_implementation)
|
||||||
|
|||||||
@@ -173,6 +173,36 @@ namespace object_tests {
|
|||||||
TEST_SUCCEED();
|
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() {
|
bool run() {
|
||||||
return
|
return
|
||||||
object_find_field_unordered() &&
|
object_find_field_unordered() &&
|
||||||
@@ -181,6 +211,9 @@ namespace object_tests {
|
|||||||
object_find_field() &&
|
object_find_field() &&
|
||||||
document_object_find_field() &&
|
document_object_find_field() &&
|
||||||
value_object_find_field() &&
|
value_object_find_field() &&
|
||||||
|
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||||
|
object_find_field_key_selector() &&
|
||||||
|
#endif
|
||||||
true;
|
true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user