Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/snmalloc/ds_core/redblacktree.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ namespace snmalloc
H ptr;

public:
ChildRef() : ptr(nullptr) {}
constexpr ChildRef() = default;

ChildRef(H p) : ptr(p) {}

Expand Down Expand Up @@ -232,7 +232,15 @@ namespace snmalloc
struct RBStep
{
ChildRef node;
bool dir = false;
bool dir;

// Default constructor needed for Array<RBStep, 128>.
constexpr RBStep() = default;

// Remove copy constructors to avoid accidentally copying and mutating the
// path.
RBStep(const RBStep& other) = delete;
RBStep& operator=(const RBStep& other) = delete;

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting RBStep's copy ctor/assignment also suppresses implicit move operations, which makes RBPath (and thus get_root_path() results) non-movable. This can break external composite operations that want to return or store an RBPath by value (e.g., returning a named local where NRVO is not guaranteed). If the intent is only to prevent copying, consider explicitly defaulting move ctor/assignment for RBStep (and/or RBPath) while keeping copy deleted.

Suggested change
// Allow moving RBStep while keeping it non-copyable.
RBStep(RBStep&&) = default;
RBStep& operator=(RBStep&&) = default;

Copilot uses AI. Check for mistakes.
/**
* Update the step to point to a new node and direction.
Expand Down
5 changes: 5 additions & 0 deletions src/snmalloc/mem/metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ namespace snmalloc
uintptr_t* val;

public:
/**
* Uninitialised constructor.
*/
BackendStateWordRef() = default;

Comment on lines +285 to +289
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BackendStateWordRef() now permits a default-constructed instance with an indeterminate val pointer. Any accidental use of get() / assignment on such an instance will dereference an invalid pointer (UB). Consider either making the default state explicitly nullptr and guarding with SNMALLOC_ASSERT(val != nullptr) before dereference, or adding debug assertions in get() / operator= to catch use-before-initialisation without affecting release performance.

Copilot uses AI. Check for mistakes.
/**
* Constructor, wraps a `uintptr_t`. Note that this may be used outside
* of the meta entry by code wishing to provide uniform storage to things
Expand Down
83 changes: 83 additions & 0 deletions src/test/perf/large_alloc/large_alloc.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include <snmalloc/snmalloc.h>
#include <test/measuretime.h>
#include <test/setup.h>

using namespace snmalloc;

static constexpr size_t ALLOC_SIZE = 800 * 1024; // 800 KB
static constexpr size_t ITERATIONS = 100000;

void test_alloc_dealloc_cycle()
{
{
MeasureTime m;
m << "Alloc/dealloc 800KB x " << ITERATIONS;

for (size_t i = 0; i < ITERATIONS; i++)
{
void* p = snmalloc::alloc(ALLOC_SIZE);
SNMALLOC_CHECK(p != nullptr);
snmalloc::dealloc(p);
}
}

snmalloc::debug_check_empty();
}

void test_batch_alloc_then_dealloc()
{
static constexpr size_t BATCH = 128;

void* ptrs[BATCH];

MeasureTime m;
m << "Batch alloc then dealloc 800KB x " << BATCH;
for (size_t j = 0; j < ITERATIONS / BATCH; j++)
{
for (size_t i = 0; i < BATCH; i++)
{
ptrs[i] = snmalloc::alloc(ALLOC_SIZE);
SNMALLOC_CHECK(ptrs[i] != nullptr);
}

for (size_t i = 0; i < BATCH; i++)
{
snmalloc::dealloc(ptrs[i]);
}
}

snmalloc::debug_check_empty();
}

void test_alloc_dealloc_with_touch()
{
{
MeasureTime m;
m << "Alloc/touch/dealloc 800KB x " << ITERATIONS;

for (size_t i = 0; i < ITERATIONS; i++)
{
char* p = static_cast<char*>(snmalloc::alloc(ALLOC_SIZE));
SNMALLOC_CHECK(p != nullptr);
// Touch every 4KiB and last bytes to ensure pages are faulted in
for (size_t offset = 0; offset < ALLOC_SIZE; offset += 4096)
{
p[offset] = 1;
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "Touch every 4KiB and last bytes" but the loop only touches each 4KiB page start and does not touch the last byte(s). Either update the comment to match the behaviour or add an explicit touch of the last byte to match the intent.

Suggested change
}
}
// Ensure the last byte of the allocation is also touched
p[ALLOC_SIZE - 1] = 1;

Copilot uses AI. Check for mistakes.
snmalloc::dealloc(p);
}
}

snmalloc::debug_check_empty();
}

int main(int, char**)
{
setup();

test_alloc_dealloc_cycle();
test_batch_alloc_then_dealloc();
test_alloc_dealloc_with_touch();

return 0;
}
Loading