Skip to content
41 changes: 41 additions & 0 deletions benchmarks/percent_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,47 @@ static void C0Control(benchmark::State& state) {
}
BENCHMARK(C0Control);

std::string long_examples[] = {
"connect timeout=10 application name=myapp server=db host internal "
"database=production analytics read preference=secondary preferred "
"ssl=true retry writes=true w=majority max pool size=50",
"ref=web twc ao gbl adsinfo utm source=twc utm medium=cpc "
"utm campaign=brand awareness q4 2024 utm content=banner 300x250 "
"utm term=weather forecast today gclid=Cj0KCQiA3Y ABhCnARIsAK",
};

double long_examples_bytes = []() -> double {
size_t bytes{0};
for (std::string& url_string : long_examples) {
bytes += url_string.size();
}
return double(bytes);
}();

static void LongFragment(benchmark::State& state) {
for (auto _ : state) {
for (std::string& url_string : long_examples) {
benchmark::DoNotOptimize(ada::unicode::percent_encode(
url_string, ada::character_sets::FRAGMENT_PERCENT_ENCODE));
}
}
state.counters["speed"] = benchmark::Counter(
long_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
}
BENCHMARK(LongFragment);

static void LongQuery(benchmark::State& state) {
for (auto _ : state) {
for (std::string& url_string : long_examples) {
benchmark::DoNotOptimize(ada::unicode::percent_encode(
url_string, ada::character_sets::QUERY_PERCENT_ENCODE));
}
}
state.counters["speed"] = benchmark::Counter(
long_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
}
BENCHMARK(LongQuery);

int main(int argc, char** argv) {
#if defined(ADA_RUST_VERSION)
benchmark::AddCustomContext("rust version ", ADA_RUST_VERSION);
Expand Down
6 changes: 5 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ target_include_directories(ada-include-source INTERFACE $<BUILD_INTERFACE:${CMAK
add_library(ada-source INTERFACE)
target_sources(ada-source INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/ada.cpp)
target_link_libraries(ada-source INTERFACE ada-include-source)
add_library(ada ada.cpp)
add_library(ada ada.cpp unicode_percent_encode.cpp)
target_compile_features(ada PUBLIC cxx_std_20)
target_include_directories(ada PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> )
target_include_directories(ada PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
Expand Down Expand Up @@ -60,6 +60,10 @@ if(ADA_TESTING)
target_compile_definitions(ada PRIVATE ADA_TESTING=1)
endif()

# Keep the SIMD percent-encode kernel in its own TU so the unity ada.cpp
# inlining budget for setters matches main.
target_compile_definitions(ada PRIVATE ADA_PERCENT_ENCODE_SIMD_SEPARATE_TU=1)

if(ADA_INCLUDE_URL_PATTERN)
target_compile_definitions(ada PRIVATE ADA_INCLUDE_URL_PATTERN=1)
else()
Expand Down
3 changes: 3 additions & 0 deletions src/ada.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "ada.h"
#include "checkers.cpp"
#include "unicode.cpp"
#if !defined(ADA_PERCENT_ENCODE_SIMD_SEPARATE_TU)
#include "unicode_percent_encode.cpp"
#endif
#include "serializers.cpp"
#include "implementation.cpp"
#include "helpers.cpp"
Expand Down
42 changes: 30 additions & 12 deletions src/unicode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,9 @@ std::string form_urlencoded_decode(const std::string_view input) {
return out;
}

void percent_encode_suffix(const char* p, const char* end,
const uint8_t character_set[], std::string& out);

std::string percent_encode(const std::string_view input,
const uint8_t character_set[]) {
auto pointer = std::ranges::find_if(input, [character_set](const char c) {
Expand All @@ -604,15 +607,18 @@ std::string percent_encode(const std::string_view input,
result.reserve(input.length()); // in the worst case, percent encoding might
// produce 3 characters.
result.append(input.substr(0, std::distance(input.begin(), pointer)));

for (; pointer != input.end(); pointer++) {
if (character_sets::bit_at(character_set, *pointer)) {
result.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
} else {
result += *pointer;
if (static_cast<size_t>(input.end() - pointer) >= 48) {
percent_encode_suffix(&*pointer, input.data() + input.size(), character_set,
result);
} else {
for (; pointer != input.end(); pointer++) {
if (character_sets::bit_at(character_set, *pointer)) {
result.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
} else {
result += *pointer;
}
}
}

return result;
}

Expand Down Expand Up @@ -651,6 +657,13 @@ bool percent_encode(const std::string_view input, const uint8_t character_set[],
return true;
}

template bool percent_encode<true>(std::string_view input,
const uint8_t character_set[],
std::string& out);
template bool percent_encode<false>(std::string_view input,
const uint8_t character_set[],
std::string& out);

bool to_ascii(std::optional<std::string>& out, const std::string_view plain,
size_t first_percent) {
std::string percent_decoded_buffer;
Expand All @@ -675,11 +688,16 @@ std::string percent_encode(const std::string_view input,
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
out.append(input.data(), index);
auto pointer = input.begin() + index;
for (; pointer != input.end(); pointer++) {
if (character_sets::bit_at(character_set, *pointer)) {
out.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
} else {
out += *pointer;
if (static_cast<size_t>(input.end() - pointer) >= 48) {
percent_encode_suffix(&*pointer, input.data() + input.size(), character_set,
out);
} else {
for (; pointer != input.end(); pointer++) {
if (character_sets::bit_at(character_set, *pointer)) {
out.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
} else {
out += *pointer;
}
}
}
return out;
Expand Down
Loading
Loading