cord_buffer.h (22786B)
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 // CordBuffer::CreateWithCustomLimit() 164 // 165 // Creates a CordBuffer instance of the desired `capacity` rounded to an 166 // appropriate power of 2 size less than, or equal to `block_size`. 167 // Requires `block_size` to be a power of 2. 168 // 169 // If `capacity` is less than or equal to `kDefaultLimit`, then this method 170 // behaves identical to `CreateWithDefaultLimit`, which means that the caller 171 // is guaranteed to get a buffer of at least the requested capacity. 172 // 173 // If `capacity` is greater than or equal to `block_size`, then this method 174 // returns a buffer with an `allocated size` of `block_size` bytes. Otherwise, 175 // this methods returns a buffer with a suitable smaller power of 2 block size 176 // to satisfy the request. The actual size depends on a number of factors, and 177 // is typically (but not necessarily) the highest or second highest power of 2 178 // value less than or equal to `capacity`. 179 // 180 // The 'allocated size' includes a small amount of overhead required for 181 // internal state, which is currently 13 bytes on 64-bit platforms. For 182 // example: a buffer created with `block_size` and `capacity' set to 8KiB 183 // will have an allocated size of 8KiB, and an effective internal `capacity` 184 // of 8KiB - 13 = 8179 bytes. 185 // 186 // To demonstrate this in practice, let's assume we want to read data from 187 // somewhat larger files using approximately 64KiB buffers: 188 // 189 // absl::Cord ReadFromFile(int fd, size_t n) { 190 // absl::Cord cord; 191 // while (n > 0) { 192 // CordBuffer buffer = CordBuffer::CreateWithCustomLimit(64 << 10, n); 193 // absl::Span<char> data = buffer.available_up_to(n); 194 // ReadFileDataOrDie(fd, data.data(), data.size()); 195 // buffer.IncreaseLengthBy(data.size()); 196 // cord.Append(std::move(buffer)); 197 // n -= data.size(); 198 // } 199 // return cord; 200 // } 201 // 202 // If we'd use this function to read a file of 659KiB, we may get the 203 // following pattern of allocated cord buffer sizes: 204 // 205 // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523) 206 // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523) 207 // ... 208 // CreateWithCustomLimit(64KiB, 19586) --> ~16KiB (16371) 209 // CreateWithCustomLimit(64KiB, 3215) --> 3215 (at least 3215) 210 // 211 // The reason the method returns a 16K buffer instead of a roughly 19K buffer 212 // is to reduce memory overhead and fragmentation risks. Using carefully 213 // chosen power of 2 values reduces the entropy of allocated memory sizes. 214 // 215 // Additionally, let's assume we'd use the above function on files that are 216 // generally smaller than 64K. If we'd use 'precise' sized buffers for such 217 // files, than we'd get a very wide distribution of allocated memory sizes 218 // rounded to 4K page sizes, and we'd end up with a lot of unused capacity. 219 // 220 // In general, application should only use custom sizes if the data they are 221 // consuming or storing is expected to be many times the chosen block size, 222 // and be based on objective data and performance metrics. For example, a 223 // compress function may work faster and consume less CPU when using larger 224 // buffers. Such an application should pick a size offering a reasonable 225 // trade-off between expected data size, compute savings with larger buffers, 226 // and the cost or fragmentation effect of larger buffers. 227 // Applications must pick a reasonable spot on that curve, and make sure their 228 // data meets their expectations in size distributions such as "mostly large". 229 static CordBuffer CreateWithCustomLimit(size_t block_size, size_t capacity); 230 231 // CordBuffer::available() 232 // 233 // Returns the span delineating the available capacity in this buffer 234 // which is defined as `{ data() + length(), capacity() - length() }`. 235 absl::Span<char> available(); 236 237 // CordBuffer::available_up_to() 238 // 239 // Returns the span delineating the available capacity in this buffer limited 240 // to `size` bytes. This is equivalent to `available().subspan(0, size)`. 241 absl::Span<char> available_up_to(size_t size); 242 243 // CordBuffer::data() 244 // 245 // Returns a non-null reference to the data managed by this instance. 246 // Applications are allowed to write up to `capacity` bytes of instance data. 247 // CordBuffer data is uninitialized by default. Reading data from an instance 248 // that has not yet been initialized will lead to undefined behavior. 249 char* data(); 250 const char* data() const; 251 252 // CordBuffer::length() 253 // 254 // Returns the length of this instance. The default length of a CordBuffer is 255 // 0, indicating an 'empty' CordBuffer. Applications must specify the length 256 // of the data in a CordBuffer before adding it to a Cord. 257 size_t length() const; 258 259 // CordBuffer::capacity() 260 // 261 // Returns the capacity of this instance. All instances have a non-zero 262 // capacity: default and `moved from` instances have a small internal buffer. 263 size_t capacity() const; 264 265 // CordBuffer::IncreaseLengthBy() 266 // 267 // Increases the length of this buffer by the specified 'n' bytes. 268 // Applications must make sure all data in this buffer up to the new length 269 // has been initialized before adding a CordBuffer to a Cord: failure to do so 270 // will lead to undefined behavior. Requires `length() + n <= capacity()`. 271 // Typically, applications will use 'available_up_to()` to get a span of the 272 // desired capacity, and use `span.size()` to increase the length as in: 273 // absl::Span<char> span = buffer.available_up_to(desired); 274 // buffer.IncreaseLengthBy(span.size()); 275 // memcpy(span.data(), src, span.size()); 276 // etc... 277 void IncreaseLengthBy(size_t n); 278 279 // CordBuffer::SetLength() 280 // 281 // Sets the data length of this instance. Applications must make sure all data 282 // of the specified length has been initialized before adding a CordBuffer to 283 // a Cord: failure to do so will lead to undefined behavior. 284 // Setting the length to a small value or zero does not release any memory 285 // held by this CordBuffer instance. Requires `length <= capacity()`. 286 // Applications should preferably use the `IncreaseLengthBy()` method above 287 // in combination with the 'available()` or `available_up_to()` methods. 288 void SetLength(size_t length); 289 290 private: 291 // Make sure we don't accidentally over promise. 292 static_assert(kCustomLimit <= cord_internal::kMaxLargeFlatSize, ""); 293 294 // Assume the cost of an 'uprounded' allocation to CeilPow2(size) versus 295 // the cost of allocating at least 1 extra flat <= 4KB: 296 // - Flat overhead = 13 bytes 297 // - Btree amortized cost / node =~ 13 bytes 298 // - 64 byte granularity of tcmalloc at 4K =~ 32 byte average 299 // CPU cost and efficiency requires we should at least 'save' something by 300 // splitting, as a poor man's measure, we say the slop needs to be 301 // at least double the cost offset to make it worth splitting: ~128 bytes. 302 static constexpr size_t kMaxPageSlop = 128; 303 304 // Overhead for allocation a flat. 305 static constexpr size_t kOverhead = cord_internal::kFlatOverhead; 306 307 using CordRepFlat = cord_internal::CordRepFlat; 308 309 // `Rep` is the internal data representation of a CordBuffer. The internal 310 // representation has an internal small size optimization similar to 311 // std::string (SSO). 312 struct Rep { 313 // Inline SSO size of a CordBuffer 314 static constexpr size_t kInlineCapacity = sizeof(intptr_t) * 2 - 1; 315 316 // Creates a default instance with kInlineCapacity. 317 Rep() : short_rep{} {} 318 319 // Creates an instance managing an allocated non zero CordRep. 320 explicit Rep(cord_internal::CordRepFlat* rep) : long_rep{rep} { 321 assert(rep != nullptr); 322 } 323 324 // Returns true if this instance manages the SSO internal buffer. 325 bool is_short() const { 326 constexpr size_t offset = offsetof(Short, raw_size); 327 return (reinterpret_cast<const char*>(this)[offset] & 1) != 0; 328 } 329 330 // Returns the available area of the internal SSO data 331 absl::Span<char> short_available() { 332 const size_t length = short_length(); 333 return absl::Span<char>(short_rep.data + length, 334 kInlineCapacity - length); 335 } 336 337 // Returns the available area of the internal SSO data 338 absl::Span<char> long_available() const { 339 assert(!is_short()); 340 const size_t length = long_rep.rep->length; 341 return absl::Span<char>(long_rep.rep->Data() + length, 342 long_rep.rep->Capacity() - length); 343 } 344 345 // Returns the length of the internal SSO data. 346 size_t short_length() const { 347 assert(is_short()); 348 return static_cast<size_t>(short_rep.raw_size >> 1); 349 } 350 351 // Sets the length of the internal SSO data. 352 // Disregards any previously set CordRep instance. 353 void set_short_length(size_t length) { 354 short_rep.raw_size = static_cast<char>((length << 1) + 1); 355 } 356 357 // Adds `n` to the current short length. 358 void add_short_length(size_t n) { 359 assert(is_short()); 360 short_rep.raw_size += static_cast<char>(n << 1); 361 } 362 363 // Returns reference to the internal SSO data buffer. 364 char* data() { 365 assert(is_short()); 366 return short_rep.data; 367 } 368 const char* data() const { 369 assert(is_short()); 370 return short_rep.data; 371 } 372 373 // Returns a pointer the external CordRep managed by this instance. 374 cord_internal::CordRepFlat* rep() const { 375 assert(!is_short()); 376 return long_rep.rep; 377 } 378 379 // The internal representation takes advantage of the fact that allocated 380 // memory is always on an even address, and uses the least significant bit 381 // of the first or last byte (depending on endianness) as the inline size 382 // indicator overlapping with the least significant byte of the CordRep*. 383 #if defined(ABSL_IS_BIG_ENDIAN) 384 struct Long { 385 explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {} 386 void* padding; 387 cord_internal::CordRepFlat* rep; 388 }; 389 struct Short { 390 char data[sizeof(Long) - 1]; 391 char raw_size = 1; 392 }; 393 #else 394 struct Long { 395 explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {} 396 cord_internal::CordRepFlat* rep; 397 void* padding; 398 }; 399 struct Short { 400 char raw_size = 1; 401 char data[sizeof(Long) - 1]; 402 }; 403 #endif 404 405 union { 406 Long long_rep; 407 Short short_rep; 408 }; 409 }; 410 411 // Power2 functions 412 static bool IsPow2(size_t size) { return absl::has_single_bit(size); } 413 static size_t Log2Floor(size_t size) { 414 return static_cast<size_t>(absl::bit_width(size) - 1); 415 } 416 static size_t Log2Ceil(size_t size) { 417 return static_cast<size_t>(absl::bit_width(size - 1)); 418 } 419 420 // Implementation of `CreateWithCustomLimit()`. 421 // This implementation allows for future memory allocation hints to 422 // be passed down into the CordRepFlat allocation function. 423 template <typename... AllocationHints> 424 static CordBuffer CreateWithCustomLimitImpl(size_t block_size, 425 size_t capacity, 426 AllocationHints... hints); 427 428 // Consumes the value contained in this instance and resets the instance. 429 // This method returns a non-null Cordrep* if the current instances manages a 430 // CordRep*, and resets the instance to an empty SSO instance. If the current 431 // instance is an SSO instance, then this method returns nullptr and sets 432 // `short_value` to the inlined data value. In either case, the current 433 // instance length is reset to zero. 434 // This method is intended to be used by Cord internal functions only. 435 cord_internal::CordRep* ConsumeValue(absl::string_view& short_value) { 436 cord_internal::CordRep* rep = nullptr; 437 if (rep_.is_short()) { 438 short_value = absl::string_view(rep_.data(), rep_.short_length()); 439 } else { 440 rep = rep_.rep(); 441 } 442 rep_.set_short_length(0); 443 return rep; 444 } 445 446 // Internal constructor. 447 explicit CordBuffer(cord_internal::CordRepFlat* rep) : rep_(rep) { 448 assert(rep != nullptr); 449 } 450 451 Rep rep_; 452 453 friend class Cord; 454 friend class CordBufferTestPeer; 455 }; 456 457 inline constexpr size_t CordBuffer::MaximumPayload() { 458 return cord_internal::kMaxFlatLength; 459 } 460 461 inline constexpr size_t CordBuffer::MaximumPayload(size_t block_size) { 462 return (std::min)(kCustomLimit, block_size) - cord_internal::kFlatOverhead; 463 } 464 465 inline CordBuffer CordBuffer::CreateWithDefaultLimit(size_t capacity) { 466 if (capacity > Rep::kInlineCapacity) { 467 auto* rep = cord_internal::CordRepFlat::New(capacity); 468 rep->length = 0; 469 return CordBuffer(rep); 470 } 471 return CordBuffer(); 472 } 473 474 template <typename... AllocationHints> 475 inline CordBuffer CordBuffer::CreateWithCustomLimitImpl( 476 size_t block_size, size_t capacity, AllocationHints... hints) { 477 assert(IsPow2(block_size)); 478 capacity = (std::min)(capacity, kCustomLimit); 479 block_size = (std::min)(block_size, kCustomLimit); 480 if (capacity + kOverhead >= block_size) { 481 capacity = block_size; 482 } else if (capacity <= kDefaultLimit) { 483 capacity = capacity + kOverhead; 484 } else if (!IsPow2(capacity)) { 485 // Check if rounded up to next power 2 is a good enough fit 486 // with limited waste making it an acceptable direct fit. 487 const size_t rounded_up = size_t{1} << Log2Ceil(capacity); 488 const size_t slop = rounded_up - capacity; 489 if (slop >= kOverhead && slop <= kMaxPageSlop + kOverhead) { 490 capacity = rounded_up; 491 } else { 492 // Round down to highest power of 2 <= capacity. 493 // Consider a more aggressive step down if that may reduce the 494 // risk of fragmentation where 'people are holding it wrong'. 495 const size_t rounded_down = size_t{1} << Log2Floor(capacity); 496 capacity = rounded_down; 497 } 498 } 499 const size_t length = capacity - kOverhead; 500 auto* rep = CordRepFlat::New(CordRepFlat::Large(), length, hints...); 501 rep->length = 0; 502 return CordBuffer(rep); 503 } 504 505 inline CordBuffer CordBuffer::CreateWithCustomLimit(size_t block_size, 506 size_t capacity) { 507 return CreateWithCustomLimitImpl(block_size, capacity); 508 } 509 510 inline CordBuffer::~CordBuffer() { 511 if (!rep_.is_short()) { 512 cord_internal::CordRepFlat::Delete(rep_.rep()); 513 } 514 } 515 516 inline CordBuffer::CordBuffer(CordBuffer&& rhs) noexcept : rep_(rhs.rep_) { 517 rhs.rep_.set_short_length(0); 518 } 519 520 inline CordBuffer& CordBuffer::operator=(CordBuffer&& rhs) noexcept { 521 if (!rep_.is_short()) cord_internal::CordRepFlat::Delete(rep_.rep()); 522 rep_ = rhs.rep_; 523 rhs.rep_.set_short_length(0); 524 return *this; 525 } 526 527 inline absl::Span<char> CordBuffer::available() { 528 return rep_.is_short() ? rep_.short_available() : rep_.long_available(); 529 } 530 531 inline absl::Span<char> CordBuffer::available_up_to(size_t size) { 532 return available().subspan(0, size); 533 } 534 535 inline char* CordBuffer::data() { 536 return rep_.is_short() ? rep_.data() : rep_.rep()->Data(); 537 } 538 539 inline const char* CordBuffer::data() const { 540 return rep_.is_short() ? rep_.data() : rep_.rep()->Data(); 541 } 542 543 inline size_t CordBuffer::capacity() const { 544 return rep_.is_short() ? Rep::kInlineCapacity : rep_.rep()->Capacity(); 545 } 546 547 inline size_t CordBuffer::length() const { 548 return rep_.is_short() ? rep_.short_length() : rep_.rep()->length; 549 } 550 551 inline void CordBuffer::SetLength(size_t length) { 552 ABSL_HARDENING_ASSERT(length <= capacity()); 553 if (rep_.is_short()) { 554 rep_.set_short_length(length); 555 } else { 556 rep_.rep()->length = length; 557 } 558 } 559 560 inline void CordBuffer::IncreaseLengthBy(size_t n) { 561 ABSL_HARDENING_ASSERT(n <= capacity() && length() + n <= capacity()); 562 if (rep_.is_short()) { 563 rep_.add_short_length(n); 564 } else { 565 rep_.rep()->length += n; 566 } 567 } 568 569 ABSL_NAMESPACE_END 570 } // namespace absl 571 572 #endif // ABSL_STRINGS_CORD_BUFFER_H_