Skip to content

Commit c406510

Browse files
Add first cut of Pthread group split support. (#296)
Please note that this is a work in progress, so a lot of cleanup and fixes are needed. This adds group splitting capability to Pthreads. That said, there are still races and memory leaks that I'm working out. In particular, there appears to be a problem in qvi_hwsplit_coll::split() that results in inconsistent colorp values (and perhaps other things). Signed-off-by: Samuel K. Gutierrez <[email protected]>
1 parent 45a63c0 commit c406510

12 files changed

+307
-148
lines changed

src/quo-vadis-pthread.cc

+19-15
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
#include "qvi-scope.h"
2525
#include "qvi-utils.h"
2626

27-
struct qvi_pthread_args_s {
27+
struct qvi_pthread_args {
2828
qv_scope_t *scope = nullptr;
2929
qvi_pthread_routine_fun_ptr_t th_routine = nullptr;
3030
void *th_routine_argp = nullptr;
31+
/** Default constructor. */
32+
qvi_pthread_args(void) = delete;
3133
/** Constructor. */
32-
qvi_pthread_args_s(void) = delete;
33-
/** Constructor. */
34-
qvi_pthread_args_s(
34+
qvi_pthread_args(
3535
qv_scope_t *scope_a,
3636
qvi_pthread_routine_fun_ptr_t th_routine_a,
3737
void *th_routine_argp_a
@@ -41,15 +41,15 @@ struct qvi_pthread_args_s {
4141
};
4242

4343
static void *
44-
qvi_pthread_routine(
44+
qvi_pthread_start_routine(
4545
void *arg
4646
) {
47-
qvi_pthread_args_s *arg_ptr = (qvi_pthread_args_s *)arg;
47+
qvi_pthread_args *args = (qvi_pthread_args *)arg;
4848
// TODO(skg) Check return code.
49-
arg_ptr->scope->bind_push();
49+
args->scope->bind_push();
5050

51-
void *const ret = arg_ptr->th_routine(arg_ptr->th_routine_argp);
52-
qvi_delete(&arg_ptr);
51+
void *const ret = args->th_routine(args->th_routine_argp);
52+
qvi_delete(&args);
5353
pthread_exit(ret);
5454
}
5555

@@ -102,17 +102,21 @@ qv_pthread_create(
102102
qv_scope_t *scope
103103
) {
104104
// Memory will be freed in qv_pthread_routine to avoid memory leaks.
105-
qvi_pthread_args_s *arg_ptr = nullptr;
106-
int rc = qvi_new(&arg_ptr, scope, thread_routine, arg);
105+
qvi_pthread_args *pthread_start_args = nullptr;
106+
int rc = qvi_new(&pthread_start_args, scope, thread_routine, arg);
107107
// Since this is meant to behave similarly to
108108
// pthread_create(), return a reasonable errno.
109109
if (qvi_unlikely(rc != QV_SUCCESS)) return ENOMEM;
110-
110+
// Note: The provided scope should have been created by
111+
// qv_pthread_scope_split*. That is why we can safely cast the scope's
112+
// underlying group it to a qvi_group_pthread *.
111113
auto group = dynamic_cast<qvi_group_pthread *>(scope->group());
112-
qvi_pthread_group_pthread_create_args_s *cargs = nullptr;
113-
rc = qvi_new(&cargs, group->thgroup, qvi_pthread_routine, arg_ptr);
114+
qvi_pthread_group_pthread_create_args *cargs = nullptr;
115+
rc = qvi_new(
116+
&cargs, group->thgroup, qvi_pthread_start_routine, pthread_start_args
117+
);
114118
if (qvi_unlikely(rc != QV_SUCCESS)) {
115-
qvi_delete(&arg_ptr);
119+
qvi_delete(&pthread_start_args);
116120
return ENOMEM;
117121
}
118122
return pthread_create(

src/qvi-group-omp.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ struct qvi_group_omp : public qvi_group {
7272
);
7373

7474
virtual int
75-
thsplit(
75+
thread_split(
7676
int,
7777
qvi_group **
7878
) {

src/qvi-group-pthread.cc

+32-3
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,36 @@
1515
#include "qvi-utils.h"
1616

1717
qvi_group_pthread::qvi_group_pthread(
18+
qvi_pthread_group_context *ctx,
1819
int group_size
1920
) {
20-
const int rc = qvi_new(&thgroup, group_size, 0);
21+
int rc = QV_SUCCESS;
22+
// A context pointer was not provided, so create a new one.
23+
if (!ctx) {
24+
rc = qvi_new(&m_context);
25+
if (qvi_unlikely(rc != QV_SUCCESS)) throw qvi_runtime_error();
26+
}
27+
else {
28+
// Else a context pointer was provided, so use it.
29+
m_context = ctx;
30+
m_context->retain();
31+
}
32+
//
33+
rc = qvi_new(&thgroup, m_context, group_size);
2134
if (qvi_unlikely(rc != QV_SUCCESS)) throw qvi_runtime_error();
2235
}
2336

37+
qvi_group_pthread::qvi_group_pthread(
38+
qvi_pthread_group_context *ctx,
39+
qvi_pthread_group *thread_group
40+
) {
41+
assert(ctx && thread_group);
42+
m_context = ctx;
43+
m_context->retain();
44+
//
45+
thgroup = thread_group;
46+
}
47+
2448
qvi_group_pthread::~qvi_group_pthread(void)
2549
{
2650
qvi_delete(&thgroup);
@@ -40,13 +64,18 @@ qvi_group_pthread::split(
4064
int key,
4165
qvi_group **child
4266
) {
67+
// NOTE: This is a collective call across
68+
// ALL threads in the parent thread group.
4369
qvi_group_pthread *ichild = nullptr;
44-
int rc = qvi_new(&ichild);
70+
71+
qvi_pthread_group *ithgroup = nullptr;
72+
int rc = thgroup->split(color, key, &ithgroup);
4573
if (qvi_unlikely(rc != QV_SUCCESS)) goto out;
4674

47-
rc = thgroup->split(color, key, &ichild->thgroup);
75+
rc = qvi_new(&ichild, m_context, ithgroup);
4876
out:
4977
if (qvi_unlikely(rc != QV_SUCCESS)) {
78+
qvi_delete(&ithgroup);
5079
qvi_delete(&ichild);
5180
}
5281
*child = ichild;

src/qvi-group-pthread.h

+29-7
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,32 @@
2020
#include "qvi-bbuff.h"
2121

2222
struct qvi_group_pthread : public qvi_group {
23+
protected:
24+
/**
25+
* Points to per-process, per-thread_split()
26+
* information that maintains Pthread group context.
27+
*/
28+
qvi_pthread_group_context *m_context = nullptr;
29+
public:
2330
/** Underlying group instance. */
2431
qvi_pthread_group *thgroup = nullptr;
25-
/** Constructor. */
26-
qvi_group_pthread(void) = default;
27-
/** Constructor. */
32+
/** Default constructor. */
33+
qvi_group_pthread(void) = delete;
34+
/**
35+
* Constructor that is called by the parent process to setup
36+
* base infrastructure required during a thread_split().
37+
*/
2838
qvi_group_pthread(
39+
qvi_pthread_group_context *ctx,
2940
int group_size
3041
);
42+
/**
43+
* Constructor that is collective across ALL threads in the parent group.
44+
*/
45+
qvi_group_pthread(
46+
qvi_pthread_group_context *ctx,
47+
qvi_pthread_group *thread_group
48+
);
3149
/** Destructor. */
3250
virtual ~qvi_group_pthread(void);
3351

@@ -59,7 +77,10 @@ struct qvi_group_pthread : public qvi_group {
5977
make_intrinsic(
6078
qv_scope_intrinsic_t
6179
) {
62-
// Nothing to do.
80+
// Nothing to do here because a Pthread group cannot be created outside
81+
// of another group. For example, a thread_split can be called from a
82+
// process context, which can be an intrinsic group, but not from a
83+
// threaded context alone.
6384
return QV_SUCCESS;
6485
}
6586

@@ -69,14 +90,15 @@ struct qvi_group_pthread : public qvi_group {
6990
);
7091

7192
virtual int
72-
thsplit(
93+
thread_split(
7394
int,
7495
qvi_group **
7596
) {
76-
// TODO(skg)
7797
return QV_ERR_NOT_SUPPORTED;
7898
}
79-
99+
/**
100+
* This is a collective call across all threads in the parent thread group.
101+
*/
80102
virtual int
81103
split(
82104
int color,

src/qvi-group.cc

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* -*- Mode: C++; c-basic-offset:4; indent-tabs-mode:nil -*- */
22
/*
3-
* Copyright (c) 2021-2024 Triad National Security, LLC
3+
* Copyright (c) 2021-2025 Triad National Security, LLC
44
* All rights reserved.
55
*
66
* This file is part of the quo-vadis project. See the LICENSE file at the
@@ -23,12 +23,16 @@ qvi_group::hwloc(void)
2323
}
2424

2525
int
26-
qvi_group::thsplit(
26+
qvi_group::thread_split(
2727
int nthreads,
2828
qvi_group **child
2929
) {
3030
qvi_group_pthread *ichild = nullptr;
31-
const int rc = qvi_new(&ichild, nthreads);
31+
// This is the entry point for creating a new thread group, so nullptr
32+
// passed to signal that a new context must be created by the new
33+
// qvi_group_pthread. Also note this is called by a single thread of
34+
// execution (i.e., the parent process).
35+
const int rc = qvi_new(&ichild, nullptr, nthreads);
3236
if (qvi_unlikely(rc != QV_SUCCESS)) {
3337
qvi_delete(&ichild);
3438
}
@@ -44,14 +48,29 @@ qvi_group::next_id(
4448
// infrastructure (e.g., QVI_MPI_GROUP_WORLD) will never equal or exceed
4549
// this value.
4650
static std::atomic<qvi_group_id_t> group_id(64);
47-
if (group_id == UINT64_MAX) {
48-
qvi_log_error("Group ID space exhausted");
51+
if (qvi_unlikely(group_id == UINT64_MAX)) {
52+
qvi_log_error("Group ID space exhausted.");
4953
return QV_ERR_OOR;
5054
}
5155
*gid = group_id++;
5256
return QV_SUCCESS;
5357
}
5458

59+
int
60+
qvi_group::next_ids(
61+
size_t n,
62+
std::vector<qvi_group_id_t> &gids
63+
) {
64+
gids.resize(n);
65+
for (size_t i = 0; i < n; ++i) {
66+
qvi_group_id_t gid = 0;
67+
const int rc = next_id(&gid);
68+
if (qvi_unlikely(rc != QV_SUCCESS)) return rc;
69+
gids[i] = gid;
70+
}
71+
return QV_SUCCESS;
72+
}
73+
5574
/*
5675
* vim: ft=cpp ts=4 sts=4 sw=4 expandtab
5776
*/

src/qvi-group.h

+9-2
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ struct qvi_group : qvi_refc {
6161
qvi_group **child
6262
) = 0;
6363
/**
64-
* Creates a new thread group by splitting off of the caller's group.
64+
* Creates a new thread group by splitting off of the calling process'
65+
* group.
6566
*/
6667
virtual int
67-
thsplit(
68+
thread_split(
6869
int nthreads,
6970
qvi_group **child
7071
);
@@ -98,6 +99,12 @@ struct qvi_group : qvi_refc {
9899
next_id(
99100
qvi_group_id_t *gid
100101
);
102+
/** Populates gids with n unique group IDs after each call. */
103+
static int
104+
next_ids(
105+
size_t n,
106+
std::vector<qvi_group_id_t> &gids
107+
);
101108
};
102109

103110
#endif

0 commit comments

Comments
 (0)