How absl::Cord Handles Memory Allocation: When to Choose It Over std::string
absl::Cord uses a chunked, reference-counted tree structure for strings larger than 15 bytes, enabling O(1) appends, zero-copy sharing, and efficient sub-string operations, while std::string remains optimal for small, random-access-heavy workloads.
The absl::Cord class in the abseil/abseil-cpp repository reimplements string storage to optimize for large, mutable, and shareable data. Unlike std::string's contiguous buffer, Cord employs a sophisticated node-based architecture that minimizes memory copies during modification and enables cheap data sharing across API boundaries.
Memory Allocation Architecture of absl::Cord
absl::Cord avoids the reallocation penalties of contiguous storage by splitting data into reference-counted chunks managed through a tree of CordRep nodes. This design trades occasional pointer indirection for significantly better performance on large string operations.
Small-Size Optimization (Inline Storage)
For strings of 15 bytes or fewer, absl::Cord stores data directly inside the object itself without heap allocation. In absl/strings/cord.h at lines 46-48, the InlineRep class defines kMaxInline as 15 bytes, using InlineRep::set_data to copy bytes directly into the Cord object's internal buffer. This optimization ensures that small strings incur zero allocation overhead and remain cache-friendly, similar to std::string short-string optimizations.
Chunk-Based Tree Structure for Large Data
When construction exceeds the inline threshold, absl::Cord allocates a tree of nodes via NewTree (found in absl/strings/cord.cc at lines 111-117). The implementation selects between two node types based on size:
- Flat nodes (
CordRepFlat): Allocated viaCreateFlatfor moderate strings up to approximately 2 KB, stored as single contiguous blocks. - B-tree nodes (
CordRepBtree): Created viaNewBtree(lines 99-108) for very large strings, organizing data into a balanced tree that supports logarithmic-time prepend and append operations for multi-megabyte payloads.
This chunking strategy means appending to a 100 MiB Cord does not require moving or copying existing data—only allocating a new node and updating tree pointers.
Reference-Counted Sharing
Every CordRep node inherits from CordRepBase, which contains an atomic reference count. When copying a Cord, the implementation calls CordRep::Ref to increment the count, making copies O(1) regardless of data size. The CordRep::Unref method handles decrementing counts and deallocation when the last reference disappears. This sharing mechanism enables efficient passing of large strings by value across threads and function boundaries without the copy penalties associated with std::string.
Zero-Copy External Buffers
For ingesting memory-mapped files or network buffers without copying, absl::Cord provides MakeCordFromExternal (implemented in absl/strings/cord.cc at lines 165-173). This function creates a CordRepExternal node that points to user-supplied memory and registers a releaser callback. The external buffer remains owned by the Cord until the last reference drops, eliminating the need to duplicate large incoming data into Cord's internal storage.
Append Optimization with Spare Capacity
To minimize allocations during incremental growth, absl::Cord tracks spare capacity in leaf nodes. The PrepareAppendRegion method (lines 236-263 in cord.cc) checks if the last chunk has unused space; if so, new data writes directly into that existing allocation. When no space exists, Cord::AppendImpl adds a new tree node via InlineRep::AppendTree. This strategy amortizes allocation costs during repeated append operations.
When to Prefer absl::Cord Over std::string
Choose absl::Cord when your workload matches these patterns, otherwise prefer std::string for simplicity.
Prefer absl::Cord for:
- Large strings with frequent prepend/append: Adding data to either end is amortized O(1) because new chunks attach without moving existing data, unlike
std::string's linear reallocation cost. - Sharing immutable data across boundaries: Copying is O(1) via reference counting, and sub-cords share underlying nodes.
- Zero-copy ingestion: Use
MakeCordFromExternalto wrap existing buffers without duplication. - Frequent slicing operations:
Subcordcreates lightweight views (lines 708-746 incord.cc) that reference original chunks, avoiding the full copies required bystd::string::substr.
Prefer std::string for:
- Small strings under 16 bytes: Cord incurs extra indirection overhead;
std::stringis more compact and faster for tiny data. - Heavy random access: Cord's
operator[]requires O(log N) traversal (lines 1510-1564 incord.h), whilestd::stringprovides O(1) indexing. - Guaranteed contiguous storage: Cord stores data in chunks; only
Flatten(lines 1401-1412 incord.cc) creates a contiguous buffer, which copies the entire payload.
Practical Implementation Examples
#include "absl/strings/cord.h"
#include <iostream>
// 1. Small data stays inline (no heap allocation)
absl::Cord small("hello"); // Stored in InlineRep (≤15 bytes)
// 2. Large data automatically uses tree nodes
std::string huge(5'000'000, 'x'); // 5 MiB of 'x'
absl::Cord large(huge); // Creates B-tree of chunks via NewTree
// 3. O(1) prepend/append without reallocation
large.Prepend("header-"); // Adds new chunk at front
large.Append("-footer"); // Adds new chunk at back
// 4. Zero-copy external buffer ownership
char* buffer = new char[1024];
std::memcpy(buffer, "network data", 12);
absl::Cord external = absl::MakeCordFromExternal(
absl::string_view(buffer, 1024),
[](absl::string_view sv) { delete[] sv.data(); });
// 5. Lightweight sub-cord sharing underlying chunks
absl::Cord view = large.Subcord(7, 10); // 10-byte view, O(1)
// 6. Flatten to std::string only when contiguous memory required
std::string flat = std::string(large); // Copies all chunks once
Summary
absl::Cordstores data in reference-counted chunks (flat nodes or B-trees) rather than contiguous buffers, enabling O(1) copies and appends.- Strings ≤15 bytes use inline storage in
absl/strings/cord.h, avoiding heap allocation for small payloads. MakeCordFromExternal(lines 165-173 incord.cc) enables zero-copy ingestion of external memory.- Prefer Cord for large, shared, or frequently modified strings; use
std::stringfor small data or when guaranteed contiguous memory and O(1) indexing are required. - Call
Flatten(lines 1401-1412) to convert to contiguous storage when needed, accepting the cost of a full copy.
Frequently Asked Questions
What is the memory overhead of absl::Cord compared to std::string?
absl::Cord carries a per-object overhead of approximately 16 bytes for the inline representation plus pointer indirection for heap-allocated nodes. For strings under 15 bytes, this makes Cord slightly larger than std::string. However, for strings above 1 KB, Cord's chunk-based approach typically reduces overall memory pressure because it avoids reallocation waste and enables sharing through reference counting, whereas std::string copies entire buffers on assignment.
How do I convert absl::Cord to std::string?
Use the explicit cast operator or assignment: std::string s = std::string(cord); or std::string s(cord);. This calls Cord::Flatten internally, which copies all chunks into a single contiguous allocation. Note that this operation is O(N) where N is the string length, unlike Cord's O(1) copy constructor. If you only need temporary contiguous access, consider cord.TryFlat() which returns a string_view without copying if the data happens to be contiguous.
Is absl::Cord thread-safe?
Yes, absl::Cord is thread-safe for concurrent read operations because its CordRep nodes use atomic reference counting. Multiple threads can copy, read, or create sub-cords from the same Cord instance safely without synchronization. However, mutable operations (such as Append or Prepend) are not thread-safe and require external synchronization if the same Cord instance is modified concurrently from multiple threads.
When should I avoid absl::Cord?
Avoid absl::Cord when you require frequent random access by index, as operator[] traverses the chunk tree in O(log N) time compared to std::string's O(1). Also avoid it for small, short-lived strings where the inline optimization of std::string outperforms Cord's extra indirection, or when you need guaranteed C-style contiguous buffers without the copying penalty of Flatten.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →