Skip to content
Merged
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 @@
Speed up creating :class:`str` objects from non-ASCII character data.
231 changes: 131 additions & 100 deletions Objects/stringlib/find_max_char.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,130 +5,161 @@
# error "find_max_char.h is specific to Unicode"
#endif

/* Mask to quickly check whether a C 'size_t' contains a
non-ASCII, UTF8-encoded char. */
#if (SIZEOF_SIZE_T == 8)
# define UCS1_ASCII_CHAR_MASK 0x8080808080808080ULL
#elif (SIZEOF_SIZE_T == 4)
# define UCS1_ASCII_CHAR_MASK 0x80808080U
#else
# error C 'size_t' size should be either 4 or 8!
#endif
#define MAX_CHAR_ASCII 0x7f
#define MAX_CHAR_UCS1 0xff
#define MAX_CHAR_UCS2 0xffff
#define MAX_CHAR_UCS4 0x10ffff

/* Masks selecting the bits that are set in a C 'size_t' holding several
characters when at least one of them is wider than ASCII, UCS1 or UCS2. */
#if STRINGLIB_SIZEOF_CHAR == 1
# if SIZEOF_SIZE_T == 8
# define MASK_ASCII 0x8080808080808080ULL
# elif SIZEOF_SIZE_T == 4
# define MASK_ASCII 0x80808080U
# else
# error C 'size_t' size should be either 4 or 8!
# endif
Comment thread
eendebakpt marked this conversation as resolved.
Resolved
# define MASK_MAX_CHAR MASK_ASCII
# define MAX_CHAR MAX_CHAR_UCS1
#elif STRINGLIB_SIZEOF_CHAR == 2
# if SIZEOF_SIZE_T == 8
# define MASK_ASCII 0xFF80FF80FF80FF80ULL
# define MASK_UCS1 0xFF00FF00FF00FF00ULL
# elif SIZEOF_SIZE_T == 4
# define MASK_ASCII 0xFF80FF80U
# define MASK_UCS1 0xFF00FF00U
# else
# error C 'size_t' size should be either 4 or 8!
# endif
# define MASK_MAX_CHAR MASK_UCS1
# define MAX_CHAR MAX_CHAR_UCS2
#elif STRINGLIB_SIZEOF_CHAR == 4
# if SIZEOF_SIZE_T == 8
# define MASK_ASCII 0xFFFFFF80FFFFFF80ULL
# define MASK_UCS1 0xFFFFFF00FFFFFF00ULL
# define MASK_UCS2 0xFFFF0000FFFF0000ULL
# elif SIZEOF_SIZE_T == 4
# define MASK_ASCII 0xFFFFFF80U
# define MASK_UCS1 0xFFFFFF00U
# define MASK_UCS2 0xFFFF0000U
# else
# error C 'size_t' size should be either 4 or 8!
# endif
# define MASK_MAX_CHAR MASK_UCS2
# define MAX_CHAR MAX_CHAR_UCS4
#else
# error Invalid STRINGLIB_SIZEOF_CHAR (must be 1, 2 or 4)
#endif

/* OR all characters into a single size_t accumulator and test the high bits
at the end, instead of comparing each character. The accumulation loop has
no branches, so compilers vectorize it. The start is first read in pieces
until the pointer is aligned, so the loops can use plain word loads on
every platform. Every 16 words, check whether the widest kind is already
known and exit early. */
Py_LOCAL_INLINE(Py_UCS4)
STRINGLIB(find_max_char)(const STRINGLIB_CHAR *begin, const STRINGLIB_CHAR *end)
{
const unsigned char *p = (const unsigned char *) begin;
const unsigned char *p = (const unsigned char *)begin;
const unsigned char *_end = (const unsigned char *)end;
Comment thread
vstinner marked this conversation as resolved.
Resolved
size_t value = 0;

while (p < _end) {
if (_Py_IS_ALIGNED(p, ALIGNOF_SIZE_T)) {
/* Help register allocation */
const unsigned char *_p = p;
while (_p + SIZEOF_SIZE_T <= _end) {
size_t value = *(const size_t *) _p;
if (value & UCS1_ASCII_CHAR_MASK)
return 255;
_p += SIZEOF_SIZE_T;
if (p + SIZEOF_SIZE_T <= _end) {
if (!_Py_IS_ALIGNED(p, ALIGNOF_SIZE_T)) {
#if STRINGLIB_SIZEOF_CHAR == 1
if (!_Py_IS_ALIGNED(p, sizeof(uint16_t))) {
value |= *p++;
}
p = _p;
if (p == _end)
break;
}
if (*p++ & 0x80)
return 255;
}
return 127;
}

#undef ASCII_CHAR_MASK

#else /* STRINGLIB_SIZEOF_CHAR == 1 */

#define MASK_ASCII 0xFFFFFF80
#define MASK_UCS1 0xFFFFFF00
#define MASK_UCS2 0xFFFF0000

#define MAX_CHAR_ASCII 0x7f
#define MAX_CHAR_UCS1 0xff
#define MAX_CHAR_UCS2 0xffff
#define MAX_CHAR_UCS4 0x10ffff

Py_LOCAL_INLINE(Py_UCS4)
STRINGLIB(find_max_char)(const STRINGLIB_CHAR *begin, const STRINGLIB_CHAR *end)
{
#if STRINGLIB_SIZEOF_CHAR == 2
const Py_UCS4 mask_limit = MASK_UCS1;
const Py_UCS4 max_char_limit = MAX_CHAR_UCS2;
#elif STRINGLIB_SIZEOF_CHAR == 4
const Py_UCS4 mask_limit = MASK_UCS2;
const Py_UCS4 max_char_limit = MAX_CHAR_UCS4;
#else
#error Invalid STRINGLIB_SIZEOF_CHAR (must be 1, 2 or 4)
#endif
Py_UCS4 mask;
Py_ssize_t n = end - begin;
const STRINGLIB_CHAR *p = begin;
const STRINGLIB_CHAR *unrolled_end = begin + _Py_SIZE_ROUND_DOWN(n, 4);
Py_UCS4 max_char;

max_char = MAX_CHAR_ASCII;
mask = MASK_ASCII;
while (p < unrolled_end) {
STRINGLIB_CHAR bits = p[0] | p[1] | p[2] | p[3];
if (bits & mask) {
if (mask == mask_limit) {
/* Limit reached */
return max_char_limit;
#if STRINGLIB_SIZEOF_CHAR <= 2
if (!_Py_IS_ALIGNED(p, sizeof(uint32_t))) {
value |= *(const uint16_t *)p;
p += sizeof(uint16_t);
}
if (mask == MASK_ASCII) {
max_char = MAX_CHAR_UCS1;
mask = MASK_UCS1;
#endif
#if SIZEOF_SIZE_T == 8
if (!_Py_IS_ALIGNED(p, SIZEOF_SIZE_T)) {
value |= *(const uint32_t *)p;
p += sizeof(uint32_t);
}
else {
/* mask can't be MASK_UCS2 because of mask_limit above */
assert(mask == MASK_UCS1);
max_char = MAX_CHAR_UCS2;
mask = MASK_UCS2;
#endif
if (value & MASK_MAX_CHAR) {
return MAX_CHAR;
}
/* We check the new mask on the same chars in the next iteration */
continue;
assert(_Py_IS_ALIGNED(p, ALIGNOF_SIZE_T));
}
p += 4;
}
while (p < end) {
if (p[0] & mask) {
if (mask == mask_limit) {
/* Limit reached */
return max_char_limit;
}
if (mask == MASK_ASCII) {
max_char = MAX_CHAR_UCS1;
mask = MASK_UCS1;

for (; p + SIZEOF_SIZE_T * 16 <= _end; p += SIZEOF_SIZE_T * 16) {
/* Four independent accumulators so the loop does not serialize on
one register when the compiler does not vectorize it. */
size_t v0 = 0, v1 = 0, v2 = 0, v3 = 0;
for (int i = 0; i < 16 * SIZEOF_SIZE_T; i += 4 * SIZEOF_SIZE_T) {
const size_t *w = (const size_t *)(p + i);
v0 |= w[0];
v1 |= w[1];
v2 |= w[2];
v3 |= w[3];
}
else {
/* mask can't be MASK_UCS2 because of mask_limit above */
assert(mask == MASK_UCS1);
max_char = MAX_CHAR_UCS2;
mask = MASK_UCS2;
size_t block = v0 | v1 | v2 | v3;
if (block & MASK_MAX_CHAR) {
return MAX_CHAR;
}
/* We check the new mask on the same chars in the next iteration */
continue;
value |= block;
}

for (; p + SIZEOF_SIZE_T <= _end; p += SIZEOF_SIZE_T) {
value |= *(const size_t *)p;
}
p++;
}
return max_char;

/* Fewer than SIZEOF_SIZE_T bytes are left. Read them as two overlapping
pieces, one from each end. OR does not mind seeing a byte twice. */
if (p < _end) {
size_t n = (size_t)(_end - p);
if (n >= 4) {
uint32_t a, b;
memcpy(&a, p, 4);
memcpy(&b, _end - 4, 4);
value |= a | b;
}
#if STRINGLIB_SIZEOF_CHAR <= 2
else if (n >= 2) {
uint16_t a, b;
memcpy(&a, p, 2);
memcpy(&b, _end - 2, 2);
value |= a | b;
}
#endif
#if STRINGLIB_SIZEOF_CHAR == 1
else {
value |= *p;
}
#endif
}

#ifdef MASK_UCS2
if (value & MASK_UCS2) {
return MAX_CHAR_UCS4;
}
#endif
#ifdef MASK_UCS1
if (value & MASK_UCS1) {
return MAX_CHAR_UCS2;
}
#endif
if (value & MASK_ASCII) {
return MAX_CHAR_UCS1;
}
return MAX_CHAR_ASCII;
}

#undef MASK_MAX_CHAR
#undef MAX_CHAR
#undef MASK_ASCII
#undef MASK_UCS1
#undef MASK_UCS2
#undef MAX_CHAR_ASCII
#undef MAX_CHAR_UCS1
#undef MAX_CHAR_UCS2
#undef MAX_CHAR_UCS4

#endif /* STRINGLIB_SIZEOF_CHAR == 1 */

Loading