New ImmutableCString string class (#9291)

ImmutableCString is a string class similar to CString except it's
immutable. ImmutableCString occupies 16 bytes instead of 8 for CString.

However, ImmutableCString is able to avoid memory allocation when
constructed from a string literal, and in that way it us similar
to StaticString.

ImmutableCString can be auto converted from StaticString.

The backend tag tracking is updated to use ImmutableCString and
the FrameGraph resource manager us updated to use StaticString.

Together these changes significantly cut down heap allocations due to
internal tagging.


We also add optional tracking to {Immutable}CString.
This commit is contained in:
Mathias Agopian
2025-10-03 14:23:36 -07:00
committed by GitHub
parent a4746eab0c
commit f2ed382cf1
50 changed files with 841 additions and 236 deletions
+37 -7
View File
@@ -17,23 +17,49 @@
#include <utils/CString.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <utils/ostream.h>
#include <algorithm>
#include <atomic>
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstddef>
#include <memory>
namespace utils {
namespace {
struct CStringStats {
std::atomic_int32_t alive = { 0 };
std::atomic_int32_t ctor = { 0 };
};
CStringStats gCStringStats{};
constexpr size_t CSTRING_LOG_INTERVAL = 10000;
}
void CString::do_tracking(bool ctor) {
if (ctor) {
gCStringStats.ctor.fetch_add(1, std::memory_order_relaxed);
gCStringStats.alive.fetch_add(1, std::memory_order_relaxed);
} else {
gCStringStats.alive.fetch_sub(1, std::memory_order_relaxed);
}
static std::atomic_int32_t sCtorSinceLastLog = { 0 };
if (UTILS_UNLIKELY(sCtorSinceLastLog.fetch_add(1, std::memory_order_relaxed) == CSTRING_LOG_INTERVAL)) {
LOG(INFO) << "CString stats: "
<< gCStringStats.alive.load(std::memory_order_relaxed) << " alive, "
<< gCStringStats.ctor.load(std::memory_order_relaxed) << " ctor";
sCtorSinceLastLog.store(0, std::memory_order_relaxed);
}
}
UTILS_NOINLINE
CString::CString(const char* cstr, size_t const length) {
track(true);
if (length && cstr) {
Data* const p = static_cast<Data*>(std::malloc(sizeof(Data) + length + 1));
p->length = size_type(length);
mCStr = reinterpret_cast<value_type*>(p + 1);
@@ -44,6 +70,7 @@ CString::CString(const char* cstr, size_t const length) {
}
CString::CString(size_t const length) {
track(true);
if (length) {
Data* const p = static_cast<Data*>(std::malloc(sizeof(Data) + length + 1));
p->length = size_type(length);
@@ -59,14 +86,13 @@ CString::CString(const CString& rhs)
CString& CString::operator=(const CString& rhs) {
if (this != &rhs) {
auto *const p = mData ? mData - 1 : nullptr;
new(this) CString(rhs);
std::free(p);
CString(rhs).swap(*this);
}
return *this;
}
CString::~CString() noexcept {
track(false);
if (mData) {
std::free(mData - 1);
}
@@ -75,6 +101,10 @@ CString::~CString() noexcept {
CString& CString::replace(size_type const pos, size_type len, char const* str, size_t const l) & noexcept {
assert(pos <= size());
if (UTILS_UNLIKELY(!l && !len)) { // nothing to do
return *this;
}
len = std::min(len, size() - pos);
const size_type newSize = size() - len + l;