577 lines
18 KiB
C
577 lines
18 KiB
C
#ifndef LIBOMP_TEST_TOPOLOGY_H
|
|
#define LIBOMP_TEST_TOPOLOGY_H
|
|
|
|
#include "libomp_test_affinity.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <omp.h>
|
|
#include <stdarg.h>
|
|
|
|
typedef enum topology_obj_type_t {
|
|
TOPOLOGY_OBJ_THREAD,
|
|
TOPOLOGY_OBJ_CORE,
|
|
TOPOLOGY_OBJ_SOCKET,
|
|
TOPOLOGY_OBJ_MAX
|
|
} topology_obj_type_t;
|
|
|
|
typedef struct place_list_t {
|
|
int num_places;
|
|
int current_place;
|
|
int *place_nums;
|
|
affinity_mask_t **masks;
|
|
} place_list_t;
|
|
|
|
// Return the first character in file 'f' that is not a whitespace character
|
|
// including newlines and carriage returns
|
|
static int get_first_nonspace_from_file(FILE *f) {
|
|
int c;
|
|
do {
|
|
c = fgetc(f);
|
|
} while (c != EOF && (isspace(c) || c == '\n' || c == '\r'));
|
|
return c;
|
|
}
|
|
|
|
// Read an integer from file 'f' into 'number'
|
|
// Return 1 on successful read of integer,
|
|
// 0 on unsuccessful read of integer,
|
|
// EOF on end of file.
|
|
static int get_integer_from_file(FILE *f, int *number) {
|
|
int n;
|
|
n = fscanf(f, "%d", number);
|
|
if (feof(f))
|
|
return EOF;
|
|
if (n != 1)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
// Read a siblings list file from Linux /sys/devices/system/cpu/cpu?/topology/*
|
|
static affinity_mask_t *topology_get_mask_from_file(const char *filename) {
|
|
int status = EXIT_SUCCESS;
|
|
FILE *f = fopen(filename, "r");
|
|
if (!f) {
|
|
perror(filename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
affinity_mask_t *mask = affinity_mask_alloc();
|
|
while (1) {
|
|
int c, i, n, lower, upper;
|
|
// Read the first integer
|
|
n = get_integer_from_file(f, &lower);
|
|
if (n == EOF) {
|
|
break;
|
|
} else if (n == 0) {
|
|
fprintf(stderr, "syntax error: expected integer\n");
|
|
status = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
|
|
// Now either a , or -
|
|
c = get_first_nonspace_from_file(f);
|
|
if (c == EOF || c == ',') {
|
|
affinity_mask_set(mask, lower);
|
|
if (c == EOF)
|
|
break;
|
|
} else if (c == '-') {
|
|
n = get_integer_from_file(f, &upper);
|
|
if (n == EOF || n == 0) {
|
|
fprintf(stderr, "syntax error: expected integer\n");
|
|
status = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
for (i = lower; i <= upper; ++i)
|
|
affinity_mask_set(mask, i);
|
|
c = get_first_nonspace_from_file(f);
|
|
if (c == EOF) {
|
|
break;
|
|
} else if (c == ',') {
|
|
continue;
|
|
} else {
|
|
fprintf(stderr, "syntax error: unexpected character: '%c (%d)'\n", c,
|
|
c);
|
|
status = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
} else {
|
|
fprintf(stderr, "syntax error: unexpected character: '%c (%d)'\n", c, c);
|
|
status = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
}
|
|
fclose(f);
|
|
if (status == EXIT_FAILURE) {
|
|
affinity_mask_free(mask);
|
|
mask = NULL;
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
static int topology_get_num_cpus() {
|
|
char buf[1024];
|
|
// Count the number of cpus
|
|
int cpu = 0;
|
|
while (1) {
|
|
snprintf(buf, sizeof(buf), "/sys/devices/system/cpu/cpu%d", cpu);
|
|
DIR *dir = opendir(buf);
|
|
if (dir) {
|
|
closedir(dir);
|
|
cpu++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (cpu == 0)
|
|
cpu = 1;
|
|
return cpu;
|
|
}
|
|
|
|
// Return whether the current thread has access to all logical processors
|
|
static int topology_using_full_mask() {
|
|
int cpu;
|
|
int has_all = 1;
|
|
int num_cpus = topology_get_num_cpus();
|
|
affinity_mask_t *mask = affinity_mask_alloc();
|
|
get_thread_affinity(mask);
|
|
for (cpu = 0; cpu < num_cpus; ++cpu) {
|
|
if (!affinity_mask_isset(mask, cpu)) {
|
|
has_all = 0;
|
|
break;
|
|
}
|
|
}
|
|
affinity_mask_free(mask);
|
|
return has_all;
|
|
}
|
|
|
|
// Return array of masks representing OMP_PLACES keyword (e.g., sockets, cores,
|
|
// threads)
|
|
static place_list_t *topology_alloc_type_places(topology_obj_type_t type) {
|
|
char buf[1024];
|
|
int i, cpu, num_places, num_unique;
|
|
int *place_nums;
|
|
int num_cpus = topology_get_num_cpus();
|
|
place_list_t *places = (place_list_t *)malloc(sizeof(place_list_t));
|
|
affinity_mask_t **masks =
|
|
(affinity_mask_t **)malloc(sizeof(affinity_mask_t *) * num_cpus);
|
|
num_unique = 0;
|
|
for (cpu = 0; cpu < num_cpus; ++cpu) {
|
|
affinity_mask_t *mask;
|
|
if (type == TOPOLOGY_OBJ_CORE) {
|
|
snprintf(buf, sizeof(buf),
|
|
"/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list",
|
|
cpu);
|
|
mask = topology_get_mask_from_file(buf);
|
|
} else if (type == TOPOLOGY_OBJ_SOCKET) {
|
|
snprintf(buf, sizeof(buf),
|
|
"/sys/devices/system/cpu/cpu%d/topology/core_siblings_list",
|
|
cpu);
|
|
mask = topology_get_mask_from_file(buf);
|
|
} else if (type == TOPOLOGY_OBJ_THREAD) {
|
|
mask = affinity_mask_alloc();
|
|
affinity_mask_set(mask, cpu);
|
|
} else {
|
|
fprintf(stderr, "Unknown topology type (%d)\n", (int)type);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
// Check for unique topology objects above the thread level
|
|
if (type != TOPOLOGY_OBJ_THREAD) {
|
|
for (i = 0; i < num_unique; ++i) {
|
|
if (affinity_mask_equal(masks[i], mask)) {
|
|
affinity_mask_free(mask);
|
|
mask = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mask)
|
|
masks[num_unique++] = mask;
|
|
}
|
|
place_nums = (int *)malloc(sizeof(int) * num_unique);
|
|
for (i = 0; i < num_unique; ++i)
|
|
place_nums[i] = i;
|
|
places->num_places = num_unique;
|
|
places->masks = masks;
|
|
places->place_nums = place_nums;
|
|
places->current_place = -1;
|
|
return places;
|
|
}
|
|
|
|
static place_list_t *topology_alloc_openmp_places() {
|
|
int place, i;
|
|
int num_places = omp_get_num_places();
|
|
place_list_t *places = (place_list_t *)malloc(sizeof(place_list_t));
|
|
affinity_mask_t **masks =
|
|
(affinity_mask_t **)malloc(sizeof(affinity_mask_t *) * num_places);
|
|
int *place_nums = (int *)malloc(sizeof(int) * num_places);
|
|
for (place = 0; place < num_places; ++place) {
|
|
int num_procs = omp_get_place_num_procs(place);
|
|
int *ids = (int *)malloc(sizeof(int) * num_procs);
|
|
omp_get_place_proc_ids(place, ids);
|
|
affinity_mask_t *mask = affinity_mask_alloc();
|
|
for (i = 0; i < num_procs; ++i)
|
|
affinity_mask_set(mask, ids[i]);
|
|
masks[place] = mask;
|
|
place_nums[place] = place;
|
|
}
|
|
places->num_places = num_places;
|
|
places->place_nums = place_nums;
|
|
places->masks = masks;
|
|
places->current_place = omp_get_place_num();
|
|
return places;
|
|
}
|
|
|
|
static place_list_t *topology_alloc_openmp_partition() {
|
|
int p, i;
|
|
int num_places = omp_get_partition_num_places();
|
|
place_list_t *places = (place_list_t *)malloc(sizeof(place_list_t));
|
|
int *place_nums = (int *)malloc(sizeof(int) * num_places);
|
|
affinity_mask_t **masks =
|
|
(affinity_mask_t **)malloc(sizeof(affinity_mask_t *) * num_places);
|
|
omp_get_partition_place_nums(place_nums);
|
|
for (p = 0; p < num_places; ++p) {
|
|
int place = place_nums[p];
|
|
int num_procs = omp_get_place_num_procs(place);
|
|
int *ids = (int *)malloc(sizeof(int) * num_procs);
|
|
if (num_procs == 0) {
|
|
fprintf(stderr, "place %d has 0 procs?\n", place);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
omp_get_place_proc_ids(place, ids);
|
|
affinity_mask_t *mask = affinity_mask_alloc();
|
|
for (i = 0; i < num_procs; ++i)
|
|
affinity_mask_set(mask, ids[i]);
|
|
if (affinity_mask_count(mask) == 0) {
|
|
fprintf(stderr, "place %d has 0 procs set?\n", place);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
masks[p] = mask;
|
|
}
|
|
places->num_places = num_places;
|
|
places->place_nums = place_nums;
|
|
places->masks = masks;
|
|
places->current_place = omp_get_place_num();
|
|
return places;
|
|
}
|
|
|
|
// Free the array of masks from one of: topology_alloc_type_masks()
|
|
// or topology_alloc_openmp_masks()
|
|
static void topology_free_places(place_list_t *places) {
|
|
int i;
|
|
for (i = 0; i < places->num_places; ++i)
|
|
affinity_mask_free(places->masks[i]);
|
|
free(places->masks);
|
|
free(places->place_nums);
|
|
free(places);
|
|
}
|
|
|
|
static void topology_print_places(const place_list_t *p) {
|
|
int i;
|
|
char buf[1024];
|
|
for (i = 0; i < p->num_places; ++i) {
|
|
affinity_mask_snprintf(buf, sizeof(buf), p->masks[i]);
|
|
printf("Place %d: %s\n", p->place_nums[i], buf);
|
|
}
|
|
}
|
|
|
|
// Print out an error message, possibly with two problem place lists,
|
|
// and then exit with failure
|
|
static void proc_bind_die(omp_proc_bind_t proc_bind, int T, int P,
|
|
const char *format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
const char *pb;
|
|
switch (proc_bind) {
|
|
case omp_proc_bind_false:
|
|
pb = "False";
|
|
break;
|
|
case omp_proc_bind_true:
|
|
pb = "True";
|
|
break;
|
|
case omp_proc_bind_master:
|
|
pb = "Master (Primary)";
|
|
break;
|
|
case omp_proc_bind_close:
|
|
pb = "Close";
|
|
break;
|
|
case omp_proc_bind_spread:
|
|
pb = "Spread";
|
|
break;
|
|
default:
|
|
pb = "(Unknown Proc Bind Type)";
|
|
break;
|
|
}
|
|
if (proc_bind == omp_proc_bind_spread || proc_bind == omp_proc_bind_close) {
|
|
if (T <= P) {
|
|
fprintf(stderr, "%s : (T(%d) <= P(%d)) : ", pb, T, P);
|
|
} else {
|
|
fprintf(stderr, "%s : (T(%d) > P(%d)) : ", pb, T, P);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "%s : T = %d, P = %d : ", pb, T, P);
|
|
}
|
|
vfprintf(stderr, format, args);
|
|
va_end(args);
|
|
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Return 1 on failure, 0 on success.
|
|
static void proc_bind_check(omp_proc_bind_t proc_bind,
|
|
const place_list_t *parent, place_list_t **children,
|
|
int nchildren) {
|
|
place_list_t *partition;
|
|
int T, i, j, place, low, high, first, last, count, current_place, num_places;
|
|
const int *place_nums;
|
|
int P = parent->num_places;
|
|
|
|
// Find the correct T (there could be null entries in children)
|
|
place_list_t **partitions =
|
|
(place_list_t **)malloc(sizeof(place_list_t *) * nchildren);
|
|
T = 0;
|
|
for (i = 0; i < nchildren; ++i)
|
|
if (children[i])
|
|
partitions[T++] = children[i];
|
|
// Only able to check spread, close, master (primary)
|
|
if (proc_bind != omp_proc_bind_spread && proc_bind != omp_proc_bind_close &&
|
|
proc_bind != omp_proc_bind_master)
|
|
proc_bind_die(proc_bind, T, P, NULL, NULL,
|
|
"Cannot check this proc bind type\n");
|
|
|
|
if (proc_bind == omp_proc_bind_spread) {
|
|
if (T <= P) {
|
|
// Run through each subpartition
|
|
for (i = 0; i < T; ++i) {
|
|
partition = partitions[i];
|
|
place_nums = partition->place_nums;
|
|
num_places = partition->num_places;
|
|
current_place = partition->current_place;
|
|
// Correct count?
|
|
low = P / T;
|
|
high = P / T + (P % T ? 1 : 0);
|
|
if (num_places != low && num_places != high) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"Incorrect number of places for thread %d: %d. "
|
|
"Expecting between %d and %d\n",
|
|
i, num_places, low, high);
|
|
}
|
|
// Consecutive places?
|
|
for (j = 1; j < num_places; ++j) {
|
|
if (place_nums[j] != (place_nums[j - 1] + 1) % P) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"Not consecutive places: %d, %d in partition\n",
|
|
place_nums[j - 1], place_nums[j]);
|
|
}
|
|
}
|
|
first = place_nums[0];
|
|
last = place_nums[num_places - 1];
|
|
// Primary thread executes on place of the parent thread?
|
|
if (i == 0) {
|
|
if (current_place != parent->current_place) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Primary thread not on same place (%d) as parent thread (%d)\n",
|
|
current_place, parent->current_place);
|
|
}
|
|
} else {
|
|
// Thread's current place is first place within it's partition?
|
|
if (current_place != first) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"Thread's current place (%d) is not the first place "
|
|
"in its partition [%d, %d]\n",
|
|
current_place, first, last);
|
|
}
|
|
}
|
|
// Partitions don't have intersections?
|
|
int f1 = first;
|
|
int l1 = last;
|
|
for (j = 0; j < i; ++j) {
|
|
int f2 = partitions[j]->place_nums[0];
|
|
int l2 = partitions[j]->place_nums[partitions[j]->num_places - 1];
|
|
if (f1 > l1 && f2 > l2) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"partitions intersect. [%d, %d] and [%d, %d]\n", f1,
|
|
l1, f2, l2);
|
|
}
|
|
if (f1 > l1 && f2 <= l2)
|
|
if (f1 < l2 || l1 > f2) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"partitions intersect. [%d, %d] and [%d, %d]\n", f1,
|
|
l1, f2, l2);
|
|
}
|
|
if (f1 <= l1 && f2 > l2)
|
|
if (f2 < l1 || l2 > f1) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"partitions intersect. [%d, %d] and [%d, %d]\n", f1,
|
|
l1, f2, l2);
|
|
}
|
|
if (f1 <= l1 && f2 <= l2)
|
|
if (!(f2 > l1 || l2 < f1)) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"partitions intersect. [%d, %d] and [%d, %d]\n", f1,
|
|
l1, f2, l2);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// T > P
|
|
// Each partition has only one place?
|
|
for (i = 0; i < T; ++i) {
|
|
if (partitions[i]->num_places != 1) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Incorrect number of places for thread %d: %d. Expecting 1\n", i,
|
|
partitions[i]->num_places);
|
|
}
|
|
}
|
|
// Correct number of consecutive threads per partition?
|
|
low = T / P;
|
|
high = T / P + (T % P ? 1 : 0);
|
|
for (i = 1, count = 1; i < T; ++i) {
|
|
if (partitions[i]->place_nums[0] == partitions[i - 1]->place_nums[0]) {
|
|
count++;
|
|
if (count > high) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Too many threads have place %d for their partition\n",
|
|
partitions[i]->place_nums[0]);
|
|
}
|
|
} else {
|
|
if (count < low) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Not enough threads have place %d for their partition\n",
|
|
partitions[i]->place_nums[0]);
|
|
}
|
|
count = 1;
|
|
}
|
|
}
|
|
// Primary thread executes on place of the parent thread?
|
|
current_place = partitions[0]->place_nums[0];
|
|
if (parent->current_place != -1 &&
|
|
current_place != parent->current_place) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Primary thread not on same place (%d) as parent thread (%d)\n",
|
|
current_place, parent->current_place);
|
|
}
|
|
}
|
|
} else if (proc_bind == omp_proc_bind_close ||
|
|
proc_bind == omp_proc_bind_master) {
|
|
// Check that each subpartition is the same as the parent
|
|
for (i = 0; i < T; ++i) {
|
|
partition = partitions[i];
|
|
place_nums = partition->place_nums;
|
|
num_places = partition->num_places;
|
|
current_place = partition->current_place;
|
|
if (parent->num_places != num_places) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"Number of places in subpartition (%d) does not match "
|
|
"parent (%d)\n",
|
|
num_places, parent->num_places);
|
|
}
|
|
for (j = 0; j < num_places; ++j) {
|
|
if (parent->place_nums[j] != place_nums[j]) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"Subpartition place (%d) does not match "
|
|
"parent partition place (%d)\n",
|
|
place_nums[j], parent->place_nums[j]);
|
|
}
|
|
}
|
|
}
|
|
// Find index into place_nums of current place for parent
|
|
for (j = 0; j < parent->num_places; ++j)
|
|
if (parent->place_nums[j] == parent->current_place)
|
|
break;
|
|
if (proc_bind == omp_proc_bind_close) {
|
|
if (T <= P) {
|
|
// close T <= P
|
|
// check place assignment for each thread
|
|
for (i = 0; i < T; ++i) {
|
|
partition = partitions[i];
|
|
current_place = partition->current_place;
|
|
if (current_place != parent->place_nums[j]) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Thread %d's current place (%d) is incorrect. expected %d\n", i,
|
|
current_place, parent->place_nums[j]);
|
|
}
|
|
j = (j + 1) % parent->num_places;
|
|
}
|
|
} else {
|
|
// close T > P
|
|
// check place assignment for each thread
|
|
low = T / P;
|
|
high = T / P + (T % P ? 1 : 0);
|
|
count = 1;
|
|
if (partitions[0]->current_place != parent->current_place) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Primary thread's place (%d) is not parent thread's place (%d)\n",
|
|
partitions[0]->current_place, parent->current_place);
|
|
}
|
|
for (i = 1; i < T; ++i) {
|
|
current_place = partitions[i]->current_place;
|
|
if (current_place == parent->place_nums[j]) {
|
|
count++;
|
|
if (count > high) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Too many threads have place %d for their current place\n",
|
|
current_place);
|
|
}
|
|
} else {
|
|
if (count < low) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Not enough threads have place %d for their current place\n",
|
|
parent->place_nums[j]);
|
|
}
|
|
j = (j + 1) % parent->num_places;
|
|
if (current_place != parent->place_nums[j]) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Thread %d's place (%d) is not corret. Expected %d\n", i,
|
|
partitions[i]->current_place, parent->place_nums[j]);
|
|
}
|
|
count = 1;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// proc_bind_primary
|
|
// Every thread should be assigned to the primary thread's place
|
|
for (i = 0; i < T; ++i) {
|
|
if (partitions[i]->current_place != parent->current_place) {
|
|
proc_bind_die(
|
|
proc_bind, T, P,
|
|
"Thread %d's place (%d) is not the primary thread's place (%d)\n",
|
|
i, partitions[i]->current_place, parent->current_place);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that each partition's current place is within the partition
|
|
for (i = 0; i < T; ++i) {
|
|
current_place = partitions[i]->current_place;
|
|
num_places = partitions[i]->num_places;
|
|
first = partitions[i]->place_nums[0];
|
|
last = partitions[i]->place_nums[num_places - 1];
|
|
for (j = 0; j < num_places; ++j)
|
|
if (partitions[i]->place_nums[j] == current_place)
|
|
break;
|
|
if (j == num_places) {
|
|
proc_bind_die(proc_bind, T, P,
|
|
"Thread %d's current place (%d) is not within its "
|
|
"partition [%d, %d]\n",
|
|
i, current_place, first, last);
|
|
}
|
|
}
|
|
|
|
free(partitions);
|
|
}
|
|
|
|
#endif
|