Skip to content

Commit 67072fd

Browse files
QrczakMKcopybara-github
authored andcommitted
Optimize counting varints when parsing packed repeated fields.
Count them in 64-bit blocks, using `& 0x8080808080808080` and `absl::popcount()`. PiperOrigin-RevId: 847794231
1 parent bc7e2d2 commit 67072fd

File tree

2 files changed

+31
-4
lines changed

2 files changed

+31
-4
lines changed

src/google/protobuf/parse_context.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "absl/base/optimization.h"
2020
#include "absl/base/prefetch.h"
2121
#include "absl/log/absl_check.h"
22+
#include "absl/numeric/bits.h"
2223
#include "absl/strings/cord.h"
2324
#include "absl/strings/str_cat.h"
2425
#include "absl/strings/string_view.h"
@@ -767,6 +768,27 @@ template std::pair<const char*, bool> EpsCopyInputStream::DoneFallback<false>(
767768
template std::pair<const char*, bool> EpsCopyInputStream::DoneFallback<true>(
768769
int, int);
769770

771+
int CountVarintsAssumingLargeArray(const char* ptr, const char* end) {
772+
// The number of varints is the number of bytes with the highest bit clear.
773+
// This is easier to compute as the total number of bytes, minus the number
774+
// of bytes with the highest bit set.
775+
int num_varints = end - ptr;
776+
ABSL_DCHECK_GE(num_varints, int{sizeof(uint64_t)});
777+
778+
// Count in whole blocks, except for the last one.
779+
const char* const limit = end - sizeof(uint64_t);
780+
while (ptr < limit) {
781+
num_varints -=
782+
absl::popcount(EndianHelper<8>::Load(ptr) & 0x8080808080808080);
783+
ptr += sizeof(uint64_t);
784+
}
785+
786+
// Count in the last, possibly incomplete block.
787+
return num_varints -
788+
absl::popcount(EndianHelper<8>::Load(limit) &
789+
(0x8080808080808080 << ((ptr - limit) * 8)));
790+
}
791+
770792
} // namespace internal
771793
} // namespace protobuf
772794
} // namespace google

src/google/protobuf/parse_context.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ inline void WriteVarint(uint32_t num, uint64_t val, UnknownFieldSet* unknown);
6666
inline void WriteLengthDelimited(uint32_t num, absl::string_view val,
6767
UnknownFieldSet* unknown);
6868

69+
// Counts the number of varints in the array, assuming that end - ptr >= 8.
70+
// If varints are valid only up to some point, then returns at least the number
71+
// of valid varints.
72+
int CountVarintsAssumingLargeArray(const char* ptr, const char* end);
73+
6974

7075
// The basic abstraction the parser is designed for is a slight modification
7176
// of the ZeroCopyInputStream (ZCIS) abstraction. A ZCIS presents a serialized
@@ -1389,13 +1394,13 @@ const char* EpsCopyInputStream::ReadPackedVarintArrayWithField(
13891394
// field, than parsing, so count the number of ints first and preallocate.
13901395
// Assume that varint are valid and just count the number of bytes with
13911396
// continuation bit not set. In a valid varint there is only 1 such byte.
1392-
if ((end - ptr) >= 16 && (out.Capacity() - out.size() < end - ptr)) {
1397+
if (end - ptr >= 16 && out.Capacity() - out.size() < end - ptr) {
13931398
int old_size = out.size();
13941399
int count = out.Capacity() - out.size();
13951400
// We are not guaranteed to have enough space for worst possible case,
13961401
// do an actual count and reserve.
13971402
if (count < end - ptr) {
1398-
count = std::count_if(ptr, end, [](char c) { return (c & 0x80) == 0; });
1403+
count = CountVarintsAssumingLargeArray(ptr, end);
13991404
// We can overread, so if the last byte has a continuation bit set,
14001405
// we need to account for that.
14011406
if (end[-1] & 0x80) count++;
@@ -1408,8 +1413,8 @@ const char* EpsCopyInputStream::ReadPackedVarintArrayWithField(
14081413
});
14091414
int new_size = x - out.data();
14101415
ABSL_DCHECK_LE(new_size, old_size + count);
1411-
// We may have overreserved if there was enough capacitiy.
1412-
// Or encountered malformed data, so set the actaul size to
1416+
// We may have overreserved if there was enough capacity.
1417+
// Or encountered malformed data, so set the actual size to
14131418
// avoid exposing uninitialized memory.
14141419
out.Truncate(new_size);
14151420
return ptr;

0 commit comments

Comments
 (0)