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