Skip to content

Commit 1cc4816

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
[Pratt Parser] Cache source content size in Lexer to avoid repeated variant dispatch in character loops.
`SourceContentView::size()` is defined out-of-line and dispatches via `absl::visit` over a 4-element `absl::variant`. Because the character scanning loops in `Lexer` also invoke out-of-line methods on `content_`, the compiler cannot hoist `content_.size()` out of loop conditions, causing it to be re-evaluated on every character scanned. Caching `content_size_` in `Lexer` improves Pratt parser CPU time across `pratt_parser_benchmark` by ~5.2% geomean (~6.7% geomean on valid expressions). Measured with `pratt_parser_benchmark` (built `-c opt --dynamic_mode=off`). "Pratt before" is this CL's parent (`p4head`), so the last column is this CL's own contribution. The ANTLR column is the series baseline and is unaffected by this change. CPU time, median across 36 interleaved repetitions: | Case | ANTLR | Pratt before | Pratt after | Pratt vs ANTLR | Delta this CL | | :--- | ---: | ---: | ---: | ---: | ---: | | ParseCommon | 210,982 ns | 11,606 ns | 11,003 ns | 19.2x faster | -5.2% | | ParseArithmeticChain/10 | 55,289 ns | 2,781 ns | 2,608 ns | 21.2x faster | -6.2% | | ParseArithmeticChain/50 | 291,952 ns | 13,794 ns | 13,265 ns | 22.0x faster | -3.8% | | ParseArithmeticChain/100 | 559,317 ns | 27,506 ns | 25,724 ns | 21.7x faster | -6.5% | | ParseLogicalChain/10 | 43,318 ns | 3,306 ns | 2,986 ns | 14.5x faster | -9.7% | | ParseLogicalChain/50 | 207,612 ns | 15,118 ns | 13,940 ns | 14.9x faster | -7.8% | | ParseLogicalChain/100 | 442,381 ns | 30,089 ns | 28,675 ns | 15.4x faster | -4.7% | | ParseMemberChain/10 | 34,693 ns | 1,819 ns | 1,705 ns | 20.4x faster | -6.3% | | ParseMemberChain/50 | 161,104 ns | 8,633 ns | 7,889 ns | 20.4x faster | -8.6% | | ParseMemberChain/100 | 335,750 ns | 16,917 ns | 15,724 ns | 21.4x faster | -7.1% | | ParseNestedParentheses/10 | 28,084 ns | 803 ns | 752 ns | 37.3x faster | -6.3% | | ParseNestedParentheses/50 | 123,579 ns | 2,841 ns | 2,620 ns | 47.2x faster | -7.8% | | ParseCommonSyntaxErrors | 429,515 ns | 18,317 ns | 17,561 ns | 24.5x faster | -4.1% | | ParseArithmeticChainSyntaxError/10 | 72,212 ns | 3,256 ns | 3,078 ns | 23.5x faster | -5.5% | | ParseArithmeticChainSyntaxError/50 | 295,287 ns | 14,321 ns | 13,419 ns | 22.0x faster | -6.3% | | ParseArithmeticChainSyntaxError/100 | 628,385 ns | 28,006 ns | 26,414 ns | 23.8x faster | -5.7% | | ParseLogicalChainSyntaxError/10 | 58,855 ns | 3,776 ns | 3,553 ns | 16.6x faster | -5.9% | | ParseLogicalChainSyntaxError/50 | 235,134 ns | 16,104 ns | 15,147 ns | 15.5x faster | -5.9% | | ParseLogicalChainSyntaxError/100 | 458,219 ns | 31,111 ns | 29,472 ns | 15.5x faster | -5.3% | | ParseMemberChainSyntaxError/10 | 50,335 ns | 2,144 ns | 2,051 ns | 24.5x faster | -4.3% | | ParseMemberChainSyntaxError/50 | 182,649 ns | 8,962 ns | 8,516 ns | 21.4x faster | -5.0% | | ParseMemberChainSyntaxError/100 | 346,654 ns | 17,161 ns | 16,230 ns | 21.4x faster | -5.4% | | ParseNestedParenthesesSyntaxError/10 | 241,350 ns | 6,098 ns | 5,982 ns | 40.3x faster | -1.9% | | ParseNestedParenthesesSyntaxError/50 | 1,561,548 ns | 28,725 ns | 27,390 ns | 57.0x faster | -4.6% | | ParseRepeatedSyntaxErrors/10 | 170,609 ns | 6,218 ns | 6,044 ns | 28.2x faster | -2.8% | | ParseRepeatedSyntaxErrors/50 | 172,554 ns | 8,602 ns | 8,620 ns | 20.0x faster | +0.2% | | ParseRepeatedSyntaxErrors/100 | 180,960 ns | 9,324 ns | 9,532 ns | 19.0x faster | +2.2% | | **Geomean** | | | | **22.5x faster** | **-5.2%** | PiperOrigin-RevId: 983495524
1 parent dd26ee7 commit 1cc4816

2 files changed

Lines changed: 30 additions & 28 deletions

File tree

parser/internal/lexer.cc

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ std::string_view TokenTypeToString(TokenType type) {
162162

163163
Token Lexer::Lex() {
164164
int32_t start = GetPosition();
165-
if (ABSL_PREDICT_FALSE(position_ >= content_.size())) {
165+
if (ABSL_PREDICT_FALSE(position_ >= content_size_)) {
166166
return MakeToken(TokenType::kEnd, start, start);
167167
}
168168
char32_t c = content_.at(position_);
@@ -182,8 +182,7 @@ Token Lexer::Lex() {
182182
return MakeToken(TokenType::kWhitespace, start, GetPosition());
183183
}
184184
case '.': {
185-
if (position_ + 1 < content_.size() &&
186-
content_.at(position_ + 1) <= 0x7f &&
185+
if (position_ + 1 < content_size_ && content_.at(position_ + 1) <= 0x7f &&
187186
absl::ascii_isdigit(static_cast<char>(content_.at(position_ + 1)))) {
188187
return ConsumeNumericLiteral();
189188
}
@@ -342,7 +341,7 @@ bool Lexer::ConsumeUntilAfter(char32_t c, bool is_raw) {
342341
ABSL_DCHECK_NE(c, '\r');
343342
int32_t pos = position_;
344343
bool escaped = false;
345-
while (pos < content_.size()) {
344+
while (pos < content_size_) {
346345
char32_t cc = content_.at(pos);
347346
if (cc == '\n' || cc == '\r') {
348347
AdvanceProcessingNewLines(pos);
@@ -359,7 +358,7 @@ bool Lexer::ConsumeUntilAfter(char32_t c, bool is_raw) {
359358
}
360359
++pos;
361360
}
362-
AdvanceProcessingNewLines(content_.size());
361+
AdvanceProcessingNewLines(content_size_);
363362
return false;
364363
}
365364

@@ -370,7 +369,7 @@ bool Lexer::ConsumeUntilAfter(char32_t c, bool is_raw) {
370369
bool Lexer::ConsumeUntilAfterString(std::u32string_view s) {
371370
ABSL_DCHECK(s.find(U'\n') == std::u32string_view::npos);
372371
int32_t pos = position_;
373-
while (pos + static_cast<int32_t>(s.size()) <= content_.size()) {
372+
while (pos + static_cast<int32_t>(s.size()) <= content_size_) {
374373
bool match = true;
375374
for (size_t i = 0; i < s.size(); ++i) {
376375
if (content_.at(pos + static_cast<int32_t>(i)) != s[i]) {
@@ -384,7 +383,7 @@ bool Lexer::ConsumeUntilAfterString(std::u32string_view s) {
384383
}
385384
++pos;
386385
}
387-
AdvanceProcessingNewLines(content_.size());
386+
AdvanceProcessingNewLines(content_size_);
388387
return false;
389388
}
390389

@@ -396,12 +395,12 @@ bool Lexer::ConsumeUntilAfterUnescapedString(std::u32string_view s) {
396395
ABSL_DCHECK(s.find(U'\n') == std::u32string_view::npos);
397396
int32_t pos = position_;
398397
bool escaped = false;
399-
while (pos < content_.size()) {
398+
while (pos < content_size_) {
400399
char32_t cc = content_.at(pos);
401400
if (cc == '\\') {
402401
escaped = !escaped;
403402
} else {
404-
if (!escaped && pos + static_cast<int32_t>(s.size()) <= content_.size()) {
403+
if (!escaped && pos + static_cast<int32_t>(s.size()) <= content_size_) {
405404
bool match = true;
406405
for (size_t j = 0; j < s.size(); ++j) {
407406
if (content_.at(pos + static_cast<int32_t>(j)) != s[j]) {
@@ -418,12 +417,12 @@ bool Lexer::ConsumeUntilAfterUnescapedString(std::u32string_view s) {
418417
}
419418
++pos;
420419
}
421-
AdvanceProcessingNewLines(content_.size());
420+
AdvanceProcessingNewLines(content_size_);
422421
return false;
423422
}
424423

425424
bool Lexer::MatchString(std::u32string_view s) const {
426-
if (position_ + static_cast<int32_t>(s.size()) > content_.size()) {
425+
if (position_ + static_cast<int32_t>(s.size()) > content_size_) {
427426
return false;
428427
}
429428
for (size_t i = 0; i < s.size(); ++i) {
@@ -436,7 +435,7 @@ bool Lexer::MatchString(std::u32string_view s) const {
436435

437436
std::optional<char32_t> Lexer::MatchIf(
438437
absl::FunctionRef<bool(char32_t)> predicate) const {
439-
if (position_ < content_.size()) {
438+
if (position_ < content_size_) {
440439
char32_t cp = content_.at(position_);
441440
if (predicate(cp)) {
442441
return cp;
@@ -446,7 +445,7 @@ std::optional<char32_t> Lexer::MatchIf(
446445
}
447446

448447
void Lexer::ConsumeLine() {
449-
while (position_ < content_.size()) {
448+
while (position_ < content_size_) {
450449
if (content_.at(position_) == '\n') {
451450
Advance(1);
452451
return;
@@ -456,7 +455,7 @@ void Lexer::ConsumeLine() {
456455
}
457456

458457
void Lexer::ConsumeWhitespace() {
459-
while (position_ < content_.size()) {
458+
while (position_ < content_size_) {
460459
char32_t c = content_.at(position_);
461460
switch (c) {
462461
case '\f':
@@ -517,7 +516,7 @@ std::optional<char32_t> Lexer::ConsumeIf(
517516

518517
bool Lexer::ConsumeDigits() {
519518
bool advanced = false;
520-
while (position_ < content_.size()) {
519+
while (position_ < content_size_) {
521520
char32_t c = content_.at(position_);
522521
if (c > 0x7f || !absl::ascii_isdigit(static_cast<char>(c))) {
523522
break;
@@ -530,7 +529,7 @@ bool Lexer::ConsumeDigits() {
530529

531530
bool Lexer::ConsumeHexDigits() {
532531
bool advanced = false;
533-
while (position_ < content_.size()) {
532+
while (position_ < content_size_) {
534533
char32_t c = content_.at(position_);
535534
if (c > 0x7f || !absl::ascii_isxdigit(static_cast<char>(c))) {
536535
break;
@@ -588,12 +587,12 @@ Token Lexer::ConsumeStringLiteral(int32_t start, char32_t quote, bool is_bytes,
588587
// rb"""...""", rb'''...'''
589588
std::optional<Token> Lexer::ConsumePrefixedStringLiteral() {
590589
int32_t start = GetPosition();
591-
if (position_ >= content_.size()) return std::nullopt;
590+
if (position_ >= content_size_) return std::nullopt;
592591
char32_t c = content_.at(position_);
593592
bool is_bytes = (c == 'b' || c == 'B');
594593
bool is_raw = (c == 'r' || c == 'R');
595594
size_t lookahead = 1;
596-
if (position_ + 1 < content_.size()) {
595+
if (position_ + 1 < content_size_) {
597596
char32_t c2 = content_.at(position_ + 1);
598597
if ((is_bytes && (c2 == 'r' || c2 == 'R')) ||
599598
(!is_bytes && (c2 == 'b' || c2 == 'B'))) {
@@ -602,7 +601,7 @@ std::optional<Token> Lexer::ConsumePrefixedStringLiteral() {
602601
lookahead = 2;
603602
}
604603
}
605-
if (position_ + static_cast<int32_t>(lookahead) < content_.size()) {
604+
if (position_ + static_cast<int32_t>(lookahead) < content_size_) {
606605
char32_t quote = content_.at(position_ + static_cast<int32_t>(lookahead));
607606
if (quote == '"' || quote == '\'') {
608607
Advance(lookahead);
@@ -649,8 +648,8 @@ Token Lexer::ConsumeNumericLiteral() {
649648
}
650649
}
651650
static_cast<void>(ConsumeDigits());
652-
if (position_ < content_.size() && content_.at(position_) == '.' &&
653-
position_ + 1 < content_.size() && content_.at(position_ + 1) <= 0x7f &&
651+
if (position_ < content_size_ && content_.at(position_) == '.' &&
652+
position_ + 1 < content_size_ && content_.at(position_ + 1) <= 0x7f &&
654653
absl::ascii_isdigit(static_cast<char>(content_.at(position_ + 1)))) {
655654
floating_point = true;
656655
Advance(1);
@@ -679,7 +678,7 @@ Token Lexer::ConsumeNumericLiteral() {
679678

680679
Token Lexer::ConsumeIdent() {
681680
int32_t start = GetPosition();
682-
while (position_ < content_.size()) {
681+
while (position_ < content_size_) {
683682
char32_t c = content_.at(position_);
684683
if (!IsIdentTrailing(c)) {
685684
break;

parser/internal/lexer.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ struct LexerError final {
135135
class Lexer final {
136136
public:
137137
explicit Lexer(const cel::Source& source)
138-
: content_(source.content()), position_(0) {
138+
: content_(source.content()),
139+
content_size_(static_cast<int32_t>(content_.size())),
140+
position_(0) {
139141
ABSL_DCHECK_LE(content_.size(), static_cast<SourcePosition>(
140142
std::numeric_limits<int32_t>::max()));
141143
}
@@ -162,31 +164,31 @@ class Lexer final {
162164

163165
void RestorePosition(int32_t position) {
164166
ABSL_DCHECK_GE(position, 0);
165-
ABSL_DCHECK_LE(position, static_cast<int32_t>(content_.size()));
167+
ABSL_DCHECK_LE(position, content_size_);
166168
position_ = position;
167169
error_ = LexerError{};
168170
}
169171

170172
private:
171173
[[nodiscard]] bool Match(char32_t c) const {
172-
return position_ < content_.size() && content_.at(position_) == c;
174+
return position_ < content_size_ && content_.at(position_) == c;
173175
}
174176

175177
[[nodiscard]] bool MatchIgnoreCase(char32_t c) const {
176-
if (position_ >= content_.size()) return false;
178+
if (position_ >= content_size_) return false;
177179
char32_t cp = content_.at(position_);
178180
return cp <= 0x7f && c <= 0x7f &&
179181
absl::ascii_tolower(static_cast<char>(cp)) ==
180182
absl::ascii_tolower(static_cast<char>(c));
181183
}
182184

183185
void Advance(size_t n) {
184-
ABSL_DCHECK_LE(n, static_cast<size_t>(content_.size() - position_));
186+
ABSL_DCHECK_LE(n, static_cast<size_t>(content_size_ - position_));
185187
position_ += static_cast<int32_t>(n);
186188
}
187189

188190
void AdvanceProcessingNewLines(int32_t end_position) {
189-
ABSL_DCHECK_LE(end_position, content_.size());
191+
ABSL_DCHECK_LE(end_position, content_size_);
190192
ABSL_DCHECK_GE(end_position, position_);
191193
Advance(static_cast<size_t>(end_position - position_));
192194
}
@@ -275,6 +277,7 @@ class Lexer final {
275277
[[nodiscard]] Token ConsumeIdent();
276278

277279
cel::SourceContentView content_;
280+
int32_t content_size_ = 0;
278281
int32_t position_ = 0;
279282
LexerError error_;
280283
};

0 commit comments

Comments
 (0)