Skip to content

Commit

Permalink
Performance improvement for absl::AsciiStrToUpper() and absl::AsciiSt…
Browse files Browse the repository at this point in the history
…rToLower()

PiperOrigin-RevId: 592664369
Change-Id: I7aa7b045c2b3c0f25cff7b82eb9d9cc13e9fc49f
  • Loading branch information
Abseil Team authored and copybara-github committed Dec 20, 2023
1 parent 72d7a15 commit 794352a
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 5 deletions.
66 changes: 63 additions & 3 deletions absl/strings/ascii.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
#include "absl/strings/ascii.h"

#include <climits>
#include <cstdint>
#include <cstring>
#include <string>
#include <type_traits>

#include "absl/base/config.h"
#include "absl/base/nullability.h"
Expand Down Expand Up @@ -160,6 +162,19 @@ ABSL_DLL const char kToUpper[256] = {
};
// clang-format on

template <class T>
static constexpr T BroadcastByte(unsigned char value) {
static_assert(std::is_integral<T>::value && sizeof(T) <= sizeof(uint64_t) &&
std::is_unsigned<T>::value,
"only unsigned integers up to 64-bit allowed");
T result = value;
constexpr size_t result_bit_width = sizeof(result) * CHAR_BIT;
result |= result << ((CHAR_BIT << 0) & (result_bit_width - 1));
result |= result << ((CHAR_BIT << 1) & (result_bit_width - 1));
result |= result << ((CHAR_BIT << 2) & (result_bit_width - 1));
return result;
}

// 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).
Expand All @@ -175,19 +190,64 @@ constexpr bool AsciiInAZRange(unsigned char c) {
}

template <bool ToUpper>
constexpr void AsciiStrCaseFold(absl::Nonnull<char*> p,
absl::Nonnull<char*> end) {
static constexpr char* PartialAsciiStrCaseFold(absl::Nonnull<char*> p,
absl::Nonnull<char*> end) {
using vec_t = size_t;
const size_t n = static_cast<size_t>(end - p);

// SWAR algorithm: http://0x80.pl/notesen/2016-01-06-swar-swap-case.html
constexpr char ch_a = ToUpper ? 'a' : 'A', ch_z = ToUpper ? 'z' : 'Z';
char* const swar_end = p + (n / sizeof(vec_t)) * sizeof(vec_t);
while (p < swar_end) {
vec_t v = vec_t();

// memcpy the vector, but constexpr
for (size_t i = 0; i < sizeof(vec_t); ++i) {
v |= static_cast<vec_t>(static_cast<unsigned char>(p[i]))
<< (i * CHAR_BIT);
}

constexpr unsigned int msb = 1u << (CHAR_BIT - 1);
const vec_t v_msb = v & BroadcastByte<vec_t>(msb);
const vec_t v_nonascii_mask = (v_msb << 1) - (v_msb >> (CHAR_BIT - 1));
const vec_t v_nonascii = v & v_nonascii_mask;
const vec_t v_ascii = v & ~v_nonascii_mask;
const vec_t a = v_ascii + BroadcastByte<vec_t>(msb - ch_a - 0),
z = v_ascii + BroadcastByte<vec_t>(msb - ch_z - 1);
v = v_nonascii | (v_ascii ^ ((a ^ z) & BroadcastByte<vec_t>(msb)) >> 2);

// memcpy the vector, but constexpr
for (size_t i = 0; i < sizeof(vec_t); ++i) {
p[i] = static_cast<char>(v >> (i * CHAR_BIT));
}

p += sizeof(v);
}

return p;
}

template <bool ToUpper>
static constexpr void AsciiStrCaseFold(absl::Nonnull<char*> p,
absl::Nonnull<char*> end) {
// The upper- and lowercase versions of ASCII characters differ by only 1 bit.
// When we need to flip the case, we can xor with this bit to achieve the
// desired result. Note that the choice of 'a' and 'A' here is arbitrary. We
// could have chosen 'z' and 'Z', or any other pair of characters as they all
// have the same single bit difference.
constexpr unsigned char kAsciiCaseBitFlip = 'a' ^ 'A';

for (; p < end; ++p) {
using vec_t = size_t;
// TODO(b/316380338): When FDO becomes able to vectorize these,
// revert this manual optimization and just leave the naive loop.
if (static_cast<size_t>(end - p) >= sizeof(vec_t)) {
p = ascii_internal::PartialAsciiStrCaseFold<ToUpper>(p, end);
}
while (p < end) {
unsigned char v = static_cast<unsigned char>(*p);
v ^= AsciiInAZRange<ToUpper>(v) ? kAsciiCaseBitFlip : 0;
*p = static_cast<char>(v);
++p;
}
}

Expand Down
4 changes: 2 additions & 2 deletions absl/strings/ascii_benchmark.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ static void BM_StrToLower(benchmark::State& state) {
BENCHMARK(BM_StrToLower)
->DenseRange(0, 32)
->RangeMultiplier(2)
->Range(64, 1 << 20);
->Range(64, 1 << 26);

static void BM_StrToUpper(benchmark::State& state) {
const int size = state.range(0);
Expand All @@ -127,6 +127,6 @@ static void BM_StrToUpper(benchmark::State& state) {
BENCHMARK(BM_StrToUpper)
->DenseRange(0, 32)
->RangeMultiplier(2)
->Range(64, 1 << 20);
->Range(64, 1 << 26);

} // namespace

0 comments on commit 794352a

Please sign in to comment.