1 /* Programme to test how long it takes to select(2), poll(2) and poll2(2) a
2 large number of file descriptors.
3
4 Copyright 1997 Richard Gooch rgooch@atnf.csiro.au
5 Distributed under the GNU General Public License.
6
7 To compile this programme, use gcc -O2 -o time-polling time-polling.c
8
9 Extra compile flags:
10
11 Add -DHAS_SELECT if your operating system has the select(2) system call
12 Add -DHAS_POLL if your operating system has the poll(2) system call
13 Add -DHAS_POLL2 if your operating system has the poll2(2) system call
14
15 Usage: time-polling [num_iter] [num_to_test] [num_active] [-v]
16
17 NOTE: on many systems the default limit on file descriptors is less than
18 1024. You should try to increase this limit to 1024 before doing the test.
19 Something like "limit descriptors 1024" or "limit openfiles 1024" should do
20 the trick. On some systems (like IRIX), doing the test on a smaller number
21 gives a *much* smaller time per descriptor, which shows that time taken
22 does not scale linearly with number of descriptors, which is non-optimal.
23 In the tests I've done, I try to use 1024 descriptors.
24 The benchmark results are available at:
25 http://www.atnf.csiro.au/~rgooch/benchmarks.html
26 If you want to contribute results, please email them to me. Please specify
27 if you want to be acknowledged.
28
29
30 This program is free software; you can redistribute it and/or modify
31 it under the terms of the GNU General Public License as published by
32 the Free Software Foundation; either version 2 of the License, or
33 (at your option) any later version.
34
35 This program is distributed in the hope that it will be useful,
36 but WITHOUT ANY WARRANTY; without even the implied warranty of
37 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 GNU General Public License for more details.
39
40 You should have received a copy of the GNU General Public License
41 along with this program; if not, write to the Free Software
42 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
43
44 Richard Gooch may be reached by email at rgooch@atnf.csiro.au
45 The postal address is:
46 Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
47
48 */
49
50 #ifdef UNIXBENCH
51 #define OUT stdout
52 #else
53 #define OUT stderr
54 #endif
55 #include <stdio.h>
56 #include <string.h>
57 #include <sys/time.h>
58 #include <sys/resource.h>
59 #ifdef HAS_POLL
60 # include <sys/poll.h>
61 #endif
62 #ifdef HAS_POLL2
63 # include <linux/poll2.h>
64 #endif
65 #include <unistd.h>
66 #include <errno.h>
67 #include <stdlib.h>
68
69 #define TRUE 1
70 #define FALSE 0
71 #ifdef UNIXBENCH
72 #define MAX_ITERATIONS 1000
73 #else
74 #define MAX_ITERATIONS 30
75 #endif
76 #define MAX_FDS 40960
77 #define CONST const
78 #define ERRSTRING strerror (errno)
79
80 typedef int flag;
81
82
83 #ifdef HAS_SELECT
84
85 /*
86 static inline int find_first_set_bit (CONST void *array, int size)
87 */
find_first_set_bit(CONST void * array,int size)88 static int find_first_set_bit (CONST void *array, int size)
89 /* [SUMMARY] Find the first bit set in a bitfield.
90 <array> A pointer to the bitfield. This must be aligned on a long boundary.
91 <size> The number of bits in the bitfield.
92 [RETURNS] The index of the first set bit. If no bits are set, <<size>> + 1
93 is returned.
94 */
95 {
96 int index;
97 unsigned long word;
98 unsigned int ul_size = 8 * sizeof (unsigned long);
99 CONST unsigned long *ul_array = array;
100
101 /* Find first word with any bit set */
102 for (index = 0; (*ul_array == 0) && (index < size);
103 index += ul_size, ++ul_array);
104 /* Find first bit set in word */
105 for (word = *ul_array; !(word & 1) && (index < size);
106 ++index, word = word >> 1);
107 return (index);
108 } /* End Function find_first_set_bit */
109
110 /*
111 static inline int find_next_set_bit (CONST void *array, int size, int offset)
112 */
find_next_set_bit(CONST void * array,int size,int offset)113 static int find_next_set_bit (CONST void *array, int size, int offset)
114 /* [SUMMARY] Find the next bit set in a bitfield.
115 <array> A pointer to the bitfield. This must be aligned on a long boundary.
116 <size> The number of bits in the bitfield.
117 <offset> The offset of the current bit in the bitfield. The current bit is
118 ignored.
119 [RETURNS] The index of the next set bit. If no more bits are set,
120 <<size>> + 1 is returned.
121 */
122 {
123 int index, tmp;
124 unsigned long word;
125 unsigned int ul_size = 8 * sizeof (unsigned long);
126 CONST unsigned long *ul_array = array;
127
128 if (++offset >= size) return (offset);
129 index = offset;
130 /* Jump to the long word containing the next bit */
131 tmp = offset / ul_size;
132 ul_array += tmp;
133 offset -= tmp * ul_size;
134 if ( (offset == 0) || (*ul_array == 0) )
135 return (find_first_set_bit (ul_array, size - index) + index);
136 /* There is a bit set somewhere in this word */
137 if ( ( (word = *ul_array) != 0 ) && ( (word = word >> offset) != 0 ) )
138 {
139 /* There is a bit set somewhere in this word at or after the offset
140 position */
141 for (; (word & 1) == 0; word = word >> 1, ++index);
142 return (index);
143 }
144 /* Have to go to subsequent word(s) */
145 index += ul_size - offset;
146 return (find_first_set_bit (++ul_array, size - index) + index);
147 } /* End Function find_next_set_bit */
148
149 #endif /* HAS_SELECT */
150
151
152 struct callback_struct
153 {
154 void (*input_func) (void *info);
155 void (*output_func) (void *info);
156 void (*exception_func) (void *info);
157 void *info;
158 };
159
160 static int total_bits = 0;
161 struct callback_struct callbacks[MAX_FDS];
162
163
test_func(void * info)164 static void test_func (void *info)
165 {
166 ++total_bits;
167 }
168
169 #ifdef HAS_SELECT
time_select(fd_set * input_fds,fd_set * output_fds,fd_set * exception_fds,int max_fd,int num_iter,long * times)170 static void time_select (fd_set *input_fds, fd_set *output_fds,
171 fd_set *exception_fds, int max_fd, int num_iter,
172 long *times)
173 /* [SUMMARY] Time how long it takes to select(2) file descriptors.
174 <input_fds> The input masks.
175 <output_fds> The output masks.
176 <exception_fds> The exception masks.
177 <max_fd> The highest file descriptor in the fd_sets.
178 <num_iter> The number of iterations.
179 <times> The time taken (in microseconds) for each iteration.
180 [RETURNS] Nothing.
181 */
182 {
183 int fd, count, nready;
184 fd_set i_fds, o_fds, e_fds;
185 struct timeval time1, time2, tv;
186
187 /* Warm the cache a bit */
188 memcpy (&i_fds, input_fds, sizeof i_fds);
189 memcpy (&o_fds, output_fds, sizeof i_fds);
190 memcpy (&e_fds, exception_fds, sizeof i_fds);
191 tv.tv_sec = 0;
192 tv.tv_usec = 0;
193 select (max_fd + 1, &i_fds, &o_fds, &e_fds, &tv);
194 for (count = 0; count < num_iter; ++count)
195 {
196 total_bits = 0;
197 gettimeofday (&time1, NULL);
198 memcpy (&i_fds, input_fds, sizeof i_fds);
199 memcpy (&o_fds, output_fds, sizeof i_fds);
200 memcpy (&e_fds, exception_fds, sizeof i_fds);
201 tv.tv_sec = 0;
202 tv.tv_usec = 0;
203 nready = select (max_fd + 1, &i_fds, &o_fds, &e_fds, &tv);
204 if (nready == -1)
205 {
206 fprintf (stderr, "Error selecting\t%s\n", ERRSTRING);
207 exit (2);
208 }
209 if (nready < 1)
210 {
211 fprintf (stderr, "Error: nready: %d\n", nready);
212 exit (1);
213 }
214 /* Scan the output */
215 for (fd = find_first_set_bit (&e_fds, sizeof e_fds * 8); fd <= max_fd;
216 fd = find_next_set_bit (&e_fds, sizeof e_fds * 8, fd) )
217 {
218 (*callbacks[fd].exception_func) (callbacks[fd].info);
219 }
220 for (fd = find_first_set_bit (&i_fds, sizeof i_fds * 8); fd <= max_fd;
221 fd = find_next_set_bit (&i_fds, sizeof i_fds * 8, fd) )
222 {
223 (*callbacks[fd].input_func) (callbacks[fd].info);
224 }
225 for (fd = find_first_set_bit (&o_fds, sizeof o_fds * 8); fd <= max_fd;
226 fd = find_next_set_bit (&o_fds, sizeof o_fds * 8, fd) )
227 {
228 (*callbacks[fd].output_func) (callbacks[fd].info);
229 }
230 gettimeofday (&time2, NULL);
231 times[count] = (time2.tv_sec - time1.tv_sec) * 1000000;
232 times[count] += time2.tv_usec - time1.tv_usec;
233 }
234 } /* End Function time_select */
235 #endif /* HAS_SELECT */
236
237 #ifdef HAS_POLL
time_poll(struct pollfd * pollfd_array,int start_index,int num_to_test,int num_iter,long * times)238 static void time_poll (struct pollfd *pollfd_array, int start_index,
239 int num_to_test, int num_iter, long *times)
240 /* [SUMMARY] Time how long it takes to poll(2) file descriptors.
241 <pollfd_array> The array of pollfd structures.
242 <start_index> The start index in the array of pollfd structures.
243 <num_to_test> The number of file descriptors to test.
244 <num_iter> The number of iterations.
245 <times> The time taken (in microseconds) for each iteration.
246 [RETURNS] Nothing.
247 */
248 {
249 short revents;
250 int fd, count, nready;
251 struct timeval time1, time2;
252 struct pollfd *pollfd_ptr;
253
254 /* Warm the cache a bit */
255 poll (pollfd_array + start_index, num_to_test, 0);
256 for (count = 0; count < num_iter; ++count)
257 {
258 total_bits = 0;
259 gettimeofday (&time1, NULL);
260 nready = poll (pollfd_array + start_index, num_to_test, 0);
261 if (nready == -1)
262 {
263 fprintf (stderr, "Error polling\t%s\n", ERRSTRING);
264 exit (2);
265 }
266 if (nready < 1)
267 {
268 fprintf (stderr, "Error: nready: %d\n", nready);
269 exit (1);
270 }
271 for (pollfd_ptr = pollfd_array + start_index; nready; ++pollfd_ptr)
272 {
273 if (pollfd_ptr->revents == 0) continue;
274 /* Have an active descriptor */
275 --nready;
276 revents = pollfd_ptr->revents;
277 fd = pollfd_ptr->fd;
278 if (revents & POLLPRI)
279 (*callbacks[fd].exception_func) (callbacks[fd].info);
280 if (revents & POLLIN)
281 (*callbacks[fd].input_func) (callbacks[fd].info);
282 if (revents & POLLOUT)
283 (*callbacks[fd].output_func) (callbacks[fd].info);
284 }
285 gettimeofday (&time2, NULL);
286 times[count] = (time2.tv_sec - time1.tv_sec) * 1000000;
287 times[count] += time2.tv_usec - time1.tv_usec;
288 }
289 } /* End Function time_poll */
290 #endif /* HAS_POLL */
291
292 #ifdef HAS_POLL2
time_poll2(struct poll2ifd * poll2ifd_array,int start_index,int num_to_test,int num_iter,long * times)293 static void time_poll2 (struct poll2ifd *poll2ifd_array, int start_index,
294 int num_to_test, int num_iter, long *times)
295 /* [SUMMARY] Time how long it takes to poll2(2) file descriptors.
296 <poll2ifd_array> The array of poll2ifd structures.
297 <start_index> The start index in the array of pollfd structures.
298 <num_to_test> The number of file descriptors to test.
299 <num_iter> The number of iterations.
300 <times> The time taken (in microseconds) for each iteration.
301 [RETURNS] Nothing.
302 */
303 {
304 short revents;
305 int fd, count, nready, i;
306 struct timeval time1, time2;
307 struct poll2ofd poll2ofd_array[MAX_FDS];
308
309 /* Warm the cache a bit */
310 poll2 (poll2ifd_array + start_index, poll2ofd_array, num_to_test, 0);
311 for (count = 0; count < num_iter; ++count)
312 {
313 total_bits = 0;
314 gettimeofday (&time1, NULL);
315 nready = poll2 (poll2ifd_array + start_index, poll2ofd_array,
316 num_to_test, 0);
317 if (nready == -1)
318 {
319 times[count] = -1;
320 if (errno == ENOSYS) return; /* Must do this first */
321 fprintf (stderr, "Error calling poll2(2)\t%s\n", ERRSTRING);
322 exit (2);
323 }
324 if (nready < 1)
325 {
326 fprintf (stderr, "Error: nready: %d\n", nready);
327 exit (1);
328 }
329 for (i = 0; i < nready; ++i)
330 {
331 revents = poll2ofd_array[i].revents;
332 fd = poll2ofd_array[i].fd;
333 if (revents & POLLPRI)
334 (*callbacks[fd].exception_func) (callbacks[fd].info);
335 if (revents & POLLIN)
336 (*callbacks[fd].input_func) (callbacks[fd].info);
337 if (revents & POLLOUT)
338 (*callbacks[fd].output_func) (callbacks[fd].info);
339 }
340 gettimeofday (&time2, NULL);
341 times[count] = (time2.tv_sec - time1.tv_sec) * 1000000;
342 times[count] += time2.tv_usec - time1.tv_usec;
343 }
344 } /* End Function time_poll2 */
345 #endif /* HAS_POLL2 */
346
347
main(argc,argv)348 int main (argc, argv)
349 int argc;
350 char *argv[];
351 {
352 flag failed = FALSE;
353 flag verbose = FALSE;
354 int first_fd = -1;
355 int fd, max_fd, count, total_fds;
356 int num_to_test, num_active;
357 #ifdef UNIXBENCH
358 int max_iter = 1000;
359 #else
360 int max_iter = 10;
361 #endif
362 #ifdef HAS_SELECT
363 long select_total = 0;
364 fd_set input_fds, output_fds, exception_fds;
365 long select_times[MAX_ITERATIONS];
366 #endif
367 #ifdef HAS_POLL
368 int start_index;
369 long poll_total = 0;
370 struct pollfd pollfd_array[MAX_FDS];
371 long poll_times[MAX_ITERATIONS];
372 #endif
373 #ifdef HAS_POLL2
374 long poll2_total = 0;
375 struct poll2ifd poll2ifd_array[MAX_FDS];
376 struct poll2ofd poll2ofd_array[MAX_FDS];
377 long poll2_times[MAX_ITERATIONS];
378 #endif
379
380 #ifdef HAS_SELECT
381 FD_ZERO (&input_fds);
382 FD_ZERO (&output_fds);
383 FD_ZERO (&exception_fds);
384 #endif
385 #ifdef HAS_POLL
386 memset (pollfd_array, 0, sizeof pollfd_array);
387 #endif
388 /* Allocate file descriptors */
389 total_fds = 0;
390 max_fd = 0;
391 while (!failed)
392 {
393 if ( ( fd = dup (1) ) == -1 )
394 {
395 if (errno != EMFILE)
396 {
397 fprintf (stderr, "Error dup()ing\t%s\n", ERRSTRING);
398 exit (1);
399 }
400 failed = TRUE;
401 continue;
402 }
403 if (fd >= MAX_FDS)
404 {
405 fprintf (stderr, "File descriptor: %d larger than max: %d\n",
406 fd, MAX_FDS - 1);
407 exit (1);
408 }
409 callbacks[fd].input_func = test_func;
410 callbacks[fd].output_func = test_func;
411 callbacks[fd].exception_func = test_func;
412 callbacks[fd].info = NULL;
413 if (fd > max_fd) max_fd = fd;
414 if (first_fd < 0) first_fd = fd;
415 #ifdef HAS_POLL
416 pollfd_array[fd].fd = fd;
417 pollfd_array[fd].events = 0;
418 #endif
419 #ifdef HAS_POLL2
420 poll2ifd_array[fd].fd = fd;
421 poll2ifd_array[fd].events = 0;
422 #endif
423 }
424 total_fds = max_fd + 1;
425 /* Process the command-line arguments */
426 if (argc > 5)
427 {
428 fputs ("Usage:\ttime-polling [num_iter] [num_to_test] [num_active] [-v]\n",
429 stderr);
430 exit (1);
431 }
432 if (argc > 1) max_iter = atoi (argv[1]);
433 if (max_iter > MAX_ITERATIONS)
434 {
435 fprintf (stderr, "num_iter too large\n");
436 exit (1);
437 }
438 if (argc > 2) num_to_test = atoi (argv[2]);
439 else num_to_test = total_fds - first_fd;
440 if (argc > 3) num_active = atoi (argv[3]);
441 else num_active = 1;
442 if (argc > 4)
443 {
444 if (strcmp (argv[4], "-v") != 0)
445 {
446 fputs ("Usage:\ttime-polling [num_iter] [num_to_test] [num_active] [-v]\n",
447 stderr);
448 exit (1);
449 }
450 verbose = TRUE;
451 }
452
453 /* Sanity tests */
454 if (num_to_test > total_fds - first_fd) num_to_test = total_fds - first_fd;
455 if (num_active > total_fds - first_fd) num_active = total_fds - first_fd;
456 /* Set activity monitoring flags */
457 for (fd = total_fds - num_to_test; fd < total_fds; ++fd)
458 {
459 #ifdef HAS_SELECT
460 FD_SET (fd, &exception_fds);
461 FD_SET (fd, &input_fds);
462 #endif
463 #ifdef HAS_POLL
464 pollfd_array[fd].events = POLLPRI | POLLIN;
465 #endif
466 #ifdef HAS_POLL2
467 poll2ifd_array[fd].events = POLLPRI | POLLIN;
468 #endif
469 }
470 for (fd = total_fds - num_active; fd < total_fds; ++fd)
471 {
472 #ifdef HAS_SELECT
473 FD_SET (fd, &output_fds);
474 #endif
475 #ifdef HAS_POLL
476 pollfd_array[fd].events |= POLLOUT;
477 #endif
478 #ifdef HAS_POLL2
479 poll2ifd_array[fd].events |= POLLOUT;
480 #endif
481 }
482 fprintf (OUT, "Num fds: %d, polling descriptors %d-%d\n",
483 total_fds, total_fds - num_to_test, max_fd);
484 /* First do all the tests, then print the results */
485 #ifdef HAS_SELECT
486 time_select (&input_fds, &output_fds, &exception_fds, max_fd, max_iter,
487 select_times);
488 #endif
489 #ifdef HAS_POLL
490 start_index = total_fds - num_to_test;
491 time_poll (pollfd_array, start_index, num_to_test, max_iter, poll_times);
492 #endif
493 #ifdef HAS_POLL2
494 start_index = total_fds - num_to_test;
495 time_poll2 (poll2ifd_array, start_index, num_to_test, max_iter,
496 poll2_times);
497 #endif
498 /* Now print out all the times */
499 fputs ("All times in microseconds\n", OUT);
500 fputs ("ITERATION\t", OUT);
501 #ifdef HAS_SELECT
502 fprintf (OUT, "%-12s", "select(2)");
503 #endif
504 #ifdef HAS_POLL
505 fprintf (OUT, "%-12s", "poll(2)");
506 #endif
507 #ifdef HAS_POLL2
508 if (poll2_times[0] >= 0) fprintf (OUT, "%-12s", "poll2(2)");
509 #endif
510 for (count = 0; count < max_iter; ++count)
511 {
512 if (verbose) fprintf (OUT, "\n%d\t\t", count);
513 #ifdef HAS_SELECT
514 if (verbose) fprintf (OUT, "%-12ld", select_times[count]);
515 select_total += select_times[count];
516 #endif
517 #ifdef HAS_POLL
518 if (verbose) fprintf (OUT, "%-12ld", poll_times[count]);
519 poll_total += poll_times[count];
520 #endif
521 #ifdef HAS_POLL2
522 if ( verbose && (poll2_times[0] >= 0) )
523 fprintf (OUT, "%-12ld", poll2_times[count]);
524 poll2_total += poll2_times[count];
525 #endif
526 }
527 fputs ("\n\naverage\t\t", OUT);
528 #ifdef HAS_SELECT
529 fprintf (OUT, "%-12ld", select_total / max_iter);
530 #endif
531 #ifdef HAS_POLL
532 fprintf (OUT, "%-12ld", poll_total / max_iter);
533 #endif
534 #ifdef HAS_POLL2
535 if (poll2_times[0] >= 0)
536 fprintf (OUT, "%-12ld", poll2_total / max_iter);
537 #endif
538 putc ('\n', OUT);
539 fputs ("Per fd\t\t", OUT);
540 #ifdef HAS_SELECT
541 fprintf (OUT, "%-12.2f",
542 (float) select_total / (float) max_iter / (float) num_to_test);
543 #ifdef UNIXBENCH
544 fprintf (stderr, "lps\t%.2f\t%.1f\n",
545 1000000 * (float) max_iter * (float) num_to_test
546 / (float) select_total, (float)select_total / 1000000);
547 #endif
548 #endif
549 #ifdef HAS_POLL
550 fprintf (OUT, "%-12.2f",
551 (float) poll_total / (float) max_iter / (float) num_to_test);
552 #ifdef UNIXBENCH
553 fprintf (stderr, "lps\t%.2f\t%.1f\n",
554 1000000 * (float) max_iter * (float) num_to_test
555 / (float) poll_total, (float)poll_total / 1000000);
556 #endif
557 #endif
558 #ifdef HAS_POLL2
559 if (poll2_times[0] >= 0) {
560 fprintf (OUT, "%-12.2f",
561 (float) poll2_total / (float) max_iter / (float) num_to_test);
562 #ifdef UNIXBENCH
563 fprintf (stderr, "lps\t%.2f\t%.1f\n",
564 1000000 * (float) max_iter * (float) num_to_test
565 / (float) poll2_total, (float)poll2_total / 1000000);
566 #endif
567 }
568
569 #endif
570 fputs ("<- the most important value\n", OUT);
571
572 exit(0);
573 } /* End Function main */
574