• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <cstddef>
18 #include <cstdint>
19 #include <unordered_set>
20 
21 #include "absl/base/attributes.h"
22 #include "absl/base/config.h"
23 #include "absl/container/inlined_vector.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 #include "absl/strings/internal/cord_rep_flat.h"
29 #include "absl/strings/internal/cord_rep_ring.h"
30 //
31 #include "absl/base/macros.h"
32 #include "absl/base/port.h"
33 #include "absl/functional/function_ref.h"
34 
35 namespace absl {
36 ABSL_NAMESPACE_BEGIN
37 namespace cord_internal {
38 namespace {
39 
40 // Accounting mode for analyzing memory usage.
41 enum class Mode { kFairShare, kTotal, kTotalMorePrecise };
42 
43 // CordRepRef holds a `const CordRep*` reference in rep, and depending on mode,
44 // holds a 'fraction' representing a cumulative inverse refcount weight.
45 template <Mode mode>
46 struct CordRepRef {
47   // Instantiates a CordRepRef instance.
CordRepRefabsl::cord_internal::__anon80e7f42d0111::CordRepRef48   explicit CordRepRef(const CordRep* r) : rep(r) {}
49 
50   // Creates a child reference holding the provided child.
51   // Overloaded to add cumulative reference count for kFairShare.
Childabsl::cord_internal::__anon80e7f42d0111::CordRepRef52   CordRepRef Child(const CordRep* child) const { return CordRepRef(child); }
53 
54   const CordRep* rep;
55 };
56 
57 // RawUsage holds the computed total number of bytes.
58 template <Mode mode>
59 struct RawUsage {
60   size_t total = 0;
61 
62   // Add 'size' to total, ignoring the CordRepRef argument.
Addabsl::cord_internal::__anon80e7f42d0111::RawUsage63   void Add(size_t size, CordRepRef<mode>) { total += size; }
64 };
65 
66 // Overloaded representation of RawUsage that tracks the set of objects
67 // counted, and avoids double-counting objects referenced more than once
68 // by the same Cord.
69 template <>
70 struct RawUsage<Mode::kTotalMorePrecise> {
71   size_t total = 0;
72   // TODO(b/289250880): Replace this with a flat_hash_set.
73   std::unordered_set<const CordRep*> counted;
74 
Addabsl::cord_internal::__anon80e7f42d0111::RawUsage75   void Add(size_t size, CordRepRef<Mode::kTotalMorePrecise> repref) {
76     if (counted.find(repref.rep) == counted.end()) {
77       counted.insert(repref.rep);
78       total += size;
79     }
80   }
81 };
82 
83 // Returns n / refcount avoiding a div for the common refcount == 1.
84 template <typename refcount_t>
MaybeDiv(double d,refcount_t refcount)85 double MaybeDiv(double d, refcount_t refcount) {
86   return refcount == 1 ? d : d / refcount;
87 }
88 
89 // Overloaded 'kFairShare' specialization for CordRepRef. This class holds a
90 // `fraction` value which represents a cumulative inverse refcount weight.
91 // For example, a top node with a reference count of 2 will have a fraction
92 // value of 1/2 = 0.5, representing the 'fair share' of memory it references.
93 // A node below such a node with a reference count of 5 then has a fraction of
94 // 0.5 / 5 = 0.1 representing the fair share of memory below that node, etc.
95 template <>
96 struct CordRepRef<Mode::kFairShare> {
97   // Creates a CordRepRef with the provided rep and top (parent) fraction.
CordRepRefabsl::cord_internal::__anon80e7f42d0111::CordRepRef98   explicit CordRepRef(const CordRep* r, double frac = 1.0)
99       : rep(r), fraction(MaybeDiv(frac, r->refcount.Get())) {}
100 
101   // Returns a CordRepRef with a fraction of `this->fraction / child.refcount`
Childabsl::cord_internal::__anon80e7f42d0111::CordRepRef102   CordRepRef Child(const CordRep* child) const {
103     return CordRepRef(child, fraction);
104   }
105 
106   const CordRep* rep;
107   double fraction;
108 };
109 
110 // Overloaded 'kFairShare' specialization for RawUsage
111 template <>
112 struct RawUsage<Mode::kFairShare> {
113   double total = 0;
114 
115   // Adds `size` multiplied by `rep.fraction` to the total size.
Addabsl::cord_internal::__anon80e7f42d0111::RawUsage116   void Add(size_t size, CordRepRef<Mode::kFairShare> rep) {
117     total += static_cast<double>(size) * rep.fraction;
118   }
119 };
120 
121 // Computes the estimated memory size of the provided data edge.
122 // External reps are assumed 'heap allocated at their exact size'.
123 template <Mode mode>
AnalyzeDataEdge(CordRepRef<mode> rep,RawUsage<mode> & raw_usage)124 void AnalyzeDataEdge(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
125   assert(IsDataEdge(rep.rep));
126 
127   // Consume all substrings
128   if (rep.rep->tag == SUBSTRING) {
129     raw_usage.Add(sizeof(CordRepSubstring), rep);
130     rep = rep.Child(rep.rep->substring()->child);
131   }
132 
133   // Consume FLAT / EXTERNAL
134   const size_t size =
135       rep.rep->tag >= FLAT
136           ? rep.rep->flat()->AllocatedSize()
137           : rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
138   raw_usage.Add(size, rep);
139 }
140 
141 // Computes the memory size of the provided Ring tree.
142 template <Mode mode>
AnalyzeRing(CordRepRef<mode> rep,RawUsage<mode> & raw_usage)143 void AnalyzeRing(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
144   const CordRepRing* ring = rep.rep->ring();
145   raw_usage.Add(CordRepRing::AllocSize(ring->capacity()), rep);
146   ring->ForEach([&](CordRepRing::index_type pos) {
147     AnalyzeDataEdge(rep.Child(ring->entry_child(pos)), raw_usage);
148   });
149 }
150 
151 // Computes the memory size of the provided Btree tree.
152 template <Mode mode>
AnalyzeBtree(CordRepRef<mode> rep,RawUsage<mode> & raw_usage)153 void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
154   raw_usage.Add(sizeof(CordRepBtree), rep);
155   const CordRepBtree* tree = rep.rep->btree();
156   if (tree->height() > 0) {
157     for (CordRep* edge : tree->Edges()) {
158       AnalyzeBtree(rep.Child(edge), raw_usage);
159     }
160   } else {
161     for (CordRep* edge : tree->Edges()) {
162       AnalyzeDataEdge(rep.Child(edge), raw_usage);
163     }
164   }
165 }
166 
167 template <Mode mode>
GetEstimatedUsage(const CordRep * rep)168 size_t GetEstimatedUsage(const CordRep* rep) {
169   // Zero initialized memory usage totals.
170   RawUsage<mode> raw_usage;
171 
172   // Capture top level node and refcount into a CordRepRef.
173   CordRepRef<mode> repref(rep);
174 
175   // Consume the top level CRC node if present.
176   if (repref.rep->tag == CRC) {
177     raw_usage.Add(sizeof(CordRepCrc), repref);
178     repref = repref.Child(repref.rep->crc()->child);
179   }
180 
181   if (IsDataEdge(repref.rep)) {
182     AnalyzeDataEdge(repref, raw_usage);
183   } else if (repref.rep->tag == BTREE) {
184     AnalyzeBtree(repref, raw_usage);
185   } else if (repref.rep->tag == RING) {
186     AnalyzeRing(repref, raw_usage);
187   } else {
188     assert(false);
189   }
190 
191   return static_cast<size_t>(raw_usage.total);
192 }
193 
194 }  // namespace
195 
GetEstimatedMemoryUsage(const CordRep * rep)196 size_t GetEstimatedMemoryUsage(const CordRep* rep) {
197   return GetEstimatedUsage<Mode::kTotal>(rep);
198 }
199 
GetEstimatedFairShareMemoryUsage(const CordRep * rep)200 size_t GetEstimatedFairShareMemoryUsage(const CordRep* rep) {
201   return GetEstimatedUsage<Mode::kFairShare>(rep);
202 }
203 
GetMorePreciseMemoryUsage(const CordRep * rep)204 size_t GetMorePreciseMemoryUsage(const CordRep* rep) {
205   return GetEstimatedUsage<Mode::kTotalMorePrecise>(rep);
206 }
207 
208 }  // namespace cord_internal
209 ABSL_NAMESPACE_END
210 }  // namespace absl
211