1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
|
/*
* threaded.c --
*
* A small study on implementation techniques for threaded code.
*
* As example a small program stacking two values on to the stack
* and adding the result is taken (example from wikipedia). This
* program uses the abstract machine instructions PUSH_A, PUSH_B,
* ADD, and END. These instructions are coded in different ways:
*
* * switch_threading(): realizing instructions as C enum
* values, the program is represented as an array of enums,
* placed in a large surrounding switch statement.
*
* * call_threading(): realizing instructions as C functions, the
* program is represented an array of C functions.
*
* * label_threading(): realizing instructions as labels, the
* program is represented an array of labels.
*
* The test show, that label_threading is clearly the winner in
* terms of performance, but this depends on a non standard C
* extension, which is implemented in at least three different
* compilers (supported on GCC, clang and IBM's XL C/C++). It
* would be interesting to define the instructions in some
* e.g. scripting language and to produce different
* implementations from the same source to address the
* portability issue.
*
* Most probably, one needs a larger program-code with more
* instructions to provide more meaningful results.
*
* Compile e.g. with:
* cc -O3 -Wall threaded.c -o threaded
*
* Gustaf Neumann (Nov 2011)
*/
#include <stdio.h>
#include <sys/time.h>
/*
*----------------------------------------------------------------------
* DiffTime --
*
* Compute the time difference between two timevals.
*
*----------------------------------------------------------------------
*/
static void
DiffTime(struct timeval *t1, struct timeval *t0, struct timeval *diffPtr) {
diffPtr->tv_sec = t1->tv_sec - t0->tv_sec;
diffPtr->tv_usec = t1->tv_usec - t0->tv_usec;
if (diffPtr->tv_usec < 0) {
diffPtr->tv_sec += (diffPtr->tv_usec / 1000000L) - 1;
diffPtr->tv_usec = (diffPtr->tv_usec % 1000000L) + 1000000L;
} else if (diffPtr->tv_usec > 1000000L) {
diffPtr->tv_sec += diffPtr->tv_usec / 1000000L;
diffPtr->tv_usec = diffPtr->tv_usec % 1000000L;
}
}
/*
*----------------------------------------------------------------------
* timeit --
*
* Run a function multiple times and measure the performance.
*
*----------------------------------------------------------------------
*/
static void
timeit( void (* fn)() ) {
struct timeval start, end, diff;
int i;
gettimeofday(&start, NULL);
for (i=0; i<10000000; i++) {
(*fn)();
}
gettimeofday(&end, NULL);
DiffTime(&end, &start, &diff);
printf("%d seconds, %d usec\n", (int) diff.tv_sec, (int) diff.tv_usec);
}
int *sp;
int stack[100];
/*
*----------------------------------------------------------------------
* switch_threading --
*
* Define an enum for the instructions and use a switch to select
* the instructions.
*
*----------------------------------------------------------------------
*/
void
switch_threading() {
typedef enum {INST_PUSH_A, INST_PUSH_B, INST_ADD, INST_END} InstEnum;
static InstEnum mycode[] = {INST_PUSH_A, INST_PUSH_B, INST_ADD, INST_END};
int a, b;
InstEnum *ip;
ip = &mycode[0];
sp = &stack[0];
for (;;) {
switch (*ip++) {
case INST_PUSH_A:
*sp++ = 100;
continue;
case INST_PUSH_B:
*sp++ = 200;
continue;
case INST_ADD:
a = *--sp;
b = *--sp;
*sp++ = a + b;
continue;
case INST_END:
//fprintf(stderr, "end %d\n", *(sp-1));
return;
}
}
}
/*
*----------------------------------------------------------------------
* call_threading --
*
* Define for every instruction a function, the program consists of
* an array of function pointers.
*
*----------------------------------------------------------------------
*/
typedef void (* InstFn)();
void pushA () { *sp++ = 100; }
void pushB () { *sp++ = 200; }
void add () { int a = *--sp; int b = *--sp; *sp++ = a + b; }
void
call_threading() {
static InstFn mycode[] = {&pushA, &pushB, &add, NULL};
InstFn *ip;
sp = &stack[0];
ip = &mycode[0];
while (*ip) {
(*ip++)();
}
//fprintf(stderr, "end %d\n", *(sp-1));
}
/*
*----------------------------------------------------------------------
* label_threading --
*
* Define for every instruction a label, the code is a sequence of
* labels. This works with gcc, clang and IBM's XL C, but not on
* every compiler.
*
*----------------------------------------------------------------------
*/
typedef void (* InstLabel)();
void
label_threading() {
static InstLabel mycode[] = {&&INST_PUSH_A, &&INST_PUSH_B, &&INST_ADD, &&INST_END};
InstLabel *ip;
int a, b;
sp = &stack[0];
ip = &mycode[0];
INST_PUSH_A:
*sp++ = 100;
goto **ip++;
INST_PUSH_B:
*sp++ = 200;
goto **ip++;
INST_ADD:
a = *--sp;
b = *--sp;
*sp++ = a + b;
goto **ip++;
INST_END:
//fprintf(stderr, "end %d\n", *(sp-1));
return;
}
/*
*----------------------------------------------------------------------
* main --
*
* Just call the testcases with timing.
*
*----------------------------------------------------------------------
*/
int
main() {
timeit( switch_threading );
timeit( call_threading );
timeit( label_threading );
return 0;
}
|