cord_analysis.cc (6332B)
1 // Copyright 2021 The Abseil Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "absl/strings/cord_analysis.h" 16 17 #include <cassert> 18 #include <cstddef> 19 #include <cstdint> 20 #include <unordered_set> 21 22 #include "absl/base/config.h" 23 #include "absl/base/nullability.h" 24 #include "absl/strings/internal/cord_data_edge.h" 25 #include "absl/strings/internal/cord_internal.h" 26 #include "absl/strings/internal/cord_rep_btree.h" 27 #include "absl/strings/internal/cord_rep_crc.h" 28 29 namespace absl { 30 ABSL_NAMESPACE_BEGIN 31 namespace cord_internal { 32 namespace { 33 34 // Accounting mode for analyzing memory usage. 35 enum class Mode { kFairShare, kTotal, kTotalMorePrecise }; 36 37 // CordRepRef holds a `const CordRep*` reference in rep, and depending on mode, 38 // holds a 'fraction' representing a cumulative inverse refcount weight. 39 template <Mode mode> 40 struct CordRepRef { 41 // Instantiates a CordRepRef instance. 42 explicit CordRepRef(absl::Nonnull<const CordRep*> r) : rep(r) {} 43 44 // Creates a child reference holding the provided child. 45 // Overloaded to add cumulative reference count for kFairShare. 46 CordRepRef Child(absl::Nonnull<const CordRep*> child) const { 47 return CordRepRef(child); 48 } 49 50 absl::Nonnull<const CordRep*> rep; 51 }; 52 53 // RawUsage holds the computed total number of bytes. 54 template <Mode mode> 55 struct RawUsage { 56 size_t total = 0; 57 58 // Add 'size' to total, ignoring the CordRepRef argument. 59 void Add(size_t size, CordRepRef<mode>) { total += size; } 60 }; 61 62 // Overloaded representation of RawUsage that tracks the set of objects 63 // counted, and avoids double-counting objects referenced more than once 64 // by the same Cord. 65 template <> 66 struct RawUsage<Mode::kTotalMorePrecise> { 67 size_t total = 0; 68 // TODO(b/289250880): Replace this with a flat_hash_set. 69 std::unordered_set<absl::Nonnull<const CordRep*>> counted; 70 71 void Add(size_t size, CordRepRef<Mode::kTotalMorePrecise> repref) { 72 if (counted.insert(repref.rep).second) { 73 total += size; 74 } 75 } 76 }; 77 78 // Returns n / refcount avoiding a div for the common refcount == 1. 79 template <typename refcount_t> 80 double MaybeDiv(double d, refcount_t refcount) { 81 return refcount == 1 ? d : d / refcount; 82 } 83 84 // Overloaded 'kFairShare' specialization for CordRepRef. This class holds a 85 // `fraction` value which represents a cumulative inverse refcount weight. 86 // For example, a top node with a reference count of 2 will have a fraction 87 // value of 1/2 = 0.5, representing the 'fair share' of memory it references. 88 // A node below such a node with a reference count of 5 then has a fraction of 89 // 0.5 / 5 = 0.1 representing the fair share of memory below that node, etc. 90 template <> 91 struct CordRepRef<Mode::kFairShare> { 92 // Creates a CordRepRef with the provided rep and top (parent) fraction. 93 explicit CordRepRef(absl::Nonnull<const CordRep*> r, double frac = 1.0) 94 : rep(r), fraction(MaybeDiv(frac, r->refcount.Get())) {} 95 96 // Returns a CordRepRef with a fraction of `this->fraction / child.refcount` 97 CordRepRef Child(absl::Nonnull<const CordRep*> child) const { 98 return CordRepRef(child, fraction); 99 } 100 101 absl::Nonnull<const CordRep*> rep; 102 double fraction; 103 }; 104 105 // Overloaded 'kFairShare' specialization for RawUsage 106 template <> 107 struct RawUsage<Mode::kFairShare> { 108 double total = 0; 109 110 // Adds `size` multiplied by `rep.fraction` to the total size. 111 void Add(size_t size, CordRepRef<Mode::kFairShare> rep) { 112 total += static_cast<double>(size) * rep.fraction; 113 } 114 }; 115 116 // Computes the estimated memory size of the provided data edge. 117 // External reps are assumed 'heap allocated at their exact size'. 118 template <Mode mode> 119 void AnalyzeDataEdge(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) { 120 assert(IsDataEdge(rep.rep)); 121 122 // Consume all substrings 123 if (rep.rep->tag == SUBSTRING) { 124 raw_usage.Add(sizeof(CordRepSubstring), rep); 125 rep = rep.Child(rep.rep->substring()->child); 126 } 127 128 // Consume FLAT / EXTERNAL 129 const size_t size = 130 rep.rep->tag >= FLAT 131 ? rep.rep->flat()->AllocatedSize() 132 : rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>); 133 raw_usage.Add(size, rep); 134 } 135 136 // Computes the memory size of the provided Btree tree. 137 template <Mode mode> 138 void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) { 139 raw_usage.Add(sizeof(CordRepBtree), rep); 140 const CordRepBtree* tree = rep.rep->btree(); 141 if (tree->height() > 0) { 142 for (CordRep* edge : tree->Edges()) { 143 AnalyzeBtree(rep.Child(edge), raw_usage); 144 } 145 } else { 146 for (CordRep* edge : tree->Edges()) { 147 AnalyzeDataEdge(rep.Child(edge), raw_usage); 148 } 149 } 150 } 151 152 template <Mode mode> 153 size_t GetEstimatedUsage(absl::Nonnull<const CordRep*> rep) { 154 // Zero initialized memory usage totals. 155 RawUsage<mode> raw_usage; 156 157 // Capture top level node and refcount into a CordRepRef. 158 CordRepRef<mode> repref(rep); 159 160 // Consume the top level CRC node if present. 161 if (repref.rep->tag == CRC) { 162 raw_usage.Add(sizeof(CordRepCrc), repref); 163 if (repref.rep->crc()->child == nullptr) { 164 return static_cast<size_t>(raw_usage.total); 165 } 166 repref = repref.Child(repref.rep->crc()->child); 167 } 168 169 if (IsDataEdge(repref.rep)) { 170 AnalyzeDataEdge(repref, raw_usage); 171 } else if (repref.rep->tag == BTREE) { 172 AnalyzeBtree(repref, raw_usage); 173 } else { 174 assert(false); 175 } 176 177 return static_cast<size_t>(raw_usage.total); 178 } 179 180 } // namespace 181 182 size_t GetEstimatedMemoryUsage(absl::Nonnull<const CordRep*> rep) { 183 return GetEstimatedUsage<Mode::kTotal>(rep); 184 } 185 186 size_t GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep*> rep) { 187 return GetEstimatedUsage<Mode::kFairShare>(rep); 188 } 189 190 size_t GetMorePreciseMemoryUsage(absl::Nonnull<const CordRep*> rep) { 191 return GetEstimatedUsage<Mode::kTotalMorePrecise>(rep); 192 } 193 194 } // namespace cord_internal 195 ABSL_NAMESPACE_END 196 } // namespace absl