Released under the GNU GPL version 2 or later
*/
+
+/*
+ this program is designed to test the relative performance of threads/processes
+ for operations typically performed by fileserving applications.
+*/
+
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
+#include <signal.h>
#include <string.h>
#include <dirent.h>
-#include <signal.h>
-#include <sys/ipc.h>
-#include <sys/shm.h>
+
+#ifndef NO_THREADS
#include <pthread.h>
+#endif
+
+/* this contains per-task data */
+static struct {
+ char *dname;
+ char *fname;
+} *id_data;
+
+/* these pipes are used for synchronised startup of the tasks */
+static struct barrier {
+ int fd1[2];
+ int fd2[2];
+} barriers[2];
+
+/* setup a barrier */
+static void barrier_setup(struct barrier *b)
+{
+ if (pipe(b->fd1) != 0 || pipe(b->fd2) != 0) {
+ fprintf(stderr,"Barrier setup failed\n");
+ exit(1);
+ }
+}
+
+/* cleanup the barrier pipes */
+static void barrier_cleanup(struct barrier *b)
+{
+ close(b->fd1[0]);
+ close(b->fd1[1]);
+ close(b->fd2[0]);
+ close(b->fd2[1]);
+}
-static void start_timer(void);
+/* wait for the parent to signal startup */
+static void barrier_wait(struct barrier *b)
+{
+ char c = 0;
-/* this shared memory area is used to synchronise the startup times of
- the tasks */
-static volatile int *startup_ptr;
-static int wait_fd;
+ if (write(b->fd1[1], &c, 1) != 1 ||
+ read(b->fd2[0], &c, 1) != 1) {
+ fprintf(stderr, "Barrier wait failed\n");
+ exit(1);
+ }
+}
-/* wait for the startup pointer */
-static void startup_wait(int id)
+/* synchronise children. Return the amount of time since the last
+ barrier */
+static double barrier_parent(struct barrier *b, int nprocs)
{
- fd_set s;
- startup_ptr[id] = 1;
-
- /* we wait till the wait_fd is readable - this gives
- a nice in-kernel barrier wait */
- do {
- FD_ZERO(&s);
- FD_SET(wait_fd, &s);
- } while (select(wait_fd+1, &s, NULL, NULL, NULL) != 1);
+ char *s = calloc(nprocs, 1);
+ int i, nwritten=0;
+ char c = 0;
+ double t;
+ static struct timeval tp1;
+ struct timeval tp2;
+
+ for (i=0;i<nprocs;i++) {
+ while (read(b->fd1[0], &c, 1) != 1) ;
+ }
+
+ /* putting the timer here prevents problems with the parent getting
+ rescheduled after the write */
+ gettimeofday(&tp2,NULL);
+ t = (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) -
+ (tp1.tv_sec + (tp1.tv_usec*1.0e-6));
+ gettimeofday(&tp1,NULL);
+
+ while (nwritten != nprocs) {
+ int n = write(b->fd2[1], s, nprocs-nwritten);
+ if (n <= 0) {
+ fprintf(stderr, "Barrier parent failed\n");
+ exit(1);
+ }
+ nwritten += n;
+ }
+ free(s);
+ return t;
}
+#ifndef NO_THREADS
/*
create a thread with initial function fn(private)
*/
{
return pthread_join(id, NULL);
}
+#endif
/*
return 0;
}
-
+#ifndef NO_THREADS
/* run a function under a set of threads */
-static void run_threads(int nthreads, void *(*fn)(int ))
+static double run_threads(int nthreads, void *(*fn)(int ))
{
int i;
- pthread_t *ids = malloc(sizeof(*ids) * nthreads);
- int fd[2];
- char c = 0;
-
- if (pipe(fd) != 0) {
- fprintf(stderr,"Pipe failed\n");
- exit(1);
- }
- wait_fd = fd[0];
+ pthread_t *ids = calloc(sizeof(*ids), nthreads);
+ double t;
- for (i=0;i<nthreads;i++) {
- startup_ptr[i] = 0;
- }
+ barrier_setup(&barriers[0]);
+ barrier_setup(&barriers[1]);
for (i=0;i<nthreads;i++) {
ids[i] = thread_start(fn, i);
}
- for (i=0;i<nthreads;i++) {
- while (startup_ptr[i] != 1) usleep(1);
- }
-
- start_timer();
-
- write(fd[1], &c, 1);
+ barrier_parent(&barriers[0], nthreads);
+ t = barrier_parent(&barriers[1], nthreads);
for (i=0;i<nthreads;i++) {
int rc;
}
}
- close(fd[0]);
- close(fd[1]);
+ barrier_cleanup(&barriers[0]);
+ barrier_cleanup(&barriers[1]);
+
+ free(ids);
+
+ return t;
}
+#endif
/* run a function under a set of processes */
-static void run_processes(int nprocs, void *(*fn)(int ))
+static double run_processes(int nprocs, void *(*fn)(int ))
{
int i;
- pid_t *ids = malloc(sizeof(*ids) * nprocs);
- int fd[2];
- char c = 0;
+ pid_t *ids = calloc(sizeof(*ids), nprocs);
+ double t;
- if (pipe(fd) != 0) {
- fprintf(stderr,"Pipe failed\n");
- exit(1);
- }
- wait_fd = fd[0];
-
- for (i=0;i<nprocs;i++) {
- startup_ptr[i] = 0;
- }
+ barrier_setup(&barriers[0]);
+ barrier_setup(&barriers[1]);
for (i=0;i<nprocs;i++) {
ids[i] = proc_start(fn, i);
}
}
- for (i=0;i<nprocs;i++) {
- while (startup_ptr[i] != 1) usleep(1);
- }
-
- start_timer();
-
- write(fd[1], &c, 1);
+ barrier_parent(&barriers[0], nprocs);
+ t = barrier_parent(&barriers[1], nprocs);
for (i=0;i<nprocs;i++) {
int rc;
}
}
- close(fd[0]);
- close(fd[1]);
+ barrier_cleanup(&barriers[0]);
+ barrier_cleanup(&barriers[1]);
+
+ free(ids);
+
+ return t;
}
int i, j;
void *ptrs[NMALLOCS];
- startup_wait(id);
+ barrier_wait(&barriers[0]);
for (j=0;j<500;j++) {
for (i=1;i<NMALLOCS;i++) {
free(ptrs[i]);
}
}
+
+ barrier_wait(&barriers[1]);
return NULL;
}
/* we use less than 1 page to prevent page table games */
char buf[4095];
- startup_wait(id);
+ barrier_wait(&barriers[0]);
fd_in = open("/dev/zero", O_RDONLY);
fd_out = open("/dev/null", O_WRONLY);
close(fd_in);
close(fd_out);
+ barrier_wait(&barriers[1]);
+
return NULL;
}
static void *test_stat(int id)
{
int i;
- char fname[60];
- char dname[30];
- startup_wait(id);
+ barrier_wait(&barriers[0]);
- sprintf(dname, "testd_%d", id);
- rmdir(dname);
+ for (i=0;i<30000;i++) {
+ struct stat st;
+ if (stat(id_data[id].dname, &st) != 0) goto failed;
+ if (stat(id_data[id].fname, &st) == 0) goto failed;
+ }
- if (mkdir(dname, 0777) != 0) goto failed;
+ barrier_wait(&barriers[1]);
- sprintf(fname, "%s/test%d.dat", dname, id);
+ return NULL;
- for (i=0;i<10000;i++) {
+failed:
+ fprintf(stderr,"stat failed\n");
+ exit(1);
+}
+
+/***********************************************************************
+test fstat() operations
+************************************************************************/
+static void *test_fstat(int id)
+{
+ int i, fd;
+
+ barrier_wait(&barriers[0]);
+
+ fd = open(id_data[id].fname, O_RDWR|O_CREAT, 0600);
+ if (fd == -1) goto failed;
+
+ for (i=0;i<1000000;i++) {
struct stat st;
- if (stat(dname, &st) != 0) goto failed;
- if (stat(fname, &st) == 0) goto failed;
+ if (fstat(fd, &st) != 0) goto failed;
}
- if (rmdir(dname) != 0) goto failed;
-
+ close(fd);
+ unlink(id_data[id].fname);
+
+ barrier_wait(&barriers[1]);
+
return NULL;
failed:
- fprintf(stderr,"stat failed\n");
+ fprintf(stderr,"fstat failed\n");
exit(1);
}
static void *test_dir(int id)
{
int i;
- char dname[30];
-
- startup_wait(id);
- sprintf(dname, "testd_%d", id);
- rmdir(dname);
-
- if (mkdir(dname, 0777) != 0) goto failed;
+ barrier_wait(&barriers[0]);
for (i=0;i<2000;i++) {
- DIR *d = opendir(dname);
+ DIR *d = opendir(id_data[id].dname);
if (!d) goto failed;
while (readdir(d)) {} ;
closedir(d);
}
- if (rmdir(dname) != 0) goto failed;
-
+ barrier_wait(&barriers[1]);
return NULL;
failed:
}
/***********************************************************************
-test directory operations on a single directory
+test directory operations
************************************************************************/
static void *test_dirsingle(int id)
{
int i;
- startup_wait(id);
+ barrier_wait(&barriers[0]);
for (i=0;i<2000;i++) {
DIR *d = opendir(".");
closedir(d);
}
+ barrier_wait(&barriers[1]);
return NULL;
failed:
- fprintf(stderr,"dir failed\n");
+ fprintf(stderr,"dirsingle failed\n");
exit(1);
}
+
/***********************************************************************
test create/unlink operations
************************************************************************/
static void *test_create(int id)
{
int i;
- char fname[60];
- char dname[30];
-
- startup_wait(id);
-
- sprintf(dname, "testd_%d", id);
- rmdir(dname);
-
- if (mkdir(dname, 0777) != 0) goto failed;
- sprintf(fname, "%s/test%d.dat", dname, id);
+ barrier_wait(&barriers[0]);
- for (i=0;i<1000;i++) {
+ for (i=0;i<3000;i++) {
int fd;
- fd = open(fname, O_CREAT|O_TRUNC|O_RDWR, 0666);
+ fd = open(id_data[id].fname, O_CREAT|O_TRUNC|O_RDWR, 0666);
if (fd == -1) goto failed;
- if (open(fname, O_CREAT|O_TRUNC|O_RDWR|O_EXCL, 0666) != -1) goto failed;
+ if (open(id_data[id].fname, O_CREAT|O_TRUNC|O_RDWR|O_EXCL, 0666) != -1) goto failed;
close(fd);
- if (unlink(fname) != 0) goto failed;
+ if (unlink(id_data[id].fname) != 0) goto failed;
}
- if (rmdir(dname) != 0) goto failed;
-
+ barrier_wait(&barriers[1]);
return NULL;
failed:
static void *test_lock(int id)
{
int i;
- char fname[30];
int fd;
- startup_wait(id);
-
- sprintf(fname, "test%d.dat", id);
+ barrier_wait(&barriers[0]);
- fd = open(fname, O_CREAT|O_RDWR, 0666);
+ fd = open(id_data[id].fname, O_CREAT|O_RDWR, 0666);
if (fd == -1) goto failed;
- unlink(fname);
+ unlink(id_data[id].fname);
for (i=0;i<20000;i++) {
struct flock lock;
close(fd);
+ barrier_wait(&barriers[1]);
return NULL;
failed:
************************************************************************/
static void *test_noop(int id)
{
- startup_wait(id);
+ barrier_wait(&barriers[0]);
+ barrier_wait(&barriers[1]);
return NULL;
}
if (t[i] > maxt) maxt = t[i];
total += t[i];
}
- printf("%s %.2f +/- %.2f seconds\n", name, total/nrepeats, (maxt-mint)/2);
-}
-
-
-static struct timeval tp1,tp2;
-
-static void start_timer(void)
-{
- gettimeofday(&tp1,NULL);
-}
-
-static double end_timer(void)
-{
- gettimeofday(&tp2,NULL);
- return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) -
- (tp1.tv_sec + (tp1.tv_usec*1.0e-6));
-}
-
-/*
- this is used to create the synchronised startup area
-*/
-void *shm_setup(int size)
-{
- int shmid;
- void *ret;
-
- shmid = shmget(IPC_PRIVATE, size, SHM_R | SHM_W);
- if (shmid == -1) {
- fprintf(stderr, "can't get shared memory\n");
- return NULL;
- }
- ret = (void *)shmat(shmid, 0, 0);
- if (!ret || ret == (void *)-1) {
- fprintf(stderr, "can't attach to shared memory\n");
- return NULL;
- }
- shmctl(shmid, IPC_RMID, 0);
- return ret;
+ printf("%s %5.2f +/- %.2f seconds\n", name, total/nrepeats, (maxt-mint)/2);
}
{"malloc", test_malloc},
{"readwrite", test_readwrite},
{"stat", test_stat},
+ {"fstat", test_fstat},
{"dir", test_dir},
{"dirsingle", test_dirsingle},
{"create", test_create},
tname = argv[2];
}
- startup_ptr = shm_setup(sizeof(*startup_ptr) * nprocs);
- if (!startup_ptr) {
+ id_data = calloc(nprocs, sizeof(*id_data));
+ if (!id_data) {
exit(1);
}
+#ifndef NO_THREADS
+ printf("NOTE! for accurate process results please compile with -DNO_THREADS and don't link to -lpthread\n\n");
+#endif
+
+ for (i=0;i<nprocs;i++) {
+ char s[30];
+ sprintf(s, "testd_%d", i);
+ id_data[i].dname = strdup(s);
+
+ sprintf(s, "%s/test.dat", id_data[i].dname);
+ id_data[i].fname = strdup(s);
+
+ rmdir(id_data[i].dname);
+ if (mkdir(id_data[i].dname, 0777) != 0) {
+ fprintf(stderr, "Failed to create %s\n", id_data[i].dname);
+ exit(1);
+ }
+
+ unlink(id_data[i].fname);
+ }
+
for (i=0;tests[i].name;i++) {
double t_threads[NREPEATS];
double t_processes[NREPEATS];
continue;
}
- printf("Running test '%s'\n", tests[i].name);
+ printf("Running test '%s' with %d tasks\n", tests[i].name, nprocs);
for (j=0;j<NREPEATS;j++) {
- start_timer();
- run_threads(nprocs, tests[i].fn);
- t_threads[j] = end_timer();
-
- start_timer();
- run_processes(nprocs, tests[i].fn);
- t_processes[j] = end_timer();
+#ifndef NO_THREADS
+ t_threads[j] = run_threads(nprocs, tests[i].fn);
+#endif
+ t_processes[j] = run_processes(nprocs, tests[i].fn);
}
+#ifndef NO_THREADS
show_result("Threads ", t_threads, NREPEATS);
+#endif
show_result("Processes", t_processes, NREPEATS);
printf("\n");
fflush(stdout);
}
+ for (i=0;i<nprocs;i++) {
+ if (rmdir(id_data[i].dname) != 0) {
+ fprintf(stderr, "Failed to delete %s\n", id_data[i].dname);
+ exit(1);
+ }
+ }
+
+ for (i=0;i<nprocs;i++) {
+ free(id_data[i].dname);
+ free(id_data[i].fname);
+ }
+ free(id_data);
+
return 0;
}