mirror of https://gitee.com/openkylin/numactl.git
571 lines
12 KiB
C
571 lines
12 KiB
C
/* Copyright (C) 2003,2004 Andi Kleen, SuSE Labs.
|
|
Test/demo program for libnuma. This is also a more or less useful benchmark
|
|
of the NUMA characteristics of your machine. It benchmarks most possible
|
|
NUMA policy memory configurations with various benchmarks.
|
|
Compile standalone with cc -O2 numademo.c -o numademo -lnuma -lm
|
|
|
|
numactl is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation; version
|
|
2.
|
|
|
|
numactl is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should find a copy of v2 of the GNU General Public License somewhere
|
|
on your Linux system; if not, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
#define _GNU_SOURCE 1
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <sys/time.h>
|
|
#include "numa.h"
|
|
#ifdef HAVE_STREAM_LIB
|
|
#include "stream_lib.h"
|
|
#endif
|
|
#ifdef HAVE_MT
|
|
#include "mt.h"
|
|
#endif
|
|
#ifdef HAVE_CLEAR_CACHE
|
|
#include "clearcache.h"
|
|
#else
|
|
static inline void clearcache(void *a, unsigned size) {}
|
|
#endif
|
|
#define FRACT_NODES 8
|
|
#define FRACT_MASKS 32
|
|
int fract_nodes;
|
|
int *node_to_use;
|
|
unsigned long msize;
|
|
|
|
/* Should get this from cpuinfo, but on !x86 it's not there */
|
|
enum {
|
|
CACHELINESIZE = 64,
|
|
};
|
|
|
|
enum test {
|
|
MEMSET = 0,
|
|
MEMCPY,
|
|
FORWARD,
|
|
BACKWARD,
|
|
STREAM,
|
|
RANDOM2,
|
|
PTRCHASE,
|
|
} thistest;
|
|
|
|
char *delim = " ";
|
|
int force;
|
|
int regression_testing=0;
|
|
|
|
char *testname[] = {
|
|
"memset",
|
|
"memcpy",
|
|
"forward",
|
|
"backward",
|
|
#ifdef HAVE_STREAM_LIB
|
|
"stream",
|
|
#endif
|
|
#ifdef HAVE_MT
|
|
"random2",
|
|
#endif
|
|
"ptrchase",
|
|
NULL,
|
|
};
|
|
|
|
void output(char *title, char *result)
|
|
{
|
|
if (!isspace(delim[0]))
|
|
printf("%s%s%s\n", title,delim, result);
|
|
else
|
|
printf("%-42s%s\n", title, result);
|
|
}
|
|
|
|
#ifdef HAVE_STREAM_LIB
|
|
void do_stream(char *name, unsigned char *mem)
|
|
{
|
|
int i;
|
|
char title[100], buf[100];
|
|
double res[STREAM_NRESULTS];
|
|
stream_verbose = 0;
|
|
clearcache(mem, msize);
|
|
stream_init(mem);
|
|
stream_test(res);
|
|
sprintf(title, "%s%s%s", name, delim, "STREAM");
|
|
buf[0] = '\0';
|
|
for (i = 0; i < STREAM_NRESULTS; i++) {
|
|
if (buf[0])
|
|
strcat(buf,delim);
|
|
sprintf(buf+strlen(buf), "%s%s%.2f%sMB/s",
|
|
stream_names[i], delim, res[i], delim);
|
|
}
|
|
output(title, buf);
|
|
clearcache(mem, msize);
|
|
}
|
|
#endif
|
|
|
|
/* Set up a randomly distributed list to fool prefetchers */
|
|
union node {
|
|
union node *next;
|
|
struct {
|
|
unsigned nexti;
|
|
unsigned val;
|
|
};
|
|
};
|
|
|
|
static int cmp_node(const void *ap, const void *bp)
|
|
{
|
|
union node *a = (union node *)ap;
|
|
union node *b = (union node *)bp;
|
|
return a->val - b->val;
|
|
}
|
|
|
|
void **ptrchase_init(unsigned char *mem)
|
|
{
|
|
long i;
|
|
union node *nodes = (union node *)mem;
|
|
long nmemb = msize / sizeof(union node);
|
|
srand(1234);
|
|
for (i = 0; i < nmemb; i++) {
|
|
nodes[i].val = rand();
|
|
nodes[i].nexti = i + 1;
|
|
}
|
|
qsort(nodes, nmemb, sizeof(union node), cmp_node);
|
|
for (i = 0; i < nmemb; i++) {
|
|
union node *n = &nodes[i];
|
|
n->next = n->nexti >= nmemb ? NULL : &nodes[n->nexti];
|
|
}
|
|
return (void **)nodes;
|
|
}
|
|
|
|
static inline unsigned long long timerfold(struct timeval *tv)
|
|
{
|
|
return tv->tv_sec * 1000000ULL + tv->tv_usec;
|
|
}
|
|
|
|
#define LOOPS 10
|
|
|
|
void memtest(char *name, unsigned char *mem)
|
|
{
|
|
long k;
|
|
struct timeval start, end, res;
|
|
unsigned long long max, min, sum, r;
|
|
int i;
|
|
char title[128], result[128];
|
|
|
|
if (!mem) {
|
|
fprintf(stderr,
|
|
"Failed to allocate %lu bytes of memory. Test \"%s\" exits.\n",
|
|
msize, name);
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_STREAM_LIB
|
|
if (thistest == STREAM) {
|
|
do_stream(name, mem);
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
max = 0;
|
|
min = ~0UL;
|
|
sum = 0;
|
|
|
|
/*
|
|
* Note: 0th pass allocates the pages, don't measure
|
|
*/
|
|
for (i = 0; i < LOOPS+1; i++) {
|
|
clearcache(mem, msize);
|
|
switch (thistest) {
|
|
case PTRCHASE:
|
|
{
|
|
void **ptr;
|
|
ptr = ptrchase_init(mem);
|
|
gettimeofday(&start,NULL);
|
|
while (*ptr)
|
|
ptr = (void **)*ptr;
|
|
gettimeofday(&end,NULL);
|
|
/* Side effect to trick the optimizer */
|
|
*ptr = "bla";
|
|
break;
|
|
}
|
|
|
|
case MEMSET:
|
|
gettimeofday(&start,NULL);
|
|
memset(mem, 0xff, msize);
|
|
gettimeofday(&end,NULL);
|
|
break;
|
|
|
|
case MEMCPY:
|
|
gettimeofday(&start,NULL);
|
|
memcpy(mem, mem + msize/2, msize/2);
|
|
gettimeofday(&end,NULL);
|
|
break;
|
|
|
|
case FORWARD:
|
|
/* simple kernel to just fetch cachelines and write them back.
|
|
will trigger hardware prefetch */
|
|
gettimeofday(&start,NULL);
|
|
for (k = 0; k < msize; k+=CACHELINESIZE)
|
|
mem[k]++;
|
|
gettimeofday(&end,NULL);
|
|
break;
|
|
|
|
case BACKWARD:
|
|
gettimeofday(&start,NULL);
|
|
for (k = msize-5; k > 0; k-=CACHELINESIZE)
|
|
mem[k]--;
|
|
gettimeofday(&end,NULL);
|
|
break;
|
|
|
|
#ifdef HAVE_MT
|
|
case RANDOM2:
|
|
{
|
|
unsigned * __restrict m = (unsigned *)mem;
|
|
unsigned max = msize / sizeof(unsigned);
|
|
unsigned mask;
|
|
|
|
mt_init();
|
|
mask = 1;
|
|
while (mask < max)
|
|
mask = (mask << 1) | 1;
|
|
/*
|
|
* There's no guarantee all memory is touched, but
|
|
* we assume (hope) that the distribution of the MT
|
|
* is good enough to touch most.
|
|
*/
|
|
gettimeofday(&start,NULL);
|
|
for (k = 0; k < max; k++) {
|
|
unsigned idx = mt_random() & mask;
|
|
if (idx >= max)
|
|
idx -= max;
|
|
m[idx]++;
|
|
}
|
|
gettimeofday(&end,NULL);
|
|
}
|
|
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!i)
|
|
continue; /* don't count allocation pass */
|
|
|
|
timersub(&end, &start, &res);
|
|
r = timerfold(&res);
|
|
if (r > max) max = r;
|
|
if (r < min) min = r;
|
|
sum += r;
|
|
}
|
|
sprintf(title, "%s%s%s", name, delim, testname[thistest]);
|
|
#define H(t) (((double)msize) / ((double)t))
|
|
#define D3 delim,delim,delim
|
|
sprintf(result, "Avg%s%.2f%sMB/s%sMax%s%.2f%sMB/s%sMin%s%.2f%sMB/s",
|
|
delim,
|
|
H(sum/LOOPS),
|
|
D3,
|
|
H(min),
|
|
D3,
|
|
H(max),
|
|
delim);
|
|
#undef H
|
|
#undef D3
|
|
output(title,result);
|
|
|
|
#ifdef HAVE_STREAM_LIB
|
|
out:
|
|
#endif
|
|
/* Just to make sure that when we switch CPUs that the old guy
|
|
doesn't still keep it around. */
|
|
clearcache(mem, msize);
|
|
|
|
numa_free(mem, msize);
|
|
}
|
|
|
|
int popcnt(unsigned long val)
|
|
{
|
|
int i = 0, cnt = 0;
|
|
while (val >> i) {
|
|
if ((1UL << i) & val)
|
|
cnt++;
|
|
i++;
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
int max_node, numnodes;
|
|
|
|
void get_node_list()
|
|
{
|
|
int a, got_nodes = 0;
|
|
long free_node_sizes;
|
|
|
|
numnodes = numa_num_configured_nodes();
|
|
node_to_use = (int *)malloc(numnodes * sizeof(int));
|
|
max_node = numa_max_node();
|
|
for (a = 0; a <= max_node; a++) {
|
|
if(numa_node_size(a, &free_node_sizes) != -1)
|
|
node_to_use[got_nodes++] = a;
|
|
}
|
|
}
|
|
|
|
void test(enum test type)
|
|
{
|
|
unsigned long mask;
|
|
int i, k;
|
|
char buf[512];
|
|
struct bitmask *nodes;
|
|
|
|
nodes = numa_allocate_nodemask();
|
|
thistest = type;
|
|
|
|
if (regression_testing) {
|
|
printf("\nTest %s doing 1 of %d nodes and 1 of %d masks.\n",
|
|
testname[thistest], fract_nodes, FRACT_MASKS);
|
|
}
|
|
|
|
memtest("memory with no policy", numa_alloc(msize));
|
|
memtest("local memory", numa_alloc_local(msize));
|
|
|
|
memtest("memory interleaved on all nodes", numa_alloc_interleaved(msize));
|
|
for (i = 0; i < numnodes; i++) {
|
|
if (regression_testing && (node_to_use[i] % fract_nodes)) {
|
|
/* for regression testing (-t) do only every eighth node */
|
|
continue;
|
|
}
|
|
sprintf(buf, "memory on node %d", node_to_use[i]);
|
|
memtest(buf, numa_alloc_onnode(msize, node_to_use[i]));
|
|
}
|
|
|
|
for (mask = 1, i = 0; mask < (1UL<<numnodes); mask++, i++) {
|
|
int w;
|
|
char buf2[20];
|
|
if (popcnt(mask) == 1)
|
|
continue;
|
|
if (regression_testing && (i > 50)) {
|
|
break;
|
|
}
|
|
if (regression_testing && (i % FRACT_MASKS)) {
|
|
/* for regression testing (-t)
|
|
do only every 32nd mask permutation */
|
|
continue;
|
|
}
|
|
numa_bitmask_clearall(nodes);
|
|
for (w = 0; mask >> w; w++) {
|
|
if ((mask >> w) & 1)
|
|
numa_bitmask_setbit(nodes, w);
|
|
}
|
|
|
|
sprintf(buf, "memory interleaved on");
|
|
for (k = 0; k < numnodes; k++)
|
|
if ((1UL<<node_to_use[k]) & mask) {
|
|
sprintf(buf2, " %d", node_to_use[k]);
|
|
strcat(buf, buf2);
|
|
}
|
|
memtest(buf, numa_alloc_interleaved_subset(msize, nodes));
|
|
}
|
|
|
|
for (i = 0; i < numnodes; i++) {
|
|
if (regression_testing && (node_to_use[i] % fract_nodes)) {
|
|
/* for regression testing (-t) do only every eighth node */
|
|
continue;
|
|
}
|
|
printf("setting preferred node to %d\n", node_to_use[i]);
|
|
numa_set_preferred(node_to_use[i]);
|
|
memtest("memory without policy", numa_alloc(msize));
|
|
}
|
|
|
|
numa_set_interleave_mask(numa_all_nodes_ptr);
|
|
memtest("manual interleaving to all nodes", numa_alloc(msize));
|
|
|
|
if (numnodes > 0) {
|
|
numa_bitmask_clearall(nodes);
|
|
numa_bitmask_setbit(nodes, 0);
|
|
numa_bitmask_setbit(nodes, 1);
|
|
numa_set_interleave_mask(nodes);
|
|
memtest("manual interleaving on node 0/1", numa_alloc(msize));
|
|
printf("current interleave node %d\n", numa_get_interleave_node());
|
|
}
|
|
|
|
numa_set_interleave_mask(numa_no_nodes_ptr);
|
|
|
|
nodes = numa_allocate_nodemask();
|
|
|
|
for (i = 0; i < numnodes; i++) {
|
|
int oldhn = numa_preferred();
|
|
|
|
if (regression_testing && (node_to_use[i] % fract_nodes)) {
|
|
/* for regression testing (-t) do only every eighth node */
|
|
continue;
|
|
}
|
|
numa_run_on_node(node_to_use[i]);
|
|
printf("running on node %d, preferred node %d\n",node_to_use[i], oldhn);
|
|
|
|
memtest("local memory", numa_alloc_local(msize));
|
|
|
|
memtest("memory interleaved on all nodes",
|
|
numa_alloc_interleaved(msize));
|
|
|
|
if (numnodes >= 2) {
|
|
numa_bitmask_clearall(nodes);
|
|
numa_bitmask_setbit(nodes, 0);
|
|
numa_bitmask_setbit(nodes, 1);
|
|
memtest("memory interleaved on node 0/1",
|
|
numa_alloc_interleaved_subset(msize, nodes));
|
|
}
|
|
|
|
for (k = 0; k < numnodes; k++) {
|
|
if (node_to_use[k] == node_to_use[i])
|
|
continue;
|
|
if (regression_testing && (node_to_use[k] % fract_nodes)) {
|
|
/* for regression testing (-t)
|
|
do only every eighth node */
|
|
continue;
|
|
}
|
|
sprintf(buf, "alloc on node %d", node_to_use[k]);
|
|
numa_bitmask_clearall(nodes);
|
|
numa_bitmask_setbit(nodes, node_to_use[k]);
|
|
numa_set_membind(nodes);
|
|
memtest(buf, numa_alloc(msize));
|
|
numa_set_membind(numa_all_nodes_ptr);
|
|
}
|
|
|
|
numa_set_localalloc();
|
|
memtest("local allocation", numa_alloc(msize));
|
|
|
|
numa_set_preferred((node_to_use[i]+1) % numnodes );
|
|
memtest("setting wrong preferred node", numa_alloc(msize));
|
|
numa_set_preferred(node_to_use[i]);
|
|
memtest("setting correct preferred node", numa_alloc(msize));
|
|
numa_set_preferred(-1);
|
|
if (!delim[0])
|
|
printf("\n\n\n");
|
|
}
|
|
|
|
/* numa_run_on_node_mask is not tested */
|
|
}
|
|
|
|
void usage(void)
|
|
{
|
|
int i;
|
|
printf("usage: numademo [-S] [-f] [-c] [-e] [-t] msize[kmg] {tests}\nNo tests means run all.\n");
|
|
printf("-c output CSV data. -f run even without NUMA API. -S run stupid tests. -e exit on error\n");
|
|
printf("-t regression test; do not run all node combinations\n");
|
|
printf("valid tests:");
|
|
for (i = 0; testname[i]; i++)
|
|
printf(" %s", testname[i]);
|
|
putchar('\n');
|
|
exit(1);
|
|
}
|
|
|
|
/* duplicated to make numademo standalone */
|
|
long memsize(char *s)
|
|
{
|
|
char *end;
|
|
long length = strtoul(s,&end,0);
|
|
switch (toupper(*end)) {
|
|
case 'G': length *= 1024; /*FALL THROUGH*/
|
|
case 'M': length *= 1024; /*FALL THROUGH*/
|
|
case 'K': length *= 1024; break;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
int main(int ac, char **av)
|
|
{
|
|
int simple_tests = 0;
|
|
|
|
while (av[1] && av[1][0] == '-') {
|
|
ac--;
|
|
switch (av[1][1]) {
|
|
case 'c':
|
|
delim = ",";
|
|
break;
|
|
case 'f':
|
|
force = 1;
|
|
break;
|
|
case 'S':
|
|
simple_tests = 1;
|
|
break;
|
|
case 'e':
|
|
numa_exit_on_error = 1;
|
|
numa_exit_on_warn = 1;
|
|
break;
|
|
case 't':
|
|
regression_testing = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
break;
|
|
}
|
|
++av;
|
|
}
|
|
|
|
if (!av[1])
|
|
usage();
|
|
|
|
if (numa_available() < 0) {
|
|
printf("your system does not support the numa API.\n");
|
|
if (!force)
|
|
exit(1);
|
|
}
|
|
get_node_list();
|
|
printf("%d nodes available\n", numnodes);
|
|
fract_nodes = (((numnodes-1)/8)*2) + FRACT_NODES;
|
|
|
|
if (numnodes <= 3)
|
|
regression_testing = 0; /* set -t auto-off for small systems */
|
|
|
|
msize = memsize(av[1]);
|
|
|
|
if (!msize)
|
|
usage();
|
|
|
|
#ifdef HAVE_STREAM_LIB
|
|
stream_setmem(msize);
|
|
#endif
|
|
|
|
if (av[2] == NULL) {
|
|
test(MEMSET);
|
|
test(MEMCPY);
|
|
if (simple_tests) {
|
|
test(FORWARD);
|
|
test(BACKWARD);
|
|
}
|
|
#ifdef HAVE_MT
|
|
test(RANDOM2);
|
|
#endif
|
|
#ifdef HAVE_STREAM_LIB
|
|
test(STREAM);
|
|
#endif
|
|
if (msize >= sizeof(union node)) {
|
|
test(PTRCHASE);
|
|
} else {
|
|
fprintf(stderr, "You must set msize at least %lu bytes for ptrchase test.\n",
|
|
sizeof(union node));
|
|
exit(1);
|
|
}
|
|
} else {
|
|
int k;
|
|
for (k = 2; k < ac; k++) {
|
|
int i;
|
|
int found = 0;
|
|
for (i = 0; testname[i]; i++) {
|
|
if (!strcmp(testname[i],av[k])) {
|
|
test(i);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
fprintf(stderr,"unknown test `%s'\n", av[k]);
|
|
usage();
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|