summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2023-10-23 17:02:31 -0700
committerGravatar Copybara-Service <copybara-worker@google.com>2023-10-23 17:03:27 -0700
commita6d9a9cddade9139616a980c8092431492b84f3c (patch)
tree70a07d57303c04a8ff01936310026c28caee6b74
parenta0b72adc3576eb0b77efb7133207c354d0adb4bc (diff)
Performance improvements for `absl::AsciiStrToLower` and `absl::AsciiStrToUpper`.
PiperOrigin-RevId: 575969640 Change-Id: If6ddc0a71debfe571c2739ec91fc99594bc36f88
-rw-r--r--absl/strings/ascii.cc22
-rw-r--r--absl/strings/ascii_benchmark.cc18
2 files changed, 29 insertions, 11 deletions
diff --git a/absl/strings/ascii.cc b/absl/strings/ascii.cc
index 8c6b1e05..1e6566e9 100644
--- a/absl/strings/ascii.cc
+++ b/absl/strings/ascii.cc
@@ -159,6 +159,20 @@ ABSL_DLL const char kToUpper[256] = {
};
// clang-format on
+// Returns whether `c` is in the a-z/A-Z range (w.r.t. `ToUpper`).
+// Implemented by:
+// 1. Pushing the a-z/A-Z range to [SCHAR_MIN, SCHAR_MIN + 26).
+// 2. Comparing to SCHAR_MIN + 26.
+template <bool ToUpper>
+constexpr bool AsciiInAZRange(unsigned char c) {
+ constexpr unsigned char sub = (ToUpper ? 'a' : 'A') - SCHAR_MIN;
+ constexpr signed char threshold = SCHAR_MIN + 26; // 26 = alphabet size.
+ // Using unsigned arithmetic as overflows/underflows are well defined.
+ unsigned char u = c - sub;
+ // Using signed cmp, as SIMD unsigned cmp isn't available in many platforms.
+ return static_cast<signed char>(u) < threshold;
+}
+
template <bool ToUpper>
constexpr void AsciiStrCaseFold(char* p, char* end) {
// The upper- and lowercase versions of ASCII characters differ by only 1 bit.
@@ -168,15 +182,9 @@ constexpr void AsciiStrCaseFold(char* p, char* end) {
// have the same single bit difference.
constexpr unsigned char kAsciiCaseBitFlip = 'a' ^ 'A';
- constexpr char ch_a = ToUpper ? 'a' : 'A';
- constexpr char ch_z = ToUpper ? 'z' : 'Z';
for (; p < end; ++p) {
unsigned char v = static_cast<unsigned char>(*p);
- // We use & instead of && to ensure this always stays branchless
- // We use static_cast<int> to suppress -Wbitwise-instead-of-logical
- bool is_in_range = static_cast<bool>(static_cast<int>(ch_a <= v) &
- static_cast<int>(v <= ch_z));
- v ^= is_in_range ? kAsciiCaseBitFlip : 0;
+ v ^= AsciiInAZRange<ToUpper>(v) ? kAsciiCaseBitFlip : 0;
*p = static_cast<char>(v);
}
}
diff --git a/absl/strings/ascii_benchmark.cc b/absl/strings/ascii_benchmark.cc
index b04b28ca..3aecdb8b 100644
--- a/absl/strings/ascii_benchmark.cc
+++ b/absl/strings/ascii_benchmark.cc
@@ -105,18 +105,28 @@ static void BM_StrToLower(benchmark::State& state) {
const int size = state.range(0);
std::string s(size, 'X');
for (auto _ : state) {
- benchmark::DoNotOptimize(absl::AsciiStrToLower(s));
+ benchmark::DoNotOptimize(s);
+ std::string res = absl::AsciiStrToLower(s);
+ benchmark::DoNotOptimize(res);
}
}
-BENCHMARK(BM_StrToLower)->Range(1, 1 << 20);
+BENCHMARK(BM_StrToLower)
+ ->DenseRange(0, 32)
+ ->RangeMultiplier(2)
+ ->Range(64, 1 << 20);
static void BM_StrToUpper(benchmark::State& state) {
const int size = state.range(0);
std::string s(size, 'x');
for (auto _ : state) {
- benchmark::DoNotOptimize(absl::AsciiStrToUpper(s));
+ benchmark::DoNotOptimize(s);
+ std::string res = absl::AsciiStrToUpper(s);
+ benchmark::DoNotOptimize(res);
}
}
-BENCHMARK(BM_StrToUpper)->Range(1, 1 << 20);
+BENCHMARK(BM_StrToUpper)
+ ->DenseRange(0, 32)
+ ->RangeMultiplier(2)
+ ->Range(64, 1 << 20);
} // namespace