Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Speed up :meth:`str.lower`, :meth:`str.upper` and :meth:`str.casefold` for
ASCII strings, and :meth:`bytes.lower`, :meth:`bytes.upper` and their
:class:`bytearray` counterparts, by converting a machine word at a time.
Patch by Parker Loflin.
59 changes: 59 additions & 0 deletions Objects/bytes_methods.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,57 @@ _Py_bytes_istitle(const char *cptr, Py_ssize_t len)
}


/* Return 0x20 in each byte of x that lies in first..first+25, else 0,
given 0x80 - first and 0x80 - first - 26 in every byte of ge and gt.
For a byte b < 0x80, b + (0x80 - first) has its high bit set iff
b >= first, and b + (0x80 - first - 26) iff b > first + 25; neither
sum carries. "& ~x" drops bytes >= 0x80. */
static inline Py_ALWAYS_INLINE size_t
ascii_case_bits(size_t x, size_t ge, size_t gt)
{
const size_t ones = (size_t)-1 / 0xff; /* 0x01 in every byte */
size_t b = x & (0x7f * ones);
size_t ge_first = b + ge;
size_t gt_last = b + gt;
return (ge_first & ~gt_last & ~x & (0x80 * ones)) >> 2;
}

static inline Py_ALWAYS_INLINE void
ascii_case_word(char *result, const char *cptr, Py_ssize_t i,
size_t ge, size_t gt)
{
size_t x;
memcpy(&x, cptr + i, sizeof(x));
x ^= ascii_case_bits(x, ge, gt);
memcpy(result + i, &x, sizeof(x));
}

/* Requires len >= SIZEOF_SIZE_T. The last word may overlap the one before
it; converting a byte twice is harmless. Not inlined, so that short
strings skip its setup. */
static Py_NO_INLINE void
ascii_case_words(char *result, const char *cptr, Py_ssize_t len,
unsigned char first)
{
const size_t ones = (size_t)-1 / 0xff;
size_t ge = (0x80 - first) * ones, gt = (0x80 - first - 26) * ones;
Py_ssize_t i;
#if defined(_MSC_VER) && !defined(__clang__)
/* MSVC only vectorizes loops that count by one; elsewhere such a loop
gets post-indexed accesses, slow on Apple cores. Dividing unsigned
avoids an idiv in code optimized for size. */
Py_ssize_t n = (size_t)(len - 1) / SIZEOF_SIZE_T;
for (i = 0; i < n; i++) {
ascii_case_word(result, cptr, i * SIZEOF_SIZE_T, ge, gt);
}
#else
for (i = 0; i < len - SIZEOF_SIZE_T; i += SIZEOF_SIZE_T) {
ascii_case_word(result, cptr, i, ge, gt);
}
#endif
ascii_case_word(result, cptr, len - SIZEOF_SIZE_T, ge, gt);
}

PyDoc_STRVAR_shared(_Py_lower__doc__,
"B.lower() -> copy of B\n\
\n\
Expand All @@ -252,6 +303,10 @@ _Py_bytes_lower(char *result, const char *cptr, Py_ssize_t len)
{
Py_ssize_t i;

if (len >= SIZEOF_SIZE_T) {
ascii_case_words(result, cptr, len, 'A');
return;
}
for (i = 0; i < len; i++) {
result[i] = Py_TOLOWER((unsigned char) cptr[i]);
}
Expand All @@ -268,6 +323,10 @@ _Py_bytes_upper(char *result, const char *cptr, Py_ssize_t len)
{
Py_ssize_t i;

if (len >= SIZEOF_SIZE_T) {
ascii_case_words(result, cptr, len, 'a');
return;
}
for (i = 0; i < len; i++) {
result[i] = Py_TOUPPER((unsigned char) cptr[i]);
}
Expand Down
Loading