Commit bf31a10b by Abseil Team Committed by dinord

Export of internal Abseil changes

--
93c607726d663800b4bfa472cba043fd3f5d0e97 by Derek Mauro <dmauro@google.com>:

Internal change

PiperOrigin-RevId: 389158822

--
55b3bb50bbc168567c6ba25d07df2c2c39e864af by Martijn Vels <mvels@google.com>:

Change CordRepRing alternative implementation to CordRepBtree alternative.

This changes makes CordRepBtree (BTREE) the alternative to CordRepConcat (CONCAT) trees, enabled through the internal / experimental 'cord_btree_enabled' latch.

PiperOrigin-RevId: 389030571

--
d6fc346143606c096bca8eb5029e4c429ac6e305 by Todd Lipcon <tlipcon@google.com>:

Fix a small typo in SequenceLock doc comment

PiperOrigin-RevId: 388972936

--
e46f9245dce8b4150e3ca2664e0cf42b75f90a83 by Martijn Vels <mvels@google.com>:

Add 'shallow' validation mode to CordRepBtree which will be the default for internal assertions.

PiperOrigin-RevId: 388753606

--
b5e74f163b490beb006f848ace67bb650433fe13 by Martijn Vels <mvels@google.com>:

Add btree statistics to CordzInfo, and reduce rounding errors

PiperOrigin-RevId: 388715878

--
105bcbf80de649937e693b29b18220f9e6841a51 by Evan Brown <ezb@google.com>:

Skip length checking when constructing absl::string_view from `const char*`.
The length check causes unnecessary code bloat.

PiperOrigin-RevId: 388271741

--
bed595158f24839efe49c65ae483f797d79fe0ae by Derek Mauro <dmauro@google.com>:

Internal change

PiperOrigin-RevId: 387713428
GitOrigin-RevId: 93c607726d663800b4bfa472cba043fd3f5d0e97
Change-Id: I2a4840f5ffcd7f70b7d7d45cce66f23c42cf565f
parent ab01e040
...@@ -49,7 +49,7 @@ inline constexpr size_t AlignUp(size_t x, size_t align) { ...@@ -49,7 +49,7 @@ inline constexpr size_t AlignUp(size_t x, size_t align) {
// The memory reads and writes protected by this lock must use the provided // The memory reads and writes protected by this lock must use the provided
// `TryRead()` and `Write()` functions. These functions behave similarly to // `TryRead()` and `Write()` functions. These functions behave similarly to
// `memcpy()`, with one oddity: the protected data must be an array of // `memcpy()`, with one oddity: the protected data must be an array of
// `std::atomic<int64>`. This is to comply with the C++ standard, which // `std::atomic<uint64>`. This is to comply with the C++ standard, which
// considers data races on non-atomic objects to be undefined behavior. See "Can // considers data races on non-atomic objects to be undefined behavior. See "Can
// Seqlocks Get Along With Programming Language Memory Models?"[1] by Hans J. // Seqlocks Get Along With Programming Language Memory Models?"[1] by Hans J.
// Boehm for more details. // Boehm for more details.
......
...@@ -318,6 +318,7 @@ cc_test( ...@@ -318,6 +318,7 @@ cc_test(
":strings", ":strings",
"//absl/base:config", "//absl/base:config",
"//absl/base:raw_logging_internal", "//absl/base:raw_logging_internal",
"//absl/cleanup",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
) )
......
...@@ -952,6 +952,7 @@ absl_cc_test( ...@@ -952,6 +952,7 @@ absl_cc_test(
${ABSL_TEST_COPTS} ${ABSL_TEST_COPTS}
DEPS DEPS
absl::base absl::base
absl::cleanup
absl::config absl::config
absl::cord_internal absl::cord_internal
absl::cord_rep_test_util absl::cord_rep_test_util
......
...@@ -79,8 +79,9 @@ ...@@ -79,8 +79,9 @@
#include "absl/functional/function_ref.h" #include "absl/functional/function_ref.h"
#include "absl/meta/type_traits.h" #include "absl/meta/type_traits.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_btree_reader.h"
#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cord_rep_ring_reader.h"
#include "absl/strings/internal/cordz_functions.h" #include "absl/strings/internal/cordz_functions.h"
#include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_statistics.h" #include "absl/strings/internal/cordz_statistics.h"
...@@ -370,8 +371,8 @@ class Cord { ...@@ -370,8 +371,8 @@ class Cord {
private: private:
using CordRep = absl::cord_internal::CordRep; using CordRep = absl::cord_internal::CordRep;
using CordRepRing = absl::cord_internal::CordRepRing; using CordRepBtree = absl::cord_internal::CordRepBtree;
using CordRepRingReader = absl::cord_internal::CordRepRingReader; using CordRepBtreeReader = absl::cord_internal::CordRepBtreeReader;
// Stack of right children of concat nodes that we have to visit. // Stack of right children of concat nodes that we have to visit.
// Keep this at the end of the structure to avoid cache-thrashing. // Keep this at the end of the structure to avoid cache-thrashing.
...@@ -397,9 +398,9 @@ class Cord { ...@@ -397,9 +398,9 @@ class Cord {
// Stack specific operator++ // Stack specific operator++
ChunkIterator& AdvanceStack(); ChunkIterator& AdvanceStack();
// Ring buffer specific operator++ // Btree specific operator++
ChunkIterator& AdvanceRing(); ChunkIterator& AdvanceBtree();
void AdvanceBytesRing(size_t n); void AdvanceBytesBtree(size_t n);
// Iterates `n` bytes, where `n` is expected to be greater than or equal to // Iterates `n` bytes, where `n` is expected to be greater than or equal to
// `current_chunk_.size()`. // `current_chunk_.size()`.
...@@ -415,8 +416,8 @@ class Cord { ...@@ -415,8 +416,8 @@ class Cord {
// The number of bytes left in the `Cord` over which we are iterating. // The number of bytes left in the `Cord` over which we are iterating.
size_t bytes_remaining_ = 0; size_t bytes_remaining_ = 0;
// Cord reader for ring buffers. Empty if not traversing a ring buffer. // Cord reader for cord btrees. Empty if not traversing a btree.
CordRepRingReader ring_reader_; CordRepBtreeReader btree_reader_;
// See 'Stack' alias definition. // See 'Stack' alias definition.
Stack stack_of_right_children_; Stack stack_of_right_children_;
...@@ -1247,8 +1248,8 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { ...@@ -1247,8 +1248,8 @@ inline bool Cord::StartsWith(absl::string_view rhs) const {
} }
inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) { inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) {
if (tree->tag == cord_internal::RING) { if (tree->tag == cord_internal::BTREE) {
current_chunk_ = ring_reader_.Reset(tree->ring()); current_chunk_ = btree_reader_.Init(tree->btree());
return; return;
} }
...@@ -1271,20 +1272,20 @@ inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) ...@@ -1271,20 +1272,20 @@ inline Cord::ChunkIterator::ChunkIterator(const Cord* cord)
} }
} }
inline Cord::ChunkIterator& Cord::ChunkIterator::AdvanceRing() { inline Cord::ChunkIterator& Cord::ChunkIterator::AdvanceBtree() {
current_chunk_ = ring_reader_.Next(); current_chunk_ = btree_reader_.Next();
return *this; return *this;
} }
inline void Cord::ChunkIterator::AdvanceBytesRing(size_t n) { inline void Cord::ChunkIterator::AdvanceBytesBtree(size_t n) {
assert(n >= current_chunk_.size()); assert(n >= current_chunk_.size());
bytes_remaining_ -= n; bytes_remaining_ -= n;
if (bytes_remaining_) { if (bytes_remaining_) {
if (n == current_chunk_.size()) { if (n == current_chunk_.size()) {
current_chunk_ = ring_reader_.Next(); current_chunk_ = btree_reader_.Next();
} else { } else {
size_t offset = ring_reader_.length() - bytes_remaining_; size_t offset = btree_reader_.length() - bytes_remaining_;
current_chunk_ = ring_reader_.Seek(offset); current_chunk_ = btree_reader_.Seek(offset);
} }
} else { } else {
current_chunk_ = {}; current_chunk_ = {};
...@@ -1297,7 +1298,7 @@ inline Cord::ChunkIterator& Cord::ChunkIterator::operator++() { ...@@ -1297,7 +1298,7 @@ inline Cord::ChunkIterator& Cord::ChunkIterator::operator++() {
assert(bytes_remaining_ >= current_chunk_.size()); assert(bytes_remaining_ >= current_chunk_.size());
bytes_remaining_ -= current_chunk_.size(); bytes_remaining_ -= current_chunk_.size();
if (bytes_remaining_ > 0) { if (bytes_remaining_ > 0) {
return ring_reader_ ? AdvanceRing() : AdvanceStack(); return btree_reader_ ? AdvanceBtree() : AdvanceStack();
} else { } else {
current_chunk_ = {}; current_chunk_ = {};
} }
...@@ -1339,7 +1340,7 @@ inline void Cord::ChunkIterator::AdvanceBytes(size_t n) { ...@@ -1339,7 +1340,7 @@ inline void Cord::ChunkIterator::AdvanceBytes(size_t n) {
if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) { if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) {
RemoveChunkPrefix(n); RemoveChunkPrefix(n);
} else if (n != 0) { } else if (n != 0) {
ring_reader_ ? AdvanceBytesRing(n) : AdvanceBytesSlowPath(n); btree_reader_ ? AdvanceBytesBtree(n) : AdvanceBytesSlowPath(n);
} }
} }
......
...@@ -366,7 +366,7 @@ TEST(Cord, Subcord) { ...@@ -366,7 +366,7 @@ TEST(Cord, Subcord) {
absl::Cord a; absl::Cord a;
AppendWithFragments(s, &rng, &a); AppendWithFragments(s, &rng, &a);
ASSERT_EQ(s.size(), a.size()); ASSERT_EQ(s, std::string(a));
// Check subcords of a, from a variety of interesting points. // Check subcords of a, from a variety of interesting points.
std::set<size_t> positions; std::set<size_t> positions;
......
...@@ -31,6 +31,7 @@ ABSL_CONST_INIT std::atomic<bool> cord_ring_buffer_enabled( ...@@ -31,6 +31,7 @@ ABSL_CONST_INIT std::atomic<bool> cord_ring_buffer_enabled(
kCordEnableRingBufferDefault); kCordEnableRingBufferDefault);
ABSL_CONST_INIT std::atomic<bool> shallow_subcords_enabled( ABSL_CONST_INIT std::atomic<bool> shallow_subcords_enabled(
kCordShallowSubcordsDefault); kCordShallowSubcordsDefault);
ABSL_CONST_INIT std::atomic<bool> cord_btree_exhaustive_validation(false);
void CordRep::Destroy(CordRep* rep) { void CordRep::Destroy(CordRep* rep) {
assert(rep != nullptr); assert(rep != nullptr);
......
...@@ -46,6 +46,12 @@ extern std::atomic<bool> cord_btree_enabled; ...@@ -46,6 +46,12 @@ extern std::atomic<bool> cord_btree_enabled;
extern std::atomic<bool> cord_ring_buffer_enabled; extern std::atomic<bool> cord_ring_buffer_enabled;
extern std::atomic<bool> shallow_subcords_enabled; extern std::atomic<bool> shallow_subcords_enabled;
// `cord_btree_exhaustive_validation` can be set to force exhaustive validation
// in debug assertions, and code that calls `IsValid()` explicitly. By default,
// assertions should be relatively cheap and AssertValid() can easily lead to
// O(n^2) complexity as recursive / full tree validation is O(n).
extern std::atomic<bool> cord_btree_exhaustive_validation;
inline void enable_cord_btree(bool enable) { inline void enable_cord_btree(bool enable) {
cord_btree_enabled.store(enable, std::memory_order_relaxed); cord_btree_enabled.store(enable, std::memory_order_relaxed);
} }
......
...@@ -32,6 +32,8 @@ namespace absl { ...@@ -32,6 +32,8 @@ namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
namespace cord_internal { namespace cord_internal {
constexpr size_t CordRepBtree::kMaxCapacity; // NOLINT: needed for c++ < c++17
namespace { namespace {
using NodeStack = CordRepBtree * [CordRepBtree::kMaxDepth]; using NodeStack = CordRepBtree * [CordRepBtree::kMaxDepth];
...@@ -42,6 +44,10 @@ using CopyResult = CordRepBtree::CopyResult; ...@@ -42,6 +44,10 @@ using CopyResult = CordRepBtree::CopyResult;
constexpr auto kFront = CordRepBtree::kFront; constexpr auto kFront = CordRepBtree::kFront;
constexpr auto kBack = CordRepBtree::kBack; constexpr auto kBack = CordRepBtree::kBack;
inline bool exhaustive_validation() {
return cord_btree_exhaustive_validation.load(std::memory_order_relaxed);
}
// Implementation of the various 'Dump' functions. // Implementation of the various 'Dump' functions.
// Prints the entire tree structure or 'rep'. External callers should // Prints the entire tree structure or 'rep'. External callers should
// not specify 'depth' and leave it to its default (0) value. // not specify 'depth' and leave it to its default (0) value.
...@@ -357,7 +363,7 @@ void CordRepBtree::DestroyNonLeaf(CordRepBtree* tree, size_t begin, ...@@ -357,7 +363,7 @@ void CordRepBtree::DestroyNonLeaf(CordRepBtree* tree, size_t begin,
Delete(tree); Delete(tree);
} }
bool CordRepBtree::IsValid(const CordRepBtree* tree) { bool CordRepBtree::IsValid(const CordRepBtree* tree, bool shallow) {
#define NODE_CHECK_VALID(x) \ #define NODE_CHECK_VALID(x) \
if (!(x)) { \ if (!(x)) { \
ABSL_RAW_LOG(ERROR, "CordRepBtree::CheckValid() FAILED: %s", #x); \ ABSL_RAW_LOG(ERROR, "CordRepBtree::CheckValid() FAILED: %s", #x); \
...@@ -389,9 +395,9 @@ bool CordRepBtree::IsValid(const CordRepBtree* tree) { ...@@ -389,9 +395,9 @@ bool CordRepBtree::IsValid(const CordRepBtree* tree) {
child_length += edge->length; child_length += edge->length;
} }
NODE_CHECK_EQ(child_length, tree->length); NODE_CHECK_EQ(child_length, tree->length);
if (tree->height() > 0) { if ((!shallow || exhaustive_validation()) && tree->height() > 0) {
for (CordRep* edge : tree->Edges()) { for (CordRep* edge : tree->Edges()) {
if (!IsValid(edge->btree())) return false; if (!IsValid(edge->btree(), shallow)) return false;
} }
} }
return true; return true;
...@@ -402,16 +408,17 @@ bool CordRepBtree::IsValid(const CordRepBtree* tree) { ...@@ -402,16 +408,17 @@ bool CordRepBtree::IsValid(const CordRepBtree* tree) {
#ifndef NDEBUG #ifndef NDEBUG
CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree) { CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree, bool shallow) {
if (!IsValid(tree)) { if (!IsValid(tree, shallow)) {
Dump(tree, "CordRepBtree validation failed:", false, std::cout); Dump(tree, "CordRepBtree validation failed:", false, std::cout);
ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED"); ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED");
} }
return tree; return tree;
} }
const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree) { const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree,
if (!IsValid(tree)) { bool shallow) {
if (!IsValid(tree, shallow)) {
Dump(tree, "CordRepBtree validation failed:", false, std::cout); Dump(tree, "CordRepBtree validation failed:", false, std::cout);
ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED"); ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED");
} }
......
...@@ -266,10 +266,28 @@ class CordRepBtree : public CordRep { ...@@ -266,10 +266,28 @@ class CordRepBtree : public CordRep {
// holding a FLAT or EXTERNAL child rep. // holding a FLAT or EXTERNAL child rep.
static bool IsDataEdge(const CordRep* rep); static bool IsDataEdge(const CordRep* rep);
// Diagnostics // Diagnostics: returns true if `tree` is valid and internally consistent.
static bool IsValid(const CordRepBtree* tree); // If `shallow` is false, then the provided top level node and all child nodes
static CordRepBtree* AssertValid(CordRepBtree* tree); // below it are recursively checked. If `shallow` is true, only the provided
static const CordRepBtree* AssertValid(const CordRepBtree* tree); // node in `tree` and the cumulative length, type and height of the direct
// child nodes of `tree` are checked. The value of `shallow` is ignored if the
// internal `cord_btree_exhaustive_validation` diagnostics variable is true,
// in which case the performed validations works as if `shallow` were false.
// This function is intended for debugging and testing purposes only.
static bool IsValid(const CordRepBtree* tree, bool shallow = false);
// Diagnostics: asserts that the provided tree is valid.
// `AssertValid()` performs a shallow validation by default. `shallow` can be
// set to false in which case an exhaustive validation is performed. This
// function is implemented in terms of calling `IsValid()` and asserting the
// return value to be true. See `IsValid()` for more information.
// This function is intended for debugging and testing purposes only.
static CordRepBtree* AssertValid(CordRepBtree* tree, bool shallow = true);
static const CordRepBtree* AssertValid(const CordRepBtree* tree,
bool shallow = true);
// Diagnostics: dump the contents of this tree to `stream`.
// This function is intended for debugging and testing purposes only.
static void Dump(const CordRep* rep, std::ostream& stream); static void Dump(const CordRep* rep, std::ostream& stream);
static void Dump(const CordRep* rep, absl::string_view label, static void Dump(const CordRep* rep, absl::string_view label,
std::ostream& stream); std::ostream& stream);
...@@ -834,11 +852,13 @@ inline CordRepBtree* CordRepBtree::Prepend(CordRepBtree* tree, CordRep* rep) { ...@@ -834,11 +852,13 @@ inline CordRepBtree* CordRepBtree::Prepend(CordRepBtree* tree, CordRep* rep) {
#ifdef NDEBUG #ifdef NDEBUG
inline CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree) { inline CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree,
bool /* shallow */) {
return tree; return tree;
} }
inline const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree) { inline const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree,
bool /* shallow */) {
return tree; return tree;
} }
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "absl/base/config.h" #include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h" #include "absl/base/internal/raw_logging.h"
#include "absl/cleanup/cleanup.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_test_util.h" #include "absl/strings/internal/cord_rep_test_util.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
...@@ -1346,6 +1347,48 @@ TEST(CordRepBtreeTest, AssertValid) { ...@@ -1346,6 +1347,48 @@ TEST(CordRepBtreeTest, AssertValid) {
CordRep::Unref(tree); CordRep::Unref(tree);
} }
TEST(CordRepBtreeTest, CheckAssertValidShallowVsDeep) {
// Restore exhaustive validation on any exit.
const bool exhaustive_validation = cord_btree_exhaustive_validation.load();
auto cleanup = absl::MakeCleanup([exhaustive_validation] {
cord_btree_exhaustive_validation.store(exhaustive_validation);
});
// Create a tree of at least 2 levels, and mess with the original flat, which
// should go undetected in shallow mode as the flat is too far away, but
// should be detected in forced non-shallow mode.
CordRep* flat = MakeFlat("abc");
CordRepBtree* tree = CordRepBtree::Create(flat);
constexpr size_t max_cap = CordRepBtree::kMaxCapacity;
const size_t n = max_cap * max_cap * 2;
for (size_t i = 0; i < n; ++i) {
tree = CordRepBtree::Append(tree, MakeFlat("Hello world"));
}
flat->length = 100;
cord_btree_exhaustive_validation.store(false);
EXPECT_FALSE(CordRepBtree::IsValid(tree));
EXPECT_TRUE(CordRepBtree::IsValid(tree, true));
EXPECT_FALSE(CordRepBtree::IsValid(tree, false));
CordRepBtree::AssertValid(tree);
CordRepBtree::AssertValid(tree, true);
#if defined(GTEST_HAS_DEATH_TEST)
EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree, false), ".*");
#endif
cord_btree_exhaustive_validation.store(true);
EXPECT_FALSE(CordRepBtree::IsValid(tree));
EXPECT_FALSE(CordRepBtree::IsValid(tree, true));
EXPECT_FALSE(CordRepBtree::IsValid(tree, false));
#if defined(GTEST_HAS_DEATH_TEST)
EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree), ".*");
EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree, true), ".*");
#endif
flat->length = 3;
CordRep::Unref(tree);
}
} // namespace } // namespace
} // namespace cord_internal } // namespace cord_internal
ABSL_NAMESPACE_END ABSL_NAMESPACE_END
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "absl/container/inlined_vector.h" #include "absl/container/inlined_vector.h"
#include "absl/debugging/stacktrace.h" #include "absl/debugging/stacktrace.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_handle.h" #include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_statistics.h" #include "absl/strings/internal/cordz_statistics.h"
...@@ -83,19 +84,23 @@ class CordRepAnalyzer { ...@@ -83,19 +84,23 @@ class CordRepAnalyzer {
// Process all top level linear nodes (substrings and flats). // Process all top level linear nodes (substrings and flats).
repref = CountLinearReps(repref, memory_usage_); repref = CountLinearReps(repref, memory_usage_);
// We should have have either a concat or ring node node if not null.
if (repref.rep != nullptr) { if (repref.rep != nullptr) {
assert(repref.rep->tag == RING || repref.rep->tag == CONCAT);
if (repref.rep->tag == RING) { if (repref.rep->tag == RING) {
AnalyzeRing(repref); AnalyzeRing(repref);
} else if (repref.rep->tag == BTREE) {
AnalyzeBtree(repref);
} else if (repref.rep->tag == CONCAT) { } else if (repref.rep->tag == CONCAT) {
AnalyzeConcat(repref); AnalyzeConcat(repref);
} else {
// We should have either a concat, btree, or ring node if not null.
assert(false);
} }
} }
// Adds values to output // Adds values to output
statistics_.estimated_memory_usage += memory_usage_.total; statistics_.estimated_memory_usage += memory_usage_.total;
statistics_.estimated_fair_share_memory_usage += memory_usage_.fair_share; statistics_.estimated_fair_share_memory_usage +=
static_cast<size_t>(memory_usage_.fair_share);
} }
private: private:
...@@ -117,13 +122,13 @@ class CordRepAnalyzer { ...@@ -117,13 +122,13 @@ class CordRepAnalyzer {
// Memory usage values // Memory usage values
struct MemoryUsage { struct MemoryUsage {
size_t total = 0; size_t total = 0;
size_t fair_share = 0; double fair_share = 0.0;
// Adds 'size` memory usage to this class, with a cumulative (recursive) // Adds 'size` memory usage to this class, with a cumulative (recursive)
// reference count of `refcount` // reference count of `refcount`
void Add(size_t size, size_t refcount) { void Add(size_t size, size_t refcount) {
total += size; total += size;
fair_share += size / refcount; fair_share += static_cast<double>(size) / refcount;
} }
}; };
...@@ -215,28 +220,32 @@ class CordRepAnalyzer { ...@@ -215,28 +220,32 @@ class CordRepAnalyzer {
} }
} }
// Counts the provided ring buffer child into `child_usage`. // Analyzes the provided ring.
void CountRingChild(const CordRep* child, MemoryUsage& child_usage) {
RepRef rep{child, static_cast<size_t>(child->refcount.Get())};
rep = CountLinearReps(rep, child_usage);
assert(rep.rep == nullptr);
}
// Analyzes the provided ring. As ring buffers can have many child nodes, the
// effect of rounding errors can become non trivial, so we compute the totals
// first at the ring level, and then divide the fair share of the total
// including children fair share totals.
void AnalyzeRing(RepRef rep) { void AnalyzeRing(RepRef rep) {
statistics_.node_count++; statistics_.node_count++;
statistics_.node_counts.ring++; statistics_.node_counts.ring++;
MemoryUsage ring_usage;
const CordRepRing* ring = rep.rep->ring(); const CordRepRing* ring = rep.rep->ring();
ring_usage.Add(CordRepRing::AllocSize(ring->capacity()), 1); memory_usage_.Add(CordRepRing::AllocSize(ring->capacity()), rep.refcount);
ring->ForEach([&](CordRepRing::index_type pos) { ring->ForEach([&](CordRepRing::index_type pos) {
CountRingChild(ring->entry_child(pos), ring_usage); CountLinearReps(rep.Child(ring->entry_child(pos)), memory_usage_);
}); });
memory_usage_.total += ring_usage.total; }
memory_usage_.fair_share += ring_usage.fair_share / rep.refcount;
// Analyzes the provided btree.
void AnalyzeBtree(RepRef rep) {
statistics_.node_count++;
statistics_.node_counts.btree++;
memory_usage_.Add(sizeof(CordRepBtree), rep.refcount);
const CordRepBtree* tree = rep.rep->btree();
if (tree->height() > 0) {
for (CordRep* edge : tree->Edges()) {
AnalyzeBtree(rep.Child(edge));
}
} else {
for (CordRep* edge : tree->Edges()) {
CountLinearReps(rep.Child(edge), memory_usage_);
}
}
} }
CordzStatistics& statistics_; CordzStatistics& statistics_;
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "absl/base/config.h" #include "absl/base/config.h"
#include "absl/strings/cord.h" #include "absl/strings/cord.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cord_rep_flat.h"
#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_info.h"
...@@ -42,6 +43,8 @@ inline void PrintTo(const CordzStatistics& stats, std::ostream* s) { ...@@ -42,6 +43,8 @@ inline void PrintTo(const CordzStatistics& stats, std::ostream* s) {
namespace { namespace {
using ::testing::Ge;
// Creates a flat of the specified allocated size // Creates a flat of the specified allocated size
CordRepFlat* Flat(size_t size) { CordRepFlat* Flat(size_t size) {
// Round up to a tag size, as we are going to poke an exact tag size back into // Round up to a tag size, as we are going to poke an exact tag size back into
...@@ -134,8 +137,8 @@ size_t SizeOf(const CordRepRing* rep) { ...@@ -134,8 +137,8 @@ size_t SizeOf(const CordRepRing* rep) {
} }
// Computes fair share memory used in a naive 'we dare to recurse' way. // Computes fair share memory used in a naive 'we dare to recurse' way.
size_t FairShare(CordRep* rep, size_t ref = 1) { double FairShareImpl(CordRep* rep, size_t ref) {
size_t self = 0, children = 0; double self = 0.0, children = 0.0;
ref *= rep->refcount.Get(); ref *= rep->refcount.Get();
if (rep->tag >= FLAT) { if (rep->tag >= FLAT) {
self = SizeOf(rep->flat()); self = SizeOf(rep->flat());
...@@ -143,22 +146,32 @@ size_t FairShare(CordRep* rep, size_t ref = 1) { ...@@ -143,22 +146,32 @@ size_t FairShare(CordRep* rep, size_t ref = 1) {
self = SizeOf(rep->external()); self = SizeOf(rep->external());
} else if (rep->tag == SUBSTRING) { } else if (rep->tag == SUBSTRING) {
self = SizeOf(rep->substring()); self = SizeOf(rep->substring());
children = FairShare(rep->substring()->child, ref); children = FairShareImpl(rep->substring()->child, ref);
} else if (rep->tag == BTREE) {
self = SizeOf(rep->btree());
for (CordRep*edge : rep->btree()->Edges()) {
children += FairShareImpl(edge, ref);
}
} else if (rep->tag == RING) { } else if (rep->tag == RING) {
self = SizeOf(rep->ring()); self = SizeOf(rep->ring());
rep->ring()->ForEach([&](CordRepRing::index_type i) { rep->ring()->ForEach([&](CordRepRing::index_type i) {
self += FairShare(rep->ring()->entry_child(i)); self += FairShareImpl(rep->ring()->entry_child(i), 1);
}); });
} else if (rep->tag == CONCAT) { } else if (rep->tag == CONCAT) {
self = SizeOf(rep->concat()); self = SizeOf(rep->concat());
children = FairShare(rep->concat()->left, ref) + children = FairShareImpl(rep->concat()->left, ref) +
FairShare(rep->concat()->right, ref); FairShareImpl(rep->concat()->right, ref);
} else { } else {
assert(false); assert(false);
} }
return self / ref + children; return self / ref + children;
} }
// Returns the fair share memory size from `ShareFhareImpl()` as a size_t.
size_t FairShare(CordRep* rep, size_t ref = 1) {
return static_cast<size_t>(FairShareImpl(rep, ref));
}
// Samples the cord and returns CordzInfo::GetStatistics() // Samples the cord and returns CordzInfo::GetStatistics()
CordzStatistics SampleCord(CordRep* rep) { CordzStatistics SampleCord(CordRep* rep) {
InlineData cord(rep); InlineData cord(rep);
...@@ -191,6 +204,7 @@ MATCHER_P(EqStatistics, stats, "Statistics equal expected values") { ...@@ -191,6 +204,7 @@ MATCHER_P(EqStatistics, stats, "Statistics equal expected values") {
STATS_MATCHER_EXPECT_EQ(node_counts.concat); STATS_MATCHER_EXPECT_EQ(node_counts.concat);
STATS_MATCHER_EXPECT_EQ(node_counts.substring); STATS_MATCHER_EXPECT_EQ(node_counts.substring);
STATS_MATCHER_EXPECT_EQ(node_counts.ring); STATS_MATCHER_EXPECT_EQ(node_counts.ring);
STATS_MATCHER_EXPECT_EQ(node_counts.btree);
STATS_MATCHER_EXPECT_EQ(estimated_memory_usage); STATS_MATCHER_EXPECT_EQ(estimated_memory_usage);
STATS_MATCHER_EXPECT_EQ(estimated_fair_share_memory_usage); STATS_MATCHER_EXPECT_EQ(estimated_fair_share_memory_usage);
...@@ -424,6 +438,103 @@ TEST(CordzInfoStatisticsTest, SharedSubstringRing) { ...@@ -424,6 +438,103 @@ TEST(CordzInfoStatisticsTest, SharedSubstringRing) {
EXPECT_THAT(SampleCord(substring), EqStatistics(expected)); EXPECT_THAT(SampleCord(substring), EqStatistics(expected));
} }
TEST(CordzInfoStatisticsTest, BtreeLeaf) {
ASSERT_THAT(CordRepBtree::kMaxCapacity, Ge(3));
RefHelper ref;
auto* flat1 = Flat(2000);
auto* flat2 = Flat(200);
auto* substr = Substring(flat2);
auto* external = External(3000);
CordRepBtree* tree = CordRepBtree::Create(flat1);
tree = CordRepBtree::Append(tree, substr);
tree = CordRepBtree::Append(tree, external);
size_t flat3_count = CordRepBtree::kMaxCapacity - 3;
size_t flat3_size = 0;
for (size_t i = 0; i < flat3_count; ++i) {
auto* flat3 = Flat(70);
flat3_size += SizeOf(flat3);
tree = CordRepBtree::Append(tree, flat3);
}
ref.NeedsUnref(tree);
CordzStatistics expected;
expected.size = tree->length;
expected.estimated_memory_usage = SizeOf(tree) + SizeOf(flat1) +
SizeOf(flat2) + SizeOf(substr) +
flat3_size + SizeOf(external);
expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
expected.node_count = 1 + 3 + 1 + flat3_count;
expected.node_counts.flat = 2 + flat3_count;
expected.node_counts.flat_128 = flat3_count;
expected.node_counts.flat_256 = 1;
expected.node_counts.external = 1;
expected.node_counts.substring = 1;
expected.node_counts.btree = 1;
EXPECT_THAT(SampleCord(tree), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, BtreeNodeShared) {
RefHelper ref;
static constexpr int leaf_count = 3;
const size_t flat3_count = CordRepBtree::kMaxCapacity - 3;
ASSERT_THAT(flat3_count, Ge(0));
CordRepBtree* tree = nullptr;
size_t mem_size = 0;
for (int i = 0; i < leaf_count; ++i) {
auto* flat1 = ref.Ref(Flat(2000), 9);
mem_size += SizeOf(flat1);
if (i == 0) {
tree = CordRepBtree::Create(flat1);
} else {
tree = CordRepBtree::Append(tree, flat1);
}
auto* flat2 = Flat(200);
auto* substr = Substring(flat2);
mem_size += SizeOf(flat2) + SizeOf(substr);
tree = CordRepBtree::Append(tree, substr);
auto* external = External(30);
mem_size += SizeOf(external);
tree = CordRepBtree::Append(tree, external);
for (size_t i = 0; i < flat3_count; ++i) {
auto* flat3 = Flat(70);
mem_size += SizeOf(flat3);
tree = CordRepBtree::Append(tree, flat3);
}
if (i == 0) {
mem_size += SizeOf(tree);
} else {
mem_size += SizeOf(tree->Edges().back()->btree());
}
}
ref.NeedsUnref(tree);
// Ref count: 2 for top (add 1), 5 for leaf 0 (add 4).
ref.Ref(tree, 1);
ref.Ref(tree->Edges().front(), 4);
CordzStatistics expected;
expected.size = tree->length;
expected.estimated_memory_usage = SizeOf(tree) + mem_size;
expected.estimated_fair_share_memory_usage = FairShare(tree);
expected.node_count = 1 + leaf_count * (1 + 3 + 1 + flat3_count);
expected.node_counts.flat = leaf_count * (2 + flat3_count);
expected.node_counts.flat_128 = leaf_count * flat3_count;
expected.node_counts.flat_256 = leaf_count;
expected.node_counts.external = leaf_count;
expected.node_counts.substring = leaf_count;
expected.node_counts.btree = 1 + leaf_count;
EXPECT_THAT(SampleCord(tree), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, ThreadSafety) { TEST(CordzInfoStatisticsTest, ThreadSafety) {
Notification stop; Notification stop;
static constexpr int kNumThreads = 8; static constexpr int kNumThreads = 8;
...@@ -471,9 +582,15 @@ TEST(CordzInfoStatisticsTest, ThreadSafety) { ...@@ -471,9 +582,15 @@ TEST(CordzInfoStatisticsTest, ThreadSafety) {
CordRep::Unref(cord.as_tree()); CordRep::Unref(cord.as_tree());
cord.set_inline_size(0); cord.set_inline_size(0);
} else { } else {
// 50/50 Ring or Flat coin toss // Coin toss to 25% ring, 25% btree, and 50% flat.
CordRep* rep = Flat(256); CordRep* rep = Flat(256);
rep = (coin_toss(gen) != 0) ? CordRepRing::Create(rep) : rep; if (coin_toss(gen) != 0) {
if (coin_toss(gen) != 0) {
rep = CordRepRing::Create(rep);
} else {
rep = CordRepBtree::Create(rep);
}
}
cord.make_tree(rep); cord.make_tree(rep);
// 50/50 sample // 50/50 sample
......
...@@ -40,6 +40,7 @@ struct CordzStatistics { ...@@ -40,6 +40,7 @@ struct CordzStatistics {
size_t substring = 0; // #substring reps size_t substring = 0; // #substring reps
size_t concat = 0; // #concat reps size_t concat = 0; // #concat reps
size_t ring = 0; // #ring buffer reps size_t ring = 0; // #ring buffer reps
size_t btree = 0; // #btree reps
}; };
// The size of the cord in bytes. This matches the result of Cord::size(). // The size of the cord in bytes. This matches the result of Cord::size().
...@@ -61,6 +62,8 @@ struct CordzStatistics { ...@@ -61,6 +62,8 @@ struct CordzStatistics {
// The total number of nodes referenced by this cord. // The total number of nodes referenced by this cord.
// For ring buffer Cords, this includes the 'ring buffer' node. // For ring buffer Cords, this includes the 'ring buffer' node.
// For btree Cords, this includes all 'CordRepBtree' tree nodes as well as all
// the substring, flat and external nodes referenced by the tree.
// A value of 0 implies the property has not been recorded. // A value of 0 implies the property has not been recorded.
int64_t node_count = 0; int64_t node_count = 0;
......
...@@ -198,9 +198,9 @@ class string_view { ...@@ -198,9 +198,9 @@ class string_view {
// Implicit constructor of a `string_view` from NUL-terminated `str`. When // Implicit constructor of a `string_view` from NUL-terminated `str`. When
// accepting possibly null strings, use `absl::NullSafeStringView(str)` // accepting possibly null strings, use `absl::NullSafeStringView(str)`
// instead (see below). // instead (see below).
// The length check is skipped since it is unnecessary and causes code bloat.
constexpr string_view(const char* str) // NOLINT(runtime/explicit) constexpr string_view(const char* str) // NOLINT(runtime/explicit)
: ptr_(str), : ptr_(str), length_(str ? StrlenInternal(str) : 0) {}
length_(str ? CheckLengthInternal(StrlenInternal(str)) : 0) {}
// Implicit constructor of a `string_view` from a `const char*` and length. // Implicit constructor of a `string_view` from a `const char*` and length.
constexpr string_view(const char* data, size_type len) constexpr string_view(const char* data, size_type len)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment