xref: /aosp_15_r20/external/abseil-cpp/absl/strings/cord_analysis.cc (revision 9356374a3709195abf420251b3e825997ff56c0f)
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.
CordRepRefabsl::cord_internal::__anonee7381ff0111::CordRepRef42   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.
Childabsl::cord_internal::__anonee7381ff0111::CordRepRef46   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.
Addabsl::cord_internal::__anonee7381ff0111::RawUsage59   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 
Addabsl::cord_internal::__anonee7381ff0111::RawUsage71   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>
MaybeDiv(double d,refcount_t refcount)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.
CordRepRefabsl::cord_internal::__anonee7381ff0111::CordRepRef93   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`
Childabsl::cord_internal::__anonee7381ff0111::CordRepRef97   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.
Addabsl::cord_internal::__anonee7381ff0111::RawUsage111   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>
AnalyzeDataEdge(CordRepRef<mode> rep,RawUsage<mode> & raw_usage)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>
AnalyzeBtree(CordRepRef<mode> rep,RawUsage<mode> & raw_usage)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>
GetEstimatedUsage(absl::Nonnull<const CordRep * > rep)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 
GetEstimatedMemoryUsage(absl::Nonnull<const CordRep * > rep)182 size_t GetEstimatedMemoryUsage(absl::Nonnull<const CordRep*> rep) {
183   return GetEstimatedUsage<Mode::kTotal>(rep);
184 }
185 
GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep * > rep)186 size_t GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep*> rep) {
187   return GetEstimatedUsage<Mode::kFairShare>(rep);
188 }
189 
GetMorePreciseMemoryUsage(absl::Nonnull<const CordRep * > rep)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
197