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 // -----------------------------------------------------------------------------
16 // File: cord_buffer.h
17 // -----------------------------------------------------------------------------
18 //
19 // This file defines an `absl::CordBuffer` data structure to hold data for
20 // eventual inclusion within an existing `Cord` data structure. Cord buffers are
21 // useful for building large Cords that may require custom allocation of its
22 // associated memory.
23 //
24 #ifndef ABSL_STRINGS_CORD_BUFFER_H_
25 #define ABSL_STRINGS_CORD_BUFFER_H_
26
27 #include <algorithm>
28 #include <cassert>
29 #include <cstddef>
30 #include <cstdint>
31 #include <memory>
32 #include <utility>
33
34 #include "absl/base/config.h"
35 #include "absl/base/macros.h"
36 #include "absl/numeric/bits.h"
37 #include "absl/strings/internal/cord_internal.h"
38 #include "absl/strings/internal/cord_rep_flat.h"
39 #include "absl/types/span.h"
40
41 namespace absl {
42 ABSL_NAMESPACE_BEGIN
43
44 class Cord;
45 class CordBufferTestPeer;
46
47 // CordBuffer
48 //
49 // CordBuffer manages memory buffers for purposes such as zero-copy APIs as well
50 // as applications building cords with large data requiring granular control
51 // over the allocation and size of cord data. For example, a function creating
52 // a cord of random data could use a CordBuffer as follows:
53 //
54 // absl::Cord CreateRandomCord(size_t length) {
55 // absl::Cord cord;
56 // while (length > 0) {
57 // CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(length);
58 // absl::Span<char> data = buffer.available_up_to(length);
59 // FillRandomValues(data.data(), data.size());
60 // buffer.IncreaseLengthBy(data.size());
61 // cord.Append(std::move(buffer));
62 // length -= data.size();
63 // }
64 // return cord;
65 // }
66 //
67 // CordBuffer instances are by default limited to a capacity of `kDefaultLimit`
68 // bytes. `kDefaultLimit` is currently just under 4KiB, but this default may
69 // change in the future and/or for specific architectures. The default limit is
70 // aimed to provide a good trade-off between performance and memory overhead.
71 // Smaller buffers typically incur more compute cost while larger buffers are
72 // more CPU efficient but create significant memory overhead because of such
73 // allocations being less granular. Using larger buffers may also increase the
74 // risk of memory fragmentation.
75 //
76 // Applications create a buffer using one of the `CreateWithDefaultLimit()` or
77 // `CreateWithCustomLimit()` methods. The returned instance will have a non-zero
78 // capacity and a zero length. Applications use the `data()` method to set the
79 // contents of the managed memory, and once done filling the buffer, use the
80 // `IncreaseLengthBy()` or 'SetLength()' method to specify the length of the
81 // initialized data before adding the buffer to a Cord.
82 //
83 // The `CreateWithCustomLimit()` method is intended for applications needing
84 // larger buffers than the default memory limit, allowing the allocation of up
85 // to a capacity of `kCustomLimit` bytes minus some minimum internal overhead.
86 // The usage of `CreateWithCustomLimit()` should be limited to only those use
87 // cases where the distribution of the input is relatively well known, and/or
88 // where the trade-off between the efficiency gains outweigh the risk of memory
89 // fragmentation. See the documentation for `CreateWithCustomLimit()` for more
90 // information on using larger custom limits.
91 //
92 // The capacity of a `CordBuffer` returned by one of the `Create` methods may
93 // be larger than the requested capacity due to rounding, alignment and
94 // granularity of the memory allocator. Applications should use the `capacity`
95 // method to obtain the effective capacity of the returned instance as
96 // demonstrated in the provided example above.
97 //
98 // CordBuffer is a move-only class. All references into the managed memory are
99 // invalidated when an instance is moved into either another CordBuffer instance
100 // or a Cord. Writing to a location obtained by a previous call to `data()`
101 // after an instance was moved will lead to undefined behavior.
102 //
103 // A `moved from` CordBuffer instance will have a valid, but empty state.
104 // CordBuffer is thread compatible.
105 class CordBuffer {
106 public:
107 // kDefaultLimit
108 //
109 // Default capacity limits of allocated CordBuffers.
110 // See the class comments for more information on allocation limits.
111 static constexpr size_t kDefaultLimit = cord_internal::kMaxFlatLength;
112
113 // kCustomLimit
114 //
115 // Maximum size for CreateWithCustomLimit() allocated buffers.
116 // Note that the effective capacity may be slightly less
117 // because of internal overhead of internal cord buffers.
118 static constexpr size_t kCustomLimit = 64U << 10;
119
120 // Constructors, Destructors and Assignment Operators
121
122 // Creates an empty CordBuffer.
123 CordBuffer() = default;
124
125 // Destroys this CordBuffer instance and, if not empty, releases any memory
126 // managed by this instance, invalidating previously returned references.
127 ~CordBuffer();
128
129 // CordBuffer is move-only
130 CordBuffer(CordBuffer&& rhs) noexcept;
131 CordBuffer& operator=(CordBuffer&&) noexcept;
132 CordBuffer(const CordBuffer&) = delete;
133 CordBuffer& operator=(const CordBuffer&) = delete;
134
135 // CordBuffer::MaximumPayload()
136 //
137 // Returns the guaranteed maximum payload for a CordBuffer returned by the
138 // `CreateWithDefaultLimit()` method. While small, each internal buffer inside
139 // a Cord incurs an overhead to manage the length, type and reference count
140 // for the buffer managed inside the cord tree. Applications can use this
141 // method to get approximate number of buffers required for a given byte
142 // size, etc.
143 //
144 // For example:
145 // const size_t payload = absl::CordBuffer::MaximumPayload();
146 // const size_t buffer_count = (total_size + payload - 1) / payload;
147 // buffers.reserve(buffer_count);
148 static constexpr size_t MaximumPayload();
149
150 // Overload to the above `MaximumPayload()` except that it returns the
151 // maximum payload for a CordBuffer returned by the `CreateWithCustomLimit()`
152 // method given the provided `block_size`.
153 static constexpr size_t MaximumPayload(size_t block_size);
154
155 // CordBuffer::CreateWithDefaultLimit()
156 //
157 // Creates a CordBuffer instance of the desired `capacity`, capped at the
158 // default limit `kDefaultLimit`. The returned buffer has a guaranteed
159 // capacity of at least `min(kDefaultLimit, capacity)`. See the class comments
160 // for more information on buffer capacities and intended usage.
161 static CordBuffer CreateWithDefaultLimit(size_t capacity);
162
163
164 // CordBuffer::CreateWithCustomLimit()
165 //
166 // Creates a CordBuffer instance of the desired `capacity` rounded to an
167 // appropriate power of 2 size less than, or equal to `block_size`.
168 // Requires `block_size` to be a power of 2.
169 //
170 // If `capacity` is less than or equal to `kDefaultLimit`, then this method
171 // behaves identical to `CreateWithDefaultLimit`, which means that the caller
172 // is guaranteed to get a buffer of at least the requested capacity.
173 //
174 // If `capacity` is greater than or equal to `block_size`, then this method
175 // returns a buffer with an `allocated size` of `block_size` bytes. Otherwise,
176 // this methods returns a buffer with a suitable smaller power of 2 block size
177 // to satisfy the request. The actual size depends on a number of factors, and
178 // is typically (but not necessarily) the highest or second highest power of 2
179 // value less than or equal to `capacity`.
180 //
181 // The 'allocated size' includes a small amount of overhead required for
182 // internal state, which is currently 13 bytes on 64-bit platforms. For
183 // example: a buffer created with `block_size` and `capacity' set to 8KiB
184 // will have an allocated size of 8KiB, and an effective internal `capacity`
185 // of 8KiB - 13 = 8179 bytes.
186 //
187 // To demonstrate this in practice, let's assume we want to read data from
188 // somewhat larger files using approximately 64KiB buffers:
189 //
190 // absl::Cord ReadFromFile(int fd, size_t n) {
191 // absl::Cord cord;
192 // while (n > 0) {
193 // CordBuffer buffer = CordBuffer::CreateWithCustomLimit(64 << 10, n);
194 // absl::Span<char> data = buffer.available_up_to(n);
195 // ReadFileDataOrDie(fd, data.data(), data.size());
196 // buffer.IncreaseLengthBy(data.size());
197 // cord.Append(std::move(buffer));
198 // n -= data.size();
199 // }
200 // return cord;
201 // }
202 //
203 // If we'd use this function to read a file of 659KiB, we may get the
204 // following pattern of allocated cord buffer sizes:
205 //
206 // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523)
207 // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523)
208 // ...
209 // CreateWithCustomLimit(64KiB, 19586) --> ~16KiB (16371)
210 // CreateWithCustomLimit(64KiB, 3215) --> 3215 (at least 3215)
211 //
212 // The reason the method returns a 16K buffer instead of a roughly 19K buffer
213 // is to reduce memory overhead and fragmentation risks. Using carefully
214 // chosen power of 2 values reduces the entropy of allocated memory sizes.
215 //
216 // Additionally, let's assume we'd use the above function on files that are
217 // generally smaller than 64K. If we'd use 'precise' sized buffers for such
218 // files, than we'd get a very wide distribution of allocated memory sizes
219 // rounded to 4K page sizes, and we'd end up with a lot of unused capacity.
220 //
221 // In general, application should only use custom sizes if the data they are
222 // consuming or storing is expected to be many times the chosen block size,
223 // and be based on objective data and performance metrics. For example, a
224 // compress function may work faster and consume less CPU when using larger
225 // buffers. Such an application should pick a size offering a reasonable
226 // trade-off between expected data size, compute savings with larger buffers,
227 // and the cost or fragmentation effect of larger buffers.
228 // Applications must pick a reasonable spot on that curve, and make sure their
229 // data meets their expectations in size distributions such as "mostly large".
230 static CordBuffer CreateWithCustomLimit(size_t block_size, size_t capacity);
231
232 // CordBuffer::available()
233 //
234 // Returns the span delineating the available capacity in this buffer
235 // which is defined as `{ data() + length(), capacity() - length() }`.
236 absl::Span<char> available();
237
238 // CordBuffer::available_up_to()
239 //
240 // Returns the span delineating the available capacity in this buffer limited
241 // to `size` bytes. This is equivalent to `available().subspan(0, size)`.
242 absl::Span<char> available_up_to(size_t size);
243
244 // CordBuffer::data()
245 //
246 // Returns a non-null reference to the data managed by this instance.
247 // Applications are allowed to write up to `capacity` bytes of instance data.
248 // CordBuffer data is uninitialized by default. Reading data from an instance
249 // that has not yet been initialized will lead to undefined behavior.
250 char* data();
251 const char* data() const;
252
253 // CordBuffer::length()
254 //
255 // Returns the length of this instance. The default length of a CordBuffer is
256 // 0, indicating an 'empty' CordBuffer. Applications must specify the length
257 // of the data in a CordBuffer before adding it to a Cord.
258 size_t length() const;
259
260 // CordBuffer::capacity()
261 //
262 // Returns the capacity of this instance. All instances have a non-zero
263 // capacity: default and `moved from` instances have a small internal buffer.
264 size_t capacity() const;
265
266 // CordBuffer::IncreaseLengthBy()
267 //
268 // Increases the length of this buffer by the specified 'n' bytes.
269 // Applications must make sure all data in this buffer up to the new length
270 // has been initialized before adding a CordBuffer to a Cord: failure to do so
271 // will lead to undefined behavior. Requires `length() + n <= capacity()`.
272 // Typically, applications will use 'available_up_to()` to get a span of the
273 // desired capacity, and use `span.size()` to increase the length as in:
274 // absl::Span<char> span = buffer.available_up_to(desired);
275 // buffer.IncreaseLengthBy(span.size());
276 // memcpy(span.data(), src, span.size());
277 // etc...
278 void IncreaseLengthBy(size_t n);
279
280 // CordBuffer::SetLength()
281 //
282 // Sets the data length of this instance. Applications must make sure all data
283 // of the specified length has been initialized before adding a CordBuffer to
284 // a Cord: failure to do so will lead to undefined behavior.
285 // Setting the length to a small value or zero does not release any memory
286 // held by this CordBuffer instance. Requires `length <= capacity()`.
287 // Applications should preferably use the `IncreaseLengthBy()` method above
288 // in combination with the 'available()` or `available_up_to()` methods.
289 void SetLength(size_t length);
290
291 private:
292 // Make sure we don't accidentally over promise.
293 static_assert(kCustomLimit <= cord_internal::kMaxLargeFlatSize, "");
294
295 // Assume the cost of an 'uprounded' allocation to CeilPow2(size) versus
296 // the cost of allocating at least 1 extra flat <= 4KB:
297 // - Flat overhead = 13 bytes
298 // - Btree amortized cost / node =~ 13 bytes
299 // - 64 byte granularity of tcmalloc at 4K =~ 32 byte average
300 // CPU cost and efficiency requires we should at least 'save' something by
301 // splitting, as a poor man's measure, we say the slop needs to be
302 // at least double the cost offset to make it worth splitting: ~128 bytes.
303 static constexpr size_t kMaxPageSlop = 128;
304
305 // Overhead for allocation a flat.
306 static constexpr size_t kOverhead = cord_internal::kFlatOverhead;
307
308 using CordRepFlat = cord_internal::CordRepFlat;
309
310 // `Rep` is the internal data representation of a CordBuffer. The internal
311 // representation has an internal small size optimization similar to
312 // std::string (SSO).
313 struct Rep {
314 // Inline SSO size of a CordBuffer
315 static constexpr size_t kInlineCapacity = sizeof(intptr_t) * 2 - 1;
316
317 // Creates a default instance with kInlineCapacity.
RepRep318 Rep() : short_rep{} {}
319
320 // Creates an instance managing an allocated non zero CordRep.
RepRep321 explicit Rep(cord_internal::CordRepFlat* rep) : long_rep{rep} {
322 assert(rep != nullptr);
323 }
324
325 // Returns true if this instance manages the SSO internal buffer.
is_shortRep326 bool is_short() const {
327 constexpr size_t offset = offsetof(Short, raw_size);
328 return (reinterpret_cast<const char*>(this)[offset] & 1) != 0;
329 }
330
331 // Returns the available area of the internal SSO data
short_availableRep332 absl::Span<char> short_available() {
333 const size_t length = short_length();
334 return absl::Span<char>(short_rep.data + length,
335 kInlineCapacity - length);
336 }
337
338 // Returns the available area of the internal SSO data
long_availableRep339 absl::Span<char> long_available() {
340 assert(!is_short());
341 const size_t length = long_rep.rep->length;
342 return absl::Span<char>(long_rep.rep->Data() + length,
343 long_rep.rep->Capacity() - length);
344 }
345
346 // Returns the length of the internal SSO data.
short_lengthRep347 size_t short_length() const {
348 assert(is_short());
349 return static_cast<size_t>(short_rep.raw_size >> 1);
350 }
351
352 // Sets the length of the internal SSO data.
353 // Disregards any previously set CordRep instance.
set_short_lengthRep354 void set_short_length(size_t length) {
355 short_rep.raw_size = static_cast<char>((length << 1) + 1);
356 }
357
358 // Adds `n` to the current short length.
add_short_lengthRep359 void add_short_length(size_t n) {
360 assert(is_short());
361 short_rep.raw_size += static_cast<char>(n << 1);
362 }
363
364 // Returns reference to the internal SSO data buffer.
dataRep365 char* data() {
366 assert(is_short());
367 return short_rep.data;
368 }
dataRep369 const char* data() const {
370 assert(is_short());
371 return short_rep.data;
372 }
373
374 // Returns a pointer the external CordRep managed by this instance.
repRep375 cord_internal::CordRepFlat* rep() const {
376 assert(!is_short());
377 return long_rep.rep;
378 }
379
380 // The internal representation takes advantage of the fact that allocated
381 // memory is always on an even address, and uses the least significant bit
382 // of the first or last byte (depending on endianness) as the inline size
383 // indicator overlapping with the least significant byte of the CordRep*.
384 #if defined(ABSL_IS_BIG_ENDIAN)
385 struct Long {
LongRep::Long386 explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {}
387 void* padding;
388 cord_internal::CordRepFlat* rep;
389 };
390 struct Short {
391 char data[sizeof(Long) - 1];
392 char raw_size = 1;
393 };
394 #else
395 struct Long {
LongRep::Long396 explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {}
397 cord_internal::CordRepFlat* rep;
398 void* padding;
399 };
400 struct Short {
401 char raw_size = 1;
402 char data[sizeof(Long) - 1];
403 };
404 #endif
405
406 union {
407 Long long_rep;
408 Short short_rep;
409 };
410 };
411
412 // Power2 functions
IsPow2(size_t size)413 static bool IsPow2(size_t size) { return absl::has_single_bit(size); }
Log2Floor(size_t size)414 static size_t Log2Floor(size_t size) {
415 return static_cast<size_t>(absl::bit_width(size) - 1);
416 }
Log2Ceil(size_t size)417 static size_t Log2Ceil(size_t size) {
418 return static_cast<size_t>(absl::bit_width(size - 1));
419 }
420
421 // Implementation of `CreateWithCustomLimit()`.
422 // This implementation allows for future memory allocation hints to
423 // be passed down into the CordRepFlat allocation function.
424 template <typename... AllocationHints>
425 static CordBuffer CreateWithCustomLimitImpl(size_t block_size,
426 size_t capacity,
427 AllocationHints... hints);
428
429 // Consumes the value contained in this instance and resets the instance.
430 // This method returns a non-null Cordrep* if the current instances manages a
431 // CordRep*, and resets the instance to an empty SSO instance. If the current
432 // instance is an SSO instance, then this method returns nullptr and sets
433 // `short_value` to the inlined data value. In either case, the current
434 // instance length is reset to zero.
435 // This method is intended to be used by Cord internal functions only.
ConsumeValue(absl::string_view & short_value)436 cord_internal::CordRep* ConsumeValue(absl::string_view& short_value) {
437 cord_internal::CordRep* rep = nullptr;
438 if (rep_.is_short()) {
439 short_value = absl::string_view(rep_.data(), rep_.short_length());
440 } else {
441 rep = rep_.rep();
442 }
443 rep_.set_short_length(0);
444 return rep;
445 }
446
447 // Internal constructor.
CordBuffer(cord_internal::CordRepFlat * rep)448 explicit CordBuffer(cord_internal::CordRepFlat* rep) : rep_(rep) {
449 assert(rep != nullptr);
450 }
451
452 Rep rep_;
453
454 friend class Cord;
455 friend class CordBufferTestPeer;
456 };
457
MaximumPayload()458 inline constexpr size_t CordBuffer::MaximumPayload() {
459 return cord_internal::kMaxFlatLength;
460 }
461
MaximumPayload(size_t block_size)462 inline constexpr size_t CordBuffer::MaximumPayload(size_t block_size) {
463 // TODO(absl-team): Use std::min when C++11 support is dropped.
464 return (kCustomLimit < block_size ? kCustomLimit : block_size) -
465 cord_internal::kFlatOverhead;
466 }
467
CreateWithDefaultLimit(size_t capacity)468 inline CordBuffer CordBuffer::CreateWithDefaultLimit(size_t capacity) {
469 if (capacity > Rep::kInlineCapacity) {
470 auto* rep = cord_internal::CordRepFlat::New(capacity);
471 rep->length = 0;
472 return CordBuffer(rep);
473 }
474 return CordBuffer();
475 }
476
477 template <typename... AllocationHints>
CreateWithCustomLimitImpl(size_t block_size,size_t capacity,AllocationHints...hints)478 inline CordBuffer CordBuffer::CreateWithCustomLimitImpl(
479 size_t block_size, size_t capacity, AllocationHints... hints) {
480 assert(IsPow2(block_size));
481 capacity = (std::min)(capacity, kCustomLimit);
482 block_size = (std::min)(block_size, kCustomLimit);
483 if (capacity + kOverhead >= block_size) {
484 capacity = block_size;
485 } else if (capacity <= kDefaultLimit) {
486 capacity = capacity + kOverhead;
487 } else if (!IsPow2(capacity)) {
488 // Check if rounded up to next power 2 is a good enough fit
489 // with limited waste making it an acceptable direct fit.
490 const size_t rounded_up = size_t{1} << Log2Ceil(capacity);
491 const size_t slop = rounded_up - capacity;
492 if (slop >= kOverhead && slop <= kMaxPageSlop + kOverhead) {
493 capacity = rounded_up;
494 } else {
495 // Round down to highest power of 2 <= capacity.
496 // Consider a more aggressive step down if that may reduce the
497 // risk of fragmentation where 'people are holding it wrong'.
498 const size_t rounded_down = size_t{1} << Log2Floor(capacity);
499 capacity = rounded_down;
500 }
501 }
502 const size_t length = capacity - kOverhead;
503 auto* rep = CordRepFlat::New(CordRepFlat::Large(), length, hints...);
504 rep->length = 0;
505 return CordBuffer(rep);
506 }
507
CreateWithCustomLimit(size_t block_size,size_t capacity)508 inline CordBuffer CordBuffer::CreateWithCustomLimit(size_t block_size,
509 size_t capacity) {
510 return CreateWithCustomLimitImpl(block_size, capacity);
511 }
512
~CordBuffer()513 inline CordBuffer::~CordBuffer() {
514 if (!rep_.is_short()) {
515 cord_internal::CordRepFlat::Delete(rep_.rep());
516 }
517 }
518
CordBuffer(CordBuffer && rhs)519 inline CordBuffer::CordBuffer(CordBuffer&& rhs) noexcept : rep_(rhs.rep_) {
520 rhs.rep_.set_short_length(0);
521 }
522
523 inline CordBuffer& CordBuffer::operator=(CordBuffer&& rhs) noexcept {
524 if (!rep_.is_short()) cord_internal::CordRepFlat::Delete(rep_.rep());
525 rep_ = rhs.rep_;
526 rhs.rep_.set_short_length(0);
527 return *this;
528 }
529
available()530 inline absl::Span<char> CordBuffer::available() {
531 return rep_.is_short() ? rep_.short_available() : rep_.long_available();
532 }
533
available_up_to(size_t size)534 inline absl::Span<char> CordBuffer::available_up_to(size_t size) {
535 return available().subspan(0, size);
536 }
537
data()538 inline char* CordBuffer::data() {
539 return rep_.is_short() ? rep_.data() : rep_.rep()->Data();
540 }
541
data()542 inline const char* CordBuffer::data() const {
543 return rep_.is_short() ? rep_.data() : rep_.rep()->Data();
544 }
545
capacity()546 inline size_t CordBuffer::capacity() const {
547 return rep_.is_short() ? Rep::kInlineCapacity : rep_.rep()->Capacity();
548 }
549
length()550 inline size_t CordBuffer::length() const {
551 return rep_.is_short() ? rep_.short_length() : rep_.rep()->length;
552 }
553
SetLength(size_t length)554 inline void CordBuffer::SetLength(size_t length) {
555 ABSL_HARDENING_ASSERT(length <= capacity());
556 if (rep_.is_short()) {
557 rep_.set_short_length(length);
558 } else {
559 rep_.rep()->length = length;
560 }
561 }
562
IncreaseLengthBy(size_t n)563 inline void CordBuffer::IncreaseLengthBy(size_t n) {
564 ABSL_HARDENING_ASSERT(n <= capacity() && length() + n <= capacity());
565 if (rep_.is_short()) {
566 rep_.add_short_length(n);
567 } else {
568 rep_.rep()->length += n;
569 }
570 }
571
572 ABSL_NAMESPACE_END
573 } // namespace absl
574
575 #endif // ABSL_STRINGS_CORD_BUFFER_H_
576