-
Notifications
You must be signed in to change notification settings - Fork 52
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
[feature request] ABT_pool_create_unit() and ABT_pool_free_unit() #378
Comments
Actually thinking a bit more about it, I think I could just have |
This is a very good point. I don't think nested
In reality, maybe you don't see an issue particularly if you don't enable extra assertion (e.g., undefined-behavior assert etc), but I wouldn't recommend it. Note: fixing this would need pool management change, so allowing nested pools might not be trivial |
I think I see your point about threads being associated with a pool, but I fail to pinpoint exactly where things would fall apart. Here is a sample of what I have so far. typedef struct wrapper_pool_t {
ABT_pool pool;
} wrapper_pool_t;
static ABT_unit wrapper_pool_create_unit(ABT_pool wrapper, ABT_thread thread)
{
(void)wrapper;
return (ABT_unit)thread;
}
static void wrapper_pool_free_unit(ABT_pool wrapper, ABT_unit unit)
{
(void)wrapper;
(void)unit;
}
static ABT_bool wrapper_pool_is_empty(ABT_pool wrapper)
{
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_bool is_empty = ABT_FALSE;
ABT_pool_is_empty(data->pool, &is_empty);
return is_empty;
}
static ABT_thread wrapper_pool_pop(ABT_pool wrapper, ABT_pool_context context)
{
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_thread thread = ABT_THREAD_NULL;
ABT_pool_pop_thread_ex(data->pool, &thread, context);
ABT_thread_set_associated_pool(thread, wrapper);
return thread;
}
static void wrapper_pool_push(ABT_pool wrapper, ABT_unit unit, ABT_pool_context context)
{
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_thread_set_associated_pool(thread, data->pool);
ABT_pool_push_thread_ex(data->pool, (ABT_thread)unit, context);
} My
EDIT: reading more of the code, it looks like (1) EDIT 2: if the child pool is a builtin pool, then it looks like there won't be any allocation/deallocation: argobots/src/include/abti_unit.h Lines 37 to 52 in 2d67034
|
I have tested my code and it seems to be working fine. How can I enable the assertions you were talking about? Here is my full code example, let me know if you see a scenario where it could fail: /*
* (C) 2022 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <abt.h>
#include <stdlib.h>
typedef struct wrapper_pool_t {
ABT_pool pool;
} wrapper_pool_t;
static ABT_unit wrapper_pool_create_unit(ABT_pool wrapper, ABT_thread thread)
{
printf("in %s\n", __func__);
(void)wrapper;
return (ABT_unit)thread;
}
static void wrapper_pool_free_unit(ABT_pool wrapper, ABT_unit unit)
{
printf("in %s\n", __func__);
(void)wrapper;
(void)unit;
}
static ABT_bool wrapper_pool_is_empty(ABT_pool wrapper)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_bool is_empty = ABT_FALSE;
ABT_pool_is_empty(data->pool, &is_empty);
return is_empty;
}
static ABT_thread wrapper_pool_pop(ABT_pool wrapper, ABT_pool_context context)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_thread thread = ABT_THREAD_NULL;
ABT_pool_pop_thread_ex(data->pool, &thread, context);
ABT_thread_set_associated_pool(thread, wrapper);
return thread;
}
static void wrapper_pool_push(ABT_pool wrapper, ABT_unit unit, ABT_pool_context context)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_pool_push_thread_ex(data->pool, (ABT_thread)unit, context);
}
static int wrapper_pool_init(ABT_pool wrapper, ABT_pool_config config)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = (wrapper_pool_t*)calloc(1, sizeof(*data));
ABT_pool_set_data(wrapper, (void*)data);
ABT_pool_create_basic(ABT_POOL_FIFO, ABT_POOL_ACCESS_MPMC, ABT_FALSE, &(data->pool));
ABT_SUCCESS;
}
static void wrapper_pool_free(ABT_pool wrapper)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_pool_free(&(data->pool));
free(data);
}
static size_t wrapper_pool_get_size(ABT_pool wrapper)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
size_t size = 0;
ABT_pool_get_size(data->pool, &size);
return size;
}
static ABT_thread wrapper_pool_pop_wait(ABT_pool wrapper, double time_sec, ABT_pool_context context)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_thread thread = ABT_THREAD_NULL;
ABT_pool_pop_wait_thread_ex(data->pool, &thread, time_sec, context);
return thread;
}
static void wrapper_pool_pop_many(ABT_pool wrapper, ABT_thread* threads, size_t max, size_t* count, ABT_pool_context context)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_pool_pop_threads_ex(data->pool, threads, max, count, context);
}
static void wrapper_pool_push_many(ABT_pool wrapper, const ABT_unit* units, size_t num, ABT_pool_context context)
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_pool_push_threads_ex(data->pool, (const ABT_thread*)units, num, context);
}
static void wrapper_pool_print_all(ABT_pool wrapper, void *arg, void(*print_fn)(void *, ABT_thread))
{
printf("in %s\n", __func__);
wrapper_pool_t* data = NULL;
ABT_pool_get_data(wrapper, (void**)&data);
ABT_pool_print_all(data->pool, arg, (void(*)(void *, ABT_unit))print_fn);
}
void create_wrapper_pool_def(ABT_pool_user_def* p_def)
{
ABT_pool_user_def_create(
wrapper_pool_create_unit,
wrapper_pool_free_unit,
wrapper_pool_is_empty,
wrapper_pool_pop,
wrapper_pool_push,
p_def);
ABT_pool_user_def_set_init(*p_def, wrapper_pool_init);
ABT_pool_user_def_set_free(*p_def, wrapper_pool_free);
ABT_pool_user_def_set_get_size(*p_def, wrapper_pool_get_size);
ABT_pool_user_def_set_pop_wait(*p_def, wrapper_pool_pop_wait);
ABT_pool_user_def_set_pop_many(*p_def, wrapper_pool_pop_many);
ABT_pool_user_def_set_push_many(*p_def, wrapper_pool_push_many);
ABT_pool_user_def_set_print_all(*p_def, wrapper_pool_print_all);
}
void free_wrapper_pool_def(ABT_pool_user_def def)
{
ABT_pool_user_def_free(&def);
}
void f(void* args) {
uint64_t x = (uint64_t)args;
for(int i=0; i < 5; i++) {
printf("f(%lu)\n", x);
ABT_thread_yield();
}
}
int main(int argc, char** argv) {
ABT_init(argc, argv);
ABT_pool_user_def pool_def;
create_wrapper_pool_def(&pool_def);
ABT_pool wrapper;
ABT_pool_create(pool_def, ABT_POOL_CONFIG_NULL, &wrapper);
ABT_xstream xstream = ABT_XSTREAM_NULL;
ABT_xstream_create_basic(ABT_SCHED_DEFAULT, 1, &wrapper, ABT_SCHED_CONFIG_NULL, &xstream);
ABT_thread threads[5];
for(uint64_t i=0; i < 5; i++) {
ABT_thread_create(wrapper, f, (void*)i, ABT_THREAD_ATTR_NULL, threads+i);
}
for(uint64_t i=0; i < 5; i++) {
ABT_thread_join(threads[i]);
ABT_thread_free(threads+i);
}
ABT_xstream_join(xstream);
ABT_xstream_free(&xstream);
free_wrapper_pool_def(pool_def);
ABT_finalize();
} |
Yes mostly correct. Argobots creates and frees units lazily, so they are called when ULT's associated pool is changed (for example, by
Yes this is part of the optimizations. Basically
I cannot come up with exactly when, but more specifically:
It'd be okay if it is working in your specific use case, but I would not highly recommend it since it's undefined.
To enable an UB assert, please use |
I'm looking at the code, and I'm under the impression that those functions ( Note also: I simplified a bit my code above but in practice my wrapper pool would have a mutex protecting every operation, so that I ensure correct behavior in an MPMC setting). Definition of Lines 422 to 437 in 915ff28
Definition of Lines 1109 to 1119 in 915ff28
Definition of Lines 468 to 479 in 915ff28
Definition of Lines 626 to 638 in 915ff28
The
I think the only way I could get an undefined behavior is if someone calls argobots/src/include/abti_unit.h Line 209 in 915ff28
ABT_thread_set_associated_pool actually safe to call? Because a custom scheduler, or any ULT, could just as well call this function to change the pool a thread is associated with.
|
Sorry for the late reply. Maybe I used a word "undefined behavior" in a confusing way:
// pseudo code, typical use case
thread = ABT_pool_pop(pool1);
// If this thread yields, this thread will be pushed to pool2, not pool1.
ABT_thread_set_associated_pool(thread, pool2);
ABT_self_schedule(thread); |
Oh I see, the problem is that an Argobots developer could change something in the implementation that would change the behavior, because that behavior is undocumented. So my code works fine with the current implementation of Argobots but may not in the future. |
I am trying to write an
ABT_pool_user_def
pool definition for a pool that redirects its calls to anotherABT_pool
(kind of a "wrapper" pool). I have multiple use-cases for that (including a pool that logs the timings of ULT it sees being pushed and popped). The problem is, I can't wrap all the functionalities of the inner pool without having access to more information about it.Example:
I think all I need is for Argobots to provide public
ABT_pool_create_unit()
andABT_pool_free_unit()
that would call the underlying pool definition's function pointers.Alternatively, this could be achieved with an
ABT_pool_get_user_def()
that would give access to the pool'sABT_pool_user_def
, and someABT_pool_user_def_get_*()
function to retrieve the function pointers from the pool definition.The text was updated successfully, but these errors were encountered: