Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make disjoint pool a C structure #898

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

bratpiorka
Copy link
Contributor

@bratpiorka bratpiorka commented Nov 14, 2024

make Disjoint Pool a C structure. With this PR:

  • Disjoint Pool code is written in C - NOTE: there are no changes in any algorithm - any optimizations should be done in separate PRs
  • it is no longer a lib - it is always added to UMF lib sources
  • UMF_BUILD_LIBUMF_POOL_DISJOINT option is removed
  • few white-box test cases where we look at internal structures are added - more could be added in separate PRs

@bratpiorka bratpiorka requested a review from a team as a code owner November 14, 2024 13:30
@lukaszstolarczuk lukaszstolarczuk marked this pull request as draft November 14, 2024 13:39
@bratpiorka bratpiorka force-pushed the rrudnick_disjoint_c branch 3 times, most recently from 97807f1 to 3521be5 Compare November 18, 2024 10:14
@bratpiorka bratpiorka force-pushed the rrudnick_disjoint_c branch 8 times, most recently from 17d00a2 to 9b22721 Compare November 27, 2024 09:57
src/pool/pool_disjoint.c Fixed Show fixed Hide fixed
src/pool/pool_disjoint.c Fixed Show fixed Hide fixed
@bratpiorka bratpiorka force-pushed the rrudnick_disjoint_c branch 14 times, most recently from cdad0f1 to 4a8935a Compare December 6, 2024 15:03
@bratpiorka bratpiorka force-pushed the rrudnick_disjoint_c branch 2 times, most recently from 5764115 to e8bc871 Compare December 12, 2024 14:34
ldorau
ldorau previously requested changes Dec 16, 2024
Copy link
Contributor

@ldorau ldorau left a comment

Choose a reason for hiding this comment

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

...

src/pool/pool_disjoint_internal.h Outdated Show resolved Hide resolved
src/pool/pool_disjoint_internal.h Outdated Show resolved Hide resolved
src/pool/pool_disjoint_internal.h Outdated Show resolved Hide resolved
src/pool/pool_disjoint_internal.h Outdated Show resolved Hide resolved
src/pool/pool_disjoint_internal.h Outdated Show resolved Hide resolved
src/pool/pool_disjoint.c Outdated Show resolved Hide resolved
src/pool/pool_disjoint.c Show resolved Hide resolved
src/pool/pool_disjoint.c Outdated Show resolved Hide resolved
src/pool/pool_disjoint.c Show resolved Hide resolved
src/pool/pool_disjoint.c Outdated Show resolved Hide resolved
README.md Outdated
@@ -258,10 +257,6 @@ To enable this feature, the `UMF_BUILD_SHARED_LIBRARY` option needs to be turned

TODO: Add a description
Copy link
Contributor

Choose a reason for hiding this comment

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

could you pls add some description here?

and also mention that it is (part of libumf) now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

Copy link
Contributor

@lplewa lplewa left a comment

Choose a reason for hiding this comment

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

Not sure about all debug_logs. Imho there are litte too much logs for production code, so maybe we should try to reduce them to maximum 1-2 logs per API call.

if (mutex_internal->lock.RecursionCount > 1) {
LeaveCriticalSection(&mutex_internal->lock);
if (mutex->lock.RecursionCount > 1) {
LeaveCriticalSection(&mutex->lock);
/* deadlock detected */
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a critical error and it should abort program

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what change do you suggest?

Copy link
Contributor

Choose a reason for hiding this comment

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

As i said Abort(). It's critical issue in our library, it should never happen.

Or add error handling to all mutex_locks, but i prefer first one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 32 to 56
// Temporary solution for disabling memory poisoning. This is needed because
// AddressSanitizer does not support memory poisoning for GPU allocations.
// More info: https://github.com/oneapi-src/unified-memory-framework/issues/634
#ifndef POISON_MEMORY
#define POISON_MEMORY 0
#endif

static void annotate_memory_inaccessible(void *ptr, size_t size) {
(void)ptr;
(void)size;
#if (POISON_MEMORY != 0)
utils_annotate_memory_inaccessible(ptr, size);
#endif
}

static void annotate_memory_undefined(void *ptr, size_t size) {
(void)ptr;
(void)size;
#if (POISON_MEMORY != 0)
utils_annotate_memory_undefined(ptr, size);
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Temporary solution for disabling memory poisoning. This is needed because
// AddressSanitizer does not support memory poisoning for GPU allocations.
// More info: https://github.com/oneapi-src/unified-memory-framework/issues/634
#ifndef POISON_MEMORY
#define POISON_MEMORY 0
#endif
static void annotate_memory_inaccessible(void *ptr, size_t size) {
(void)ptr;
(void)size;
#if (POISON_MEMORY != 0)
utils_annotate_memory_inaccessible(ptr, size);
#endif
}
static void annotate_memory_undefined(void *ptr, size_t size) {
(void)ptr;
(void)size;
#if (POISON_MEMORY != 0)
utils_annotate_memory_undefined(ptr, size);
#endif
}
// Temporary solution for disabling memory poisoning. This is needed because
// AddressSanitizer does not support memory poisoning for GPU allocations.
// More info: https://github.com/oneapi-src/unified-memory-framework/issues/634
#ifndef POISON_MEMORY
#undef __SANITIZE_ADDRESS__
#endif
#include "utils_sanitizers.h"
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

slab->bucket = bucket;

slab->iter =
(slab_list_item_t *)umf_ba_global_alloc(sizeof(slab_list_item_t));
Copy link
Contributor

Choose a reason for hiding this comment

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

This is no longer c++. Cast is not needed
sizeof(slab->iter )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

slab_t *create_slab(bucket_t *bucket, bool full_size) {
assert(bucket);

slab_t *slab = umf_ba_global_alloc(sizeof(slab_t));
Copy link
Contributor

Choose a reason for hiding this comment

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

sizeof(*slab)

Copy link
Contributor Author

@bratpiorka bratpiorka Jan 14, 2025

Choose a reason for hiding this comment

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

no, no, no. * slab looks like dereferencing an pointer which is invalid at the time I pass it to sizeof(). I understand this will compile but let's say I got a function that allocates mem and returns void * and I don't know the "name" of the results pointer - what should I then pass to sizeof? passing variable name instead of type in such simple cases looks strange and makes things more complex to understand

Copy link
Contributor

Choose a reason for hiding this comment

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

If i cannot convince you let me show some references (by searching for them i did not found single one preferring sizeof(type) instead of sizeof(variable)):
1.

The preferred form for passing a size of a struct is the following:
p = kmalloc(sizeof(*p), ...);
The alternative form where struct name is spelled out hurts readability and introduces an opportunity for a bug when the pointer variable type is changed but the corresponding sizeof that is passed to a memory allocator is not.
https://www.kernel.org/doc/html/v4.10/process/coding-style.html#allocating-memory

I perfer sizeof(variable) over sizeof(type). Consider:
int a1;
float a2;
memset(&a1,0,sizeof(a1));
memset(&a2,0,sizeof(a2));

vs.

int a1;
float a2;
memset(&a1,0,sizeof(int));
memset(&a2,0,sizeof(float));

In the first case, it's easy to verify that the right sizes are being passed to memset. In the second case, you need to constantly review top and bottom sections to make sure you are consistent.
https://softwareengineering.stackexchange.com/questions/201104/sizeof-style-sizeoftype-or-sizeof-variable

Prefer sizeof(varname) to sizeof(type).

Use sizeof(varname) when you take the size of a particular variable. sizeof(varname) will update appropriately if someone changes the variable type either now or later.
https://google.github.io/styleguide/cppguide.html#sizeof

IMO a = malloc(sizeof(int)); is poor, and worse than a = (int *)malloc(sizeof(int));, because you cannot tell just from looking at this line whether or not it is correct.
a = malloc(sizeof(*a)); is obviously correct.
https://www.reddit.com/r/C_Programming/comments/6e008m/sizeof_type_vs_sizeof_variable/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

slab->chunks = umf_ba_global_alloc(sizeof(bool) * slab->num_chunks);
if (slab->chunks == NULL) {
LOG_ERR("allocation of slab chunks failed!");
umf_ba_global_free(slab->iter);
Copy link
Contributor

Choose a reason for hiding this comment

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

please use goto to handle errors

Comment on lines 581 to 586
#ifndef NDEBUG
// debug check
bucket_t *bucket = pool->buckets[calculated_idx];
assert(bucket->size >= size);
(void)bucket;

if (calculated_idx > 0) {
bucket_t *bucket_prev = pool->buckets[calculated_idx - 1];
assert(bucket_prev->size < size);
(void)bucket_prev;
}
#endif // NDEBUG
Copy link
Contributor

Choose a reason for hiding this comment

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

If buckets are created on pool creation, do we need to check their sizes and order each time that we are looking for correct one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

Comment on lines +597 to +573
void bucket_print_stats(bucket_t *bucket, bool *title_printed,
const char *label) {
if (bucket->alloc_count) {
if (!*title_printed) {
LOG_DEBUG("\"%s\" pool memory statistics", label);
LOG_DEBUG("%14s %12s %12s %18s %20s %21s", "Bucket Size", "Allocs",
"Frees", "Allocs from Pool", "Peak Slabs in Use",
"Peak Slabs in Pool");
*title_printed = true;
}
LOG_DEBUG("%14zu %12zu %12zu %18zu %20zu %21zu", bucket->size,
bucket->alloc_count, bucket->free_count,
bucket->alloc_pool_count, bucket->max_slabs_in_use,
bucket->max_slabs_in_pool);
}
}

void disjoint_pool_print_stats(disjoint_pool_t *pool, bool *title_printed,
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of bool *title_printed, create function print_title and just print once, without extra pointer argument passed to all functions.


disjoint_pool_print_stats(hPool, &title_printed, &high_bucket_size,
&high_peak_slabs_in_use, name);
if (title_printed) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It will be allways printed, so not sure why we have if here

}

disjoint_pool_t *disjoint_pool =
(disjoint_pool_t *)umf_ba_global_alloc(sizeof(struct disjoint_pool_t));
Copy link
Contributor

Choose a reason for hiding this comment

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

not needed cast

sizeof(*disjoint_pool)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


umf_result_t disjoint_pool_initialize(umf_memory_provider_handle_t provider,
void *params, void **ppPool) {
if (!provider) {
Copy link
Contributor

Choose a reason for hiding this comment

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

check other arguments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// Available list if any one or more of their chunks is free.The entire slab
// is not necessarily free, just some chunks in the slab are free. To
// implement pooling we will allow one slab in the Available list to be
// entirely empty. Normally such a slab would have been freed. But
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's better to describe what API does, not what it doesn't do, to avoid confusion. Normally(..) sentence could be dropped. Same below before Instead(...).

// Protects the bucket and all the corresponding slabs
utils_mutex_t bucket_lock;

// Reference to the allocator context, used access memory allocation
Copy link
Contributor

Choose a reason for hiding this comment

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

used to access ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

umf_result_t
umfDisjointPoolParamsDestroy(umf_disjoint_pool_params_handle_t hParams) {
// NOTE: dereferencing hParams when BA is already destroyed leads to crash
if (hParams && !umf_ba_global_is_destroyed()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shall there be some warning when base allocator is already destroyed?

disjoint_pool_print_stats(hPool, &title_printed, &high_bucket_size,
&high_peak_slabs_in_use, name);
if (title_printed) {
LOG_DEBUG("current pool size: %zu",
Copy link
Contributor

Choose a reason for hiding this comment

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

current -> destroyed ?

if (title_printed) {
LOG_DEBUG("current pool size: %zu",
disjoint_pool_get_limits(hPool)->total_size);
LOG_DEBUG("suggested setting=;%c%s:%zu,%zu,64K",
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are destroying a pool, what do we need suggested settings for?

umf_result_t ret =
umfMemoryProviderAlloc(pool->provider, size, 0, &ptr);
if (ret != UMF_RESULT_SUCCESS) {
TLS_last_allocation_error = ret;
Copy link
Contributor

Choose a reason for hiding this comment

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

Log a failure?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

int i = 0;
Size1 = ts1;
Size2 = ts2;
for (; Size2 < CutOff; Size1 *= 2, Size2 *= 2, i += 2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The last Size1 bucket won't be created in case 1.5 * Size1 < CutOff even if Size1 < CutOff. This should be either created or clearly commented somewhere that this is the case.


void *disjoint_pool_malloc(void *pool, size_t size) {
disjoint_pool_t *hPool = (disjoint_pool_t *)pool;
void *ptr = disjoint_pool_allocate(hPool, size);
Copy link
Contributor

Choose a reason for hiding this comment

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

Check for pool == NULL

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is already done in umfPoolMalloc

// This allocation will be served from a Bucket which size is multiple
// of Alignment and Slab address is aligned to provider_min_page_size
// so the address will be properly aligned.
aligned_size = (size > 1) ? ALIGN_UP(size, alignment) : alignment;
Copy link
Contributor

Choose a reason for hiding this comment

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

ALIGN_UP_SAFE?

while (chunk != slab->chunks + slab->num_chunks) {
// false means not used
if (*chunk == false) {
size_t idx = (chunk - slab->chunks) / sizeof(bool);
Copy link
Contributor

Choose a reason for hiding this comment

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

bool * pointer arithmetic will already provide the index number. Remove the division by sizeof(bool).

@bratpiorka bratpiorka force-pushed the rrudnick_disjoint_c branch 7 times, most recently from 6f24a0e to 6237f6c Compare January 14, 2025 17:27
Copy link
Contributor

@ldorau ldorau left a comment

Choose a reason for hiding this comment

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

Issues resolved

@ldorau ldorau dismissed their stale review January 16, 2025 11:02

All issues fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants