time_thread.c (8950B)
1 /* 2 Copyright 2010-2011, D. E. Shaw Research. 3 All rights reserved. 4 5 Redistribution and use in source and binary forms, with or without 6 modification, are permitted provided that the following conditions are 7 met: 8 9 * Redistributions of source code must retain the above copyright 10 notice, this list of conditions, and the following disclaimer. 11 12 * Redistributions in binary form must reproduce the above copyright 13 notice, this list of conditions, and the following disclaimer in the 14 documentation and/or other materials provided with the distribution. 15 16 * Neither the name of D. E. Shaw Research nor the names of its 17 contributors may be used to endorse or promote products derived from 18 this software without specific prior written permission. 19 20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 /* 33 * Pthreads test and timing harness for Random123 RNGs. 34 * Uses macros and util_expandtpl.h to "templatize" over all the 35 * different permutations of RNGs and NxW and R. 36 */ 37 38 #include "util.h" 39 #include <sys/stat.h> 40 41 #include "Random123/philox.h" 42 #include "Random123/threefry.h" 43 #include "Random123/ars.h" 44 #include "Random123/aes.h" 45 46 #include "time_misc.h" 47 #include "util_print.h" 48 #include "util.h" 49 #include <pthread.h> 50 51 /* 52 * Main thread initializes thread_info[i].started to zero, .tid to its own 53 * pthread_self() as a placeholder. 54 * Child #i sets thread_info[i].tid to pthread_self() and then 55 * sets .started. Only child #i ever writes to thread_info[i], and 56 * does so only once. So searching through thread_info is race-free 57 * for get_global_id, since it only ever looking for its own pthread_self 58 * anyway. The write to started needs to be atomic. 59 * This is all so that we can use the same kernel as OpenCL/CUDA. 60 * Note that parent keeps its own copy of thread ids returned 61 * by pthread_create in tids[] to avoid any races with thread_info. 62 */ 63 typedef struct { 64 int started; /* started == 1 means tid contains pthread_self() of thread */ 65 pthread_t tid; 66 } ThreadInfo; 67 static volatile ThreadInfo *thread_info; /* thread_id state, one per thread */ \ 68 static int thread_count = 12; 69 70 /* Linear search should be fast enough for small thread count... */ 71 R123_STATIC_INLINE int get_global_id(int x) 72 { 73 int i; 74 pthread_t me = pthread_self(); 75 (void)x; /* why is this an arg? */ 76 for (i = 0; i < thread_count; i++) { 77 if (thread_info[i].started && pthread_equal(me, thread_info[i].tid)) 78 return i; 79 } 80 fprintf(stderr, "could not find thread %lu\n", (unsigned long) me); 81 pthread_exit(NULL); 82 } 83 84 #define KERNEL R123_STATIC_INLINE 85 #include "time_random123.h" 86 87 #define TEST_TPL(NAME, N, W, R) \ 88 typedef struct { \ 89 unsigned kcount; \ 90 NAME##N##x##W##_ukey_t ukey; \ 91 NAME##N##x##W##_ctr_t ctr; \ 92 NAME##N##x##W##_ctr_t *octrs; \ 93 } ThreadData_##NAME##N##x##W##_##R; \ 94 \ 95 typedef struct { \ 96 ThreadData_##NAME##N##x##W##_##R *tp; \ 97 volatile ThreadInfo *tip; \ 98 } ThreadArg_##NAME##N##x##W##_##R; \ 99 \ 100 /* thread_run is launched in a new thread by pthread_create */ \ 101 void *thread_run_##NAME##N##x##W##_##R(void *p) \ 102 { \ 103 ThreadArg_##NAME##N##x##W##_##R *ta = p; \ 104 ThreadData_##NAME##N##x##W##_##R *tp = ta->tp; \ 105 volatile ThreadInfo *tip = ta->tip; \ 106 /* store our thread id for use by get_global_info */ \ 107 tip->tid = pthread_self(); \ 108 tip->started = 1; \ 109 test_##NAME##N##x##W##_##R(tp->kcount, tp->ctr, tp->ukey, tp->octrs); \ 110 return tp; \ 111 }\ 112 void NAME##N##x##W##_##R(NAME##N##x##W##_ctr_t ctr, NAME##N##x##W##_ukey_t ukey, NAME##N##x##W##_ctr_t kactr, unsigned count, void* unused) \ 113 { \ 114 const char *kernelname = #NAME #N "x" #W "_" #R; \ 115 double cur_time; \ 116 int i, n, niterations = numtrials; /* we make niterations + 2 (warmup, overhead) calls to the kernel */ \ 117 double basetime = 0., dt = 0., mindt = 0.; \ 118 ThreadData_##NAME##N##x##W##_##R td; /* same for all threads */ \ 119 ThreadArg_##NAME##N##x##W##_##R *tap; /* args for thread_run, 1 per thread */ \ 120 pthread_t me = pthread_self(); /* parent thread id */ \ 121 pthread_t *tids; /* array of child thread ids */ \ 122 void *vp; /* return from join */ \ 123 (void)unused; /* suppress warning */ \ 124 CHECKNOTZERO(thread_info = (ThreadInfo *) malloc(sizeof(thread_info[0])*thread_count)); \ 125 CHECKNOTZERO(tap = (ThreadArg_##NAME##N##x##W##_##R *) malloc(sizeof(tap[0])*thread_count)); \ 126 CHECKNOTZERO(tids = (pthread_t *) malloc(sizeof(tids[0])*thread_count)); \ 127 for (i = 0; i < thread_count; i++) { \ 128 thread_info[i].started = 0; \ 129 thread_info[i].tid = me; \ 130 tap[i].tip = &thread_info[i]; \ 131 tap[i].tp = &td; \ 132 } \ 133 CHECKNOTZERO(td.octrs = (NAME##N##x##W##_ctr_t *) malloc(sizeof(td.octrs[0])*thread_count)); \ 134 td.ukey = ukey; \ 135 td.ctr = ctr; \ 136 td.kcount = 0; \ 137 for (n = -2; n < niterations; n++) { \ 138 if (n == -2) { \ 139 if (count == 0) { \ 140 /* try to set a good guess for count */ \ 141 count = 1000000; \ 142 dprintf(("starting with count = %u\n", count)); \ 143 } \ 144 td.kcount = count; \ 145 } else if (n == -1) { \ 146 /* use first iteration time to calibrate count to get approximately sec_per_trial */ \ 147 if (count > 1) { \ 148 count = (unsigned)(count * sec_per_trial / dt); \ 149 dprintf(("scaled count = %u\n", count)); \ 150 } \ 151 /* second iteration is to calculate overhead after warmup */ \ 152 td.kcount = 1; \ 153 } else if (n == 0) { \ 154 int xj; \ 155 /* Check that we got the expected value */ \ 156 for (xj = 0; xj < N; xj++) { \ 157 if (kactr.v[xj] != td.octrs[0].v[xj]) { \ 158 printf("%s mismatch: xj = %d, expected\n", kernelname, xj); \ 159 printline_##NAME##N##x##W##_##R(ukey, ctr, &kactr, 1); \ 160 printf(" but got\n"); \ 161 printline_##NAME##N##x##W##_##R(ukey, ctr, td.octrs, 1); \ 162 if(!debug) exit(1); \ 163 else break; \ 164 } else { \ 165 dprintf(("%s matched word %d\n", kernelname, xj)); \ 166 } \ 167 } \ 168 basetime = dt; \ 169 if (verbose) { \ 170 dprintf(("%s %.3f secs\n", kernelname, basetime)); \ 171 printline_##NAME##N##x##W##_##R(ukey, ctr, td.octrs, thread_count); \ 172 } \ 173 td.kcount = count + 1; \ 174 } \ 175 dprintf(("call function %s\n", kernelname)); \ 176 (void)timer(&cur_time); \ 177 for (i = 0; i < thread_count; i++) { \ 178 CHECKZERO(pthread_create(&tids[i], NULL, thread_run_##NAME##N##x##W##_##R, &tap[i])); \ 179 dprintf(("thread %d started\n", i)); \ 180 } \ 181 for (i = 0; i < thread_count; i++) { \ 182 CHECKZERO(pthread_join(tids[i], &vp)); \ 183 dprintf(("thread %d done\n", i)); \ 184 } \ 185 dt = timer(&cur_time); \ 186 dprintf(("iteration %d took %.3f secs\n", n, dt)); \ 187 ALLZEROS(td.octrs, thread_count, N); \ 188 if (n == 0 || dt < mindt) mindt = dt; \ 189 } \ 190 if (count > 1) { \ 191 double tpB = (mindt - basetime) / ( (td.kcount - 1.) * thread_count * (N * W / 8.) ); \ 192 printf("%-17s %#5.3g GB/s %u B granularity (best %u in %.3f s - %.6f s)\n", \ 193 kernelname, 1e-9/tpB, \ 194 (unsigned)(N*W/8), td.kcount, mindt, basetime ); \ 195 fflush(stdout); \ 196 } \ 197 free((void *) thread_info); \ 198 thread_info = NULL; \ 199 free(td.octrs); \ 200 free(tap); \ 201 free(tids); \ 202 } 203 204 #include "util_expandtpl.h" 205 206 int main(int argc, char **argv) 207 { 208 char *cp; 209 unsigned count = 0; 210 int keyctroffset = 0; 211 void *infop = NULL; 212 213 progname = argv[0]; 214 if (argc > 3|| (argv[1] && argv[1][0] == '-')) { 215 fprintf(stderr, "Usage: %s [COUNT]\n", progname); 216 exit(1); 217 } 218 if (argc > 1) 219 count = atoi(argv[1]); 220 if ((cp = getenv("TIME_THREAD_NTHREADS")) != NULL) { 221 thread_count = atoi(cp); 222 } 223 printf("Running with %d threads. Try 'env TIME_THREAD_NTHREADS=N %s' to change it\n", thread_count, argv[0]); 224 225 if ((cp = getenv("TIME_THREAD_VERBOSE")) != NULL) { 226 verbose = atoi(cp); 227 } 228 if ((cp = getenv("TIME_THREAD_DEBUG")) != NULL) { 229 debug = atoi(cp); 230 } 231 if ((cp = getenv("TIME_THREAD_OFFSET")) != NULL) { 232 keyctroffset = atoi(cp); 233 } 234 if ((cp = getenv("TIME_THREAD_NUMTRIALS")) != NULL) { 235 numtrials = atoi(cp); 236 } 237 if ((cp = getenv("TIME_THREAD_SEC_PER_TRIAL")) != NULL) { 238 sec_per_trial = atof(cp); 239 } 240 # include "time_initkeyctr.h" 241 return 0; 242 }