re2: hoist a few loads out of BitState ShouldVisit
Caching the fields from prog_ in the outer loop instead
of reloading them inside each call to ShouldVisit makes
the fast search path of BitState noticeably faster.
Thanks to @nafi3000 for the idea and the initial patch.
benchmark \ host s7 mac
vs base vs base
Search_Success1_BitState/size=8 ~ ~
Search_Success1_BitState/size=64 -3.63% -2.04%
Search_Success1_BitState/size=512 -15.83% -6.83%
Search_Success1_BitState/size=4096 -25.59% -10.74%
Search_Success1_BitState/size=32768 -27.85% -11.85%
Search_Success1_BitState/size=262144 -28.03% -11.94%
Search_Success1_BitState/size=2097152 -27.99% -11.80%
Search_Success1_CachedBitState/size=8 -17.81% -8.16%
Search_Success1_CachedBitState/size=64 -27.17% -10.69%
Search_Success1_CachedBitState/size=512 -27.63% -11.56%
Search_Success1_CachedBitState/size=4096 -27.88% -11.61%
Search_Success1_CachedBitState/size=32768 -27.90% -11.60%
Search_Success1_CachedBitState/size=262144 -27.73% -11.55%
Search_Success1_CachedBitState/size=2097152 -27.88% -11.54%
Search_AltMatch_BitState/size=8 +0.83% ~
Search_AltMatch_BitState/size=64 +0.88% ~
Search_AltMatch_BitState/size=512 +0.88% ~
Search_AltMatch_BitState/size=4096 +1.02% ~
Search_AltMatch_BitState/size=32768 +1.02% ~
Search_AltMatch_BitState/size=262144 +1.22% ~
Search_AltMatch_BitState/size=2097152 +1.10% ~
Search_AltMatch_BitState/size=16777216 ~ ~
Search_AltMatch_CachedBitState/size=8 -1.79% ~
Search_AltMatch_CachedBitState/size=64 -3.23% ~
Search_AltMatch_CachedBitState/size=512 -3.39% ~
Search_AltMatch_CachedBitState/size=4096 ~ ~
Search_AltMatch_CachedBitState/size=32768 ~ ~
Search_AltMatch_CachedBitState/size=262144 ~ ~
Search_AltMatch_CachedBitState/size=2097152 ~ ~
Search_AltMatch_CachedBitState/size=16777216 ~ ~
host: s7
│ old │ new │
│ sec/op │ sec/op vs base │
Search_Success1_BitState/size=8 50.55µ ± 0% 50.58µ ± 0% ~ (p=0.806 n=25)
Search_Success1_BitState/size=64 58.28µ ± 0% 56.16µ ± 0% -3.63% (p=0.000 n=25)
Search_Success1_BitState/size=512 119.6µ ± 0% 100.7µ ± 0% -15.83% (p=0.000 n=25)
Search_Success1_BitState/size=4096 612.7µ ± 0% 455.9µ ± 0% -25.59% (p=0.000 n=25)
Search_Success1_BitState/size=32768 4.563m ± 0% 3.292m ± 0% -27.85% (p=0.000 n=25)
Search_Success1_BitState/size=262144 36.09m ± 0% 25.98m ± 0% -28.03% (p=0.000 n=25)
Search_Success1_BitState/size=2097152 288.6m ± 0% 207.8m ± 0% -27.99% (p=0.000 n=25)
Search_Success1_CachedBitState/size=8 1.971µ ± 1% 1.620µ ± 0% -17.81% (p=0.000 n=25)
Search_Success1_CachedBitState/size=64 9.829µ ± 1% 7.158µ ± 0% -27.17% (p=0.000 n=25)
Search_Success1_CachedBitState/size=512 71.19µ ± 0% 51.52µ ± 0% -27.63% (p=0.000 n=25)
Search_Success1_CachedBitState/size=4096 563.1µ ± 0% 406.1µ ± 0% -27.88% (p=0.000 n=25)
Search_Success1_CachedBitState/size=32768 4.492m ± 0% 3.239m ± 0% -27.90% (p=0.000 n=25)
Search_Success1_CachedBitState/size=262144 35.88m ± 0% 25.93m ± 0% -27.73% (p=0.000 n=25)
Search_Success1_CachedBitState/size=2097152 287.3m ± 0% 207.2m ± 0% -27.88% (p=0.000 n=25)
Search_AltMatch_BitState/size=8 16.54µ ± 0% 16.68µ ± 0% +0.83% (p=0.000 n=25)
Search_AltMatch_BitState/size=64 16.53µ ± 0% 16.68µ ± 0% +0.88% (p=0.000 n=25)
Search_AltMatch_BitState/size=512 16.53µ ± 0% 16.68µ ± 0% +0.88% (p=0.000 n=25)
Search_AltMatch_BitState/size=4096 16.62µ ± 0% 16.79µ ± 0% +1.02% (p=0.000 n=25)
Search_AltMatch_BitState/size=32768 16.63µ ± 0% 16.80µ ± 0% +1.02% (p=0.000 n=25)
Search_AltMatch_BitState/size=262144 17.05µ ± 0% 17.26µ ± 0% +1.22% (p=0.000 n=25)
Search_AltMatch_BitState/size=2097152 22.36µ ± 0% 22.61µ ± 0% +1.10% (p=0.000 n=25)
Search_AltMatch_BitState/size=16777216 508.3µ ± 1% 510.5µ ± 0% ~ (p=0.202 n=25)
Search_AltMatch_CachedBitState/size=8 616.0n ± 1% 605.0n ± 2% -1.79% (p=0.000 n=25)
Search_AltMatch_CachedBitState/size=64 620.0n ± 1% 600.0n ± 1% -3.23% (p=0.000 n=25)
Search_AltMatch_CachedBitState/size=512 620.0n ± 1% 599.0n ± 1% -3.39% (p=0.000 n=25)
Search_AltMatch_CachedBitState/size=4096 629.0n ± 1% 629.0n ± 0% ~ (p=0.747 n=25)
Search_AltMatch_CachedBitState/size=32768 690.0n ± 0% 688.0n ± 1% ~ (p=0.874 n=25)
Search_AltMatch_CachedBitState/size=262144 1.212µ ± 0% 1.210µ ± 0% ~ (p=0.415 n=25)
Search_AltMatch_CachedBitState/size=2097152 5.988µ ± 2% 5.931µ ± 4% ~ (p=0.066 n=25)
Search_AltMatch_CachedBitState/size=16777216 510.8µ ± 0% 512.0µ ± 0% ~ (p=0.164 n=25)
geomean 70.97µ 62.83µ -11.47%
host: mac
│ old │ new │
│ sec/op │ sec/op vs base │
Search_Success1_BitState/size=8 33.09µ ± 1% 33.02µ ± 1% ~ (p=0.003 n=25)
Search_Success1_BitState/size=64 37.52µ ± 1% 36.75µ ± 0% -2.04% (p=0.000 n=25)
Search_Success1_BitState/size=512 71.32µ ± 1% 66.45µ ± 0% -6.83% (p=0.000 n=25)
Search_Success1_BitState/size=4096 341.2µ ± 0% 304.6µ ± 0% -10.74% (p=0.000 n=25)
Search_Success1_BitState/size=32768 2.505m ± 0% 2.208m ± 0% -11.85% (p=0.000 n=25)
Search_Success1_BitState/size=262144 19.79m ± 0% 17.43m ± 0% -11.94% (p=0.000 n=25)
Search_Success1_BitState/size=2097152 158.1m ± 0% 139.5m ± 0% -11.80% (p=0.000 n=25)
Search_Success1_CachedBitState/size=8 1.201µ ± 0% 1.103µ ± 0% -8.16% (p=0.000 n=25)
Search_Success1_CachedBitState/size=64 5.442µ ± 0% 4.860µ ± 0% -10.69% (p=0.000 n=25)
Search_Success1_CachedBitState/size=512 39.18µ ± 0% 34.65µ ± 0% -11.56% (p=0.000 n=25)
Search_Success1_CachedBitState/size=4096 308.7µ ± 0% 272.9µ ± 0% -11.61% (p=0.000 n=25)
Search_Success1_CachedBitState/size=32768 2.466m ± 0% 2.180m ± 0% -11.60% (p=0.000 n=25)
Search_Success1_CachedBitState/size=262144 19.71m ± 0% 17.44m ± 0% -11.55% (p=0.000 n=25)
Search_Success1_CachedBitState/size=2097152 157.8m ± 0% 139.6m ± 0% -11.54% (p=0.000 n=25)
Search_AltMatch_BitState/size=8 11.48µ ± 0% 11.40µ ± 1% ~ (p=0.573 n=25)
Search_AltMatch_BitState/size=64 11.51µ ± 0% 11.39µ ± 2% ~ (p=0.200 n=25)
Search_AltMatch_BitState/size=512 11.41µ ± 1% 11.39µ ± 1% ~ (p=0.866 n=25)
Search_AltMatch_BitState/size=4096 11.51µ ± 1% 11.39µ ± 2% ~ (p=0.328 n=25)
Search_AltMatch_BitState/size=32768 11.60µ ± 0% 11.61µ ± 2% ~ (p=0.711 n=25)
Search_AltMatch_BitState/size=262144 12.33µ ± 1% 12.20µ ± 1% ~ (p=0.044 n=25)
Search_AltMatch_BitState/size=2097152 24.24µ ± 2% 25.27µ ± 7% ~ (p=0.095 n=25)
Search_AltMatch_BitState/size=16777216 310.3µ ± 7% 280.1µ ± 2% ~ (p=0.001 n=25)
Search_AltMatch_CachedBitState/size=8 375.0n ± 1% 375.0n ± 0% ~ (p=0.260 n=25)
Search_AltMatch_CachedBitState/size=64 414.0n ± 6% 380.0n ± 14% ~ (p=0.025 n=25)
Search_AltMatch_CachedBitState/size=512 377.0n ± 0% 378.0n ± 1% ~ (p=0.775 n=25)
Search_AltMatch_CachedBitState/size=4096 414.0n ± 1% 412.0n ± 1% ~ (p=0.008 n=25)
Search_AltMatch_CachedBitState/size=32768 524.0n ± 1% 526.0n ± 1% ~ (p=0.248 n=25)
Search_AltMatch_CachedBitState/size=262144 2.825µ ± 4% 2.904µ ± 6% ~ (p=0.342 n=25)
Search_AltMatch_CachedBitState/size=2097152 22.63µ ± 31% 22.63µ ± 9% ~ (p=0.399 n=25)
Search_AltMatch_CachedBitState/size=16777216 304.0µ ± 6% 281.2µ ± 4% ~ (p=0.052 n=25)
geomean 49.43µ 46.81µ -5.31%
diff --git a/re2/bitstate.cc b/re2/bitstate.cc
index 71f41c3..b63a92b 100644
--- a/re2/bitstate.cc
+++ b/re2/bitstate.cc
@@ -49,7 +49,7 @@
bool longest, absl::string_view* submatch, int nsubmatch);
private:
- inline bool ShouldVisit(int id, const char* p);
+ static inline bool ShouldVisit(absl::string_view text, uint64_t* visited, uint16_t id, const char* p);
void Push(int id, const char* p);
void GrowStack();
bool TrySearch(int id, const char* p);
@@ -85,16 +85,19 @@
njob_(0) {
}
-// Given id, which *must* be a list head, we can look up its list ID.
-// Then the question is: Should the search visit the (list ID, p) pair?
+// Given the text being searched and current visited state,
+// as well as a list ID, should the search visit the (list ID, p) pair?
// If so, remember that it was visited so that the next time,
// we don't repeat the visit.
-bool BitState::ShouldVisit(int id, const char* p) {
- int n = prog_->list_heads()[id] * static_cast<int>(text_.size()+1) +
- static_cast<int>(p-text_.data());
- if (visited_[n/kVisitedBits] & (uint64_t{1} << (n & (kVisitedBits-1))))
+// We pass text and visited to this as a static method so that the
+// caller can do those loads once instead of this code dereferencing
+// them multiple times.
+bool BitState::ShouldVisit(absl::string_view text, uint64_t* visited, uint16_t list_id, const char* p) {
+ int n = list_id * static_cast<int>(text.size()+1) +
+ static_cast<int>(p-text.data());
+ if (visited[n/kVisitedBits] & (uint64_t{1} << (n & (kVisitedBits-1))))
return false;
- visited_[n/kVisitedBits] |= uint64_t{1} << (n & (kVisitedBits-1));
+ visited[n/kVisitedBits] |= uint64_t{1} << (n & (kVisitedBits-1));
return true;
}
@@ -140,10 +143,12 @@
bool BitState::TrySearch(int id0, const char* p0) {
bool matched = false;
const char* end = text_.data() + text_.size();
+ uint16_t* list_heads = prog_->list_heads();
+ uint64_t* visited = visited_.data();
njob_ = 0;
// Push() no longer checks ShouldVisit(),
// so we must perform the check ourselves.
- if (ShouldVisit(id0, p0))
+ if (ShouldVisit(text_, visited, list_heads[id0], p0))
Push(id0, p0);
while (njob_ > 0) {
// Pop job off stack.
@@ -237,7 +242,7 @@
// Sanity check: id is the head of its list, which must
// be the case if id-1 is the last of *its* list. :)
ABSL_DCHECK(id == 0 || prog_->inst(id-1)->last());
- if (ShouldVisit(id, p))
+ if (ShouldVisit(text_, visited, list_heads[id], p))
goto Loop;
break;