Grow the ASCII run before walking for forward index reads (#2502)

This commit is contained in:
Chris Tate
2026-10-28 22:27:56 -05:00
committed by GitHub
2 changed files with 79 additions and 6 deletions
+30 -6
View File
@@ -1756,6 +1756,36 @@ static __attribute__((noinline)) size_t scr_u16_to_byte_walk(const ScrStr *s, Sc
e->cb = cb;
return cb;
}
/* A forward access past the cached ASCII run (a scanner taking slices at
* increasing positions of a mostly ASCII text) grows the run over the
* ASCII bytes up to it. When a non-ASCII character ends the run first, a
* nearby access walks from the run's end or the hot cursor, whichever is
* nearer, and a new run starts where the walk lands, so the accesses after
* it map by arithmetic again. */
if (u16 >= e->ascii_cu) {
if (scr_sidx_ascii_grow(s, e, e->ascii_cb + (u16 - e->ascii_cu))) {
e->cb = e->ascii_cb + (u16 - e->ascii_cu);
e->cu = u16;
*mid = false;
return e->cb;
}
size_t run_cu = e->ascii_cu + (e->ascii_end - e->ascii_cb);
if (e->ascii_end < s->len && u16 - run_cu <= SCR_SIDX_NEAR_UNITS) {
size_t cu = run_cu, cb = e->ascii_end;
if (e->cb <= s->len && e->cu >= cu && e->cu <= u16) {
cu = e->cu;
cb = e->cb;
}
size_t at = scr_sidx_walk_from(s, e, cu, cb, u16, mid, false);
if (!*mid && at < s->len && (unsigned char)s->data[at] < 0x80) {
e->ascii_cu = e->cu;
e->ascii_cb = at;
e->ascii_end = at;
(void)scr_sidx_ascii_grow(s, e, at);
}
return at;
}
}
/* Within a short distance of the hot cursor (a scanner
* taking slices near its position), walk from that cursor: no index
* search, extension or ASCII-run growth is cheaper. */
@@ -1785,12 +1815,6 @@ static __attribute__((noinline)) size_t scr_u16_to_byte_walk(const ScrStr *s, Sc
return cb;
}
}
if (u16 >= e->ascii_cu && scr_sidx_ascii_grow(s, e, e->ascii_cb + (u16 - e->ascii_cu))) {
e->cb = e->ascii_cb + (u16 - e->ascii_cu);
e->cu = u16;
*mid = false;
return e->cb;
}
scr_sidx_extend_to_u16(s, e, u16);
/* Extending a far-end lookup can just have proved identity. Do not
* materialize an index that the identity fast path will never consult. */
+49
View File
@@ -927,6 +927,54 @@ static void local_navigation_asserts(void) {
scr_sidx_test_reset_cache();
}
/* A scanner reading a mostly ASCII text front to back (positioned
* startsWith and substrings at increasing positions, with a non-ASCII
* character every 300 bytes) maps its positions through the ASCII run, so
* it walks only across the non-ASCII characters. */
static void forward_scan_asserts(void) {
enum { ASCII = 300, UNITS = ASCII + 1, REPS = 100 };
char piece[ASCII + 2];
for (size_t i = 0; i < ASCII; i++) piece[i] = (char)('a' + i % 26);
memcpy(piece + ASCII, "\xC3\xA9", 2); /* é: one unit */
ScrStr *unit = scr_str_new(piece, sizeof piece);
ScrStr *s = scr_str_repeat(unit, REPS);
scr_str_release(unit);
scr_sidx_test_reset_cache();
if (scr_str_utf16_len(s) != UNITS * REPS) sidx_fail("forward scan length");
ScrStr *needle = scr_str_new("ab", 2);
scr_sidx_test_reset_steps();
for (size_t at = 0; at + 2 <= UNITS * REPS; at += 5) {
size_t offset = at % UNITS;
bool expected = offset + 1 < ASCII && offset % 26 == 0;
if (scr_str_starts_with_from(s, needle, (double)at) != expected) {
sidx_fail("forward startsWith");
break;
}
char want[4];
size_t n = 0;
for (size_t k = at; k < at + 2; k++) {
size_t o = k % UNITS;
if (o < ASCII) want[n++] = (char)('a' + o % 26);
else {
want[n++] = '\xC3';
want[n++] = '\xA9';
}
}
ScrStr *part = scr_str_substring(s, (double)at, (double)(at + 2));
bool same = part->len == n && memcmp(part->data, want, n) == 0;
scr_str_release(part);
if (!same) {
sidx_fail("forward substring");
break;
}
}
if (scr_sidx_test_walk_steps() > REPS * 16)
sidx_fail("forward reads walked between non-ASCII characters");
scr_str_release(needle);
scr_str_release(s);
scr_sidx_test_reset_cache();
}
/* An entry borrowed for a receiver must remain its entry when a second
* string needs indexing. Fill the cursor tier so the needle would evict
* precisely the receiver under a receiver-first lookup order. */
@@ -1244,6 +1292,7 @@ int main(int argc, char **argv) {
index_activity_asserts();
sparse_slice_churn_asserts();
local_navigation_asserts();
forward_scan_asserts();
positioned_suffix_eviction_asserts();
#endif