Skip to content

Commit a0c6c4c

Browse files
authored
gh-153568: Cache repeated identifiers while parsing (#153577)
1 parent a53b5b1 commit a0c6c4c

3 files changed

Lines changed: 57 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up the parser by caching repeated identifiers during a parse.

Parser/pegen.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212
#include "tokenizer/helpers.h"
1313
#include "pegen.h"
1414

15+
#define IDENTIFIER_CACHE_SIZE 2048 // Must be a power of two.
16+
#define IDENTIFIER_CACHE_MAX_PROBES 8
17+
18+
struct _identifier_cache_entry {
19+
const char *key; // Borrowed from arena-owned token bytes.
20+
Py_ssize_t len;
21+
Py_hash_t hash;
22+
PyObject *value; // Borrowed from an arena-owned identifier.
23+
};
24+
1525
// Internal parser functions
1626

1727
asdl_stmt_seq*
@@ -572,11 +582,44 @@ _PyPegen_name_from_token(Parser *p, Token* t)
572582
p->error_indicator = 1;
573583
return NULL;
574584
}
585+
// Identifiers repeat constantly; a small span-keyed cache skips the
586+
// UTF-8 decode + intern for repeated occurrences. Keys point into
587+
// arena-owned token bytes and values are arena-owned interned strings,
588+
// so borrowed references are valid for the lifetime of the parse
589+
// (including the second error pass, which reuses parser and arena).
590+
Py_ssize_t len = PyBytes_GET_SIZE(t->bytes);
591+
Py_hash_t hash = PyObject_Hash(t->bytes);
592+
if (hash == -1) {
593+
p->error_indicator = 1;
594+
return NULL;
595+
}
596+
IdentifierCacheEntry *free_slot = NULL;
597+
size_t idx = (size_t)hash & (IDENTIFIER_CACHE_SIZE - 1);
598+
for (int probe = 0; probe < IDENTIFIER_CACHE_MAX_PROBES; probe++) {
599+
IdentifierCacheEntry *entry = &p->identifier_cache[
600+
(idx + probe) & (IDENTIFIER_CACHE_SIZE - 1)];
601+
if (entry->key == NULL) {
602+
free_slot = entry;
603+
break;
604+
}
605+
if (entry->hash == hash && entry->len == len &&
606+
memcmp(entry->key, s, len) == 0)
607+
{
608+
return _PyAST_Name(entry->value, Load, t->lineno, t->col_offset,
609+
t->end_lineno, t->end_col_offset, p->arena);
610+
}
611+
}
575612
PyObject *id = _PyPegen_new_identifier(p, s);
576613
if (id == NULL) {
577614
p->error_indicator = 1;
578615
return NULL;
579616
}
617+
if (free_slot != NULL) {
618+
free_slot->key = s;
619+
free_slot->len = len;
620+
free_slot->hash = hash;
621+
free_slot->value = id;
622+
}
580623
return _PyAST_Name(id, Load, t->lineno, t->col_offset, t->end_lineno,
581624
t->end_col_offset, p->arena);
582625
}
@@ -844,6 +887,15 @@ _PyPegen_Parser_New(struct tok_state *tok, int start_rule, int flags,
844887
p->flags = flags;
845888
p->feature_version = feature_version;
846889
p->known_err_token = NULL;
890+
p->identifier_cache = PyMem_Calloc(
891+
IDENTIFIER_CACHE_SIZE, sizeof(*p->identifier_cache));
892+
if (p->identifier_cache == NULL) {
893+
growable_comment_array_deallocate(&p->type_ignore_comments);
894+
PyMem_Free(p->tokens[0]);
895+
PyMem_Free(p->tokens);
896+
PyMem_Free(p);
897+
return (Parser *) PyErr_NoMemory();
898+
}
847899
p->level = 0;
848900
p->call_invalid_rules = 0;
849901
p->last_stmt_location.lineno = 0;
@@ -859,6 +911,7 @@ _PyPegen_Parser_New(struct tok_state *tok, int start_rule, int flags,
859911
void
860912
_PyPegen_Parser_Free(Parser *p)
861913
{
914+
PyMem_Free(p->identifier_cache);
862915
Py_XDECREF(p->normalize);
863916
for (int i = 0; i < p->size; i++) {
864917
PyMem_Free(p->tokens[i]);

Parser/pegen.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ typedef struct {
6767
int end_col_offset;
6868
} location;
6969

70+
typedef struct _identifier_cache_entry IdentifierCacheEntry;
71+
7072
typedef struct {
7173
struct tok_state *tok;
7274
Token **tokens;
@@ -91,6 +93,7 @@ typedef struct {
9193
int call_invalid_rules;
9294
int debug;
9395
location last_stmt_location;
96+
IdentifierCacheEntry *identifier_cache;
9497
} Parser;
9598

9699
typedef struct {

0 commit comments

Comments
 (0)