// Copyright 2021 The Abseil Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/container/inlined_vector.h" #include "absl/strings/cord_analysis.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" #include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cord_rep_ring.h" // #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/functional/function_ref.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { namespace { // Accounting mode for analyzing memory usage. enum class Mode { kTotal, kFairShare }; // CordRepRef holds a `const CordRep*` reference in rep, and depending on mode, // holds a 'fraction' representing a cumulative inverse refcount weight. template struct CordRepRef { // Instantiates a CordRepRef instance. explicit CordRepRef(const CordRep* r) : rep(r) {} // Creates a child reference holding the provided child. // Overloaded to add cumulative reference count for kFairShare. CordRepRef Child(const CordRep* child) const { return CordRepRef(child); } const CordRep* rep; }; // RawUsage holds the computed total number of bytes. template struct RawUsage { size_t total = 0; // Add 'size' to total, ignoring the CordRepRef argument. void Add(size_t size, CordRepRef) { total += size; } }; // Returns n / refcount avoiding a div for the common refcount == 1. template double MaybeDiv(double d, refcount_t refcount) { return refcount == 1 ? d : d / refcount; } // Overloaded 'kFairShare' specialization for CordRepRef. This class holds a // `fraction` value which represents a cumulative inverse refcount weight. // For example, a top node with a reference count of 2 will have a fraction // value of 1/2 = 0.5, representing the 'fair share' of memory it references. // A node below such a node with a reference count of 5 then has a fraction of // 0.5 / 5 = 0.1 representing the fair share of memory below that node, etc. template <> struct CordRepRef { // Creates a CordRepRef with the provided rep and top (parent) fraction. explicit CordRepRef(const CordRep* r, double frac = 1.0) : rep(r), fraction(MaybeDiv(frac, r->refcount.Get())) {} // Returns a CordRepRef with a fraction of `this->fraction / child.refcount` CordRepRef Child(const CordRep* child) const { return CordRepRef(child, fraction); } const CordRep* rep; double fraction; }; // Overloaded 'kFairShare' specialization for RawUsage template <> struct RawUsage { double total = 0; // Adds `size` multiplied by `rep.fraction` to the total size. void Add(size_t size, CordRepRef rep) { total += static_cast(size) * rep.fraction; } }; // Returns true if the provided rep is a valid data edge. bool IsDataEdge(const CordRep* rep) { // The fast path is that `rep` is an EXTERNAL or FLAT node, making the below // if a single, well predicted branch. We then repeat the FLAT or EXTERNAL // check in the slow path the SUBSTRING check to optimize for the hot path. if (rep->tag == EXTERNAL || rep->tag >= FLAT) return true; if (rep->tag == SUBSTRING) rep = rep->substring()->child; return rep->tag == EXTERNAL || rep->tag >= FLAT; } // Computes the estimated memory size of the provided data edge. // External reps are assumed 'heap allocated at their exact size'. template void AnalyzeDataEdge(CordRepRef rep, RawUsage& raw_usage) { assert(IsDataEdge(rep.rep)); // Consume all substrings if (rep.rep->tag == SUBSTRING) { raw_usage.Add(sizeof(CordRepSubstring), rep); rep = rep.Child(rep.rep->substring()->child); } // Consume FLAT / EXTERNAL const size_t size = rep.rep->tag >= FLAT ? rep.rep->flat()->AllocatedSize() : rep.rep->length + sizeof(CordRepExternalImpl); raw_usage.Add(size, rep); } // Computes the memory size of the provided Concat tree. template void AnalyzeConcat(CordRepRef rep, RawUsage& raw_usage) { absl::InlinedVector, 47> pending; while (rep.rep != nullptr) { const CordRepConcat* concat = rep.rep->concat(); CordRepRef left = rep.Child(concat->left); CordRepRef right = rep.Child(concat->right); raw_usage.Add(sizeof(CordRepConcat), rep); switch ((IsDataEdge(left.rep) ? 1 : 0) | (IsDataEdge(right.rep) ? 2 : 0)) { case 0: // neither left or right are data edges rep = left; pending.push_back(right); break; case 1: // only left is a data edge AnalyzeDataEdge(left, raw_usage); rep = right; break; case 2: // only right is a data edge AnalyzeDataEdge(right, raw_usage); rep = left; break; case 3: // left and right are data edges AnalyzeDataEdge(right, raw_usage); AnalyzeDataEdge(left, raw_usage); if (!pending.empty()) { rep = pending.back(); pending.pop_back(); } else { rep.rep = nullptr; } break; } } } // Computes the memory size of the provided Ring tree. template void AnalyzeRing(CordRepRef rep, RawUsage& raw_usage) { const CordRepRing* ring = rep.rep->ring(); raw_usage.Add(CordRepRing::AllocSize(ring->capacity()), rep); ring->ForEach([&](CordRepRing::index_type pos) { AnalyzeDataEdge(rep.Child(ring->entry_child(pos)), raw_usage); }); } // Computes the memory size of the provided Btree tree. template void AnalyzeBtree(CordRepRef rep, RawUsage& raw_usage) { raw_usage.Add(sizeof(CordRepBtree), rep); const CordRepBtree* tree = rep.rep->btree(); if (tree->height() > 0) { for (CordRep* edge : tree->Edges()) { AnalyzeBtree(rep.Child(edge), raw_usage); } } else { for (CordRep* edge : tree->Edges()) { AnalyzeDataEdge(rep.Child(edge), raw_usage); } } } template size_t GetEstimatedUsage(const CordRep* rep) { // Zero initialized memory usage totals. RawUsage raw_usage; // Capture top level node and refcount into a CordRepRef. CordRepRef repref(rep); // Consume the top level CRC node if present. if (repref.rep->tag == CRC) { raw_usage.Add(sizeof(CordRepCrc), repref); repref = repref.Child(repref.rep->crc()->child); } if (IsDataEdge(repref.rep)) { AnalyzeDataEdge(repref, raw_usage); } else if (repref.rep->tag == BTREE) { AnalyzeBtree(repref, raw_usage); } else if (repref.rep->IsConcat()) { AnalyzeConcat(repref, raw_usage); } else if (repref.rep->tag == RING) { AnalyzeRing(repref, raw_usage); } else { assert(false); } return static_cast(raw_usage.total); } } // namespace size_t GetEstimatedMemoryUsage(const CordRep* rep) { return GetEstimatedUsage(rep); } size_t GetEstimatedFairShareMemoryUsage(const CordRep* rep) { return GetEstimatedUsage(rep); } } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl