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 /*
84 static inline int find_first_set_bit (CONST void *array, int size)
85 */
find_first_set_bit(CONST void * array,int size)86 static int find_first_set_bit (CONST void *array, int size)
87 /*  [SUMMARY] Find the first bit set in a bitfield.
88     <array> A pointer to the bitfield. This must be aligned on a long boundary.
89     <size> The number of bits in the bitfield.
90     [RETURNS] The index of the first set bit. If no bits are set, <<size>> + 1
91     is returned.
92 */
93 {
94     int index;
95     unsigned long word;
96     unsigned int ul_size = 8 * sizeof (unsigned long);
97     CONST unsigned long *ul_array = array;
98 
99     /*  Find first word with any bit set  */
100     for (index = 0; (*ul_array == 0) && (index < size);
101 	 index += ul_size, ++ul_array);
102     /*  Find first bit set in word  */
103     for (word = *ul_array; !(word & 1) && (index < size);
104 	 ++index, word = word >> 1);
105     return (index);
106 }   /*  End Function find_first_set_bit  */
107 
108 /*
109 static inline int find_next_set_bit (CONST void *array, int size, int offset)
110 */
find_next_set_bit(CONST void * array,int size,int offset)111 static int find_next_set_bit (CONST void *array, int size, int offset)
112 /*  [SUMMARY] Find the next bit set in a bitfield.
113     <array> A pointer to the bitfield. This must be aligned on a long boundary.
114     <size> The number of bits in the bitfield.
115     <offset> The offset of the current bit in the bitfield. The current bit is
116     ignored.
117     [RETURNS] The index of the next set bit. If no more bits are set,
118     <<size>> + 1 is returned.
119 */
120 {
121     int index, tmp;
122     unsigned long word;
123     unsigned int ul_size = 8 * sizeof (unsigned long);
124     CONST unsigned long *ul_array = array;
125 
126     if (++offset >= size) return (offset);
127     index = offset;
128     /*  Jump to the long word containing the next bit  */
129     tmp = offset / ul_size;
130     ul_array += tmp;
131     offset -= tmp * ul_size;
132     if ( (offset == 0) || (*ul_array == 0) )
133 	return (find_first_set_bit (ul_array, size - index) + index);
134     /*  There is a bit set somewhere in this word  */
135     if ( ( (word = *ul_array) != 0 ) && ( (word = word >> offset) != 0 ) )
136     {
137 	/*  There is a bit set somewhere in this word at or after the offset
138 	    position  */
139 	for (; (word & 1) == 0; word = word >> 1, ++index);
140 	return (index);
141     }
142     /*  Have to go to subsequent word(s)  */
143     index += ul_size - offset;
144     return (find_first_set_bit (++ul_array, size - index) + index);
145 }   /*  End Function find_next_set_bit  */
146 
147 
148 struct callback_struct
149 {
150     void (*input_func) (void *info);
151     void (*output_func) (void *info);
152     void (*exception_func) (void *info);
153     void *info;
154 };
155 
156 static int total_bits = 0;
157 struct callback_struct callbacks[MAX_FDS];
158 
159 
test_func(void * info)160 static void test_func (void *info)
161 {
162     ++total_bits;
163 }
164 
165 #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)166 static void time_select (fd_set *input_fds, fd_set *output_fds,
167 			 fd_set *exception_fds, int max_fd, int num_iter,
168 			 long *times)
169 /*  [SUMMARY] Time how long it takes to select(2) file descriptors.
170     <input_fds> The input masks.
171     <output_fds> The output masks.
172     <exception_fds> The exception masks.
173     <max_fd> The highest file descriptor in the fd_sets.
174     <num_iter> The number of iterations.
175     <times> The time taken (in microseconds) for each iteration.
176     [RETURNS] Nothing.
177 */
178 {
179     int fd, count, nready;
180     fd_set i_fds, o_fds, e_fds;
181     struct timeval time1, time2, tv;
182 
183     /*  Warm the cache a bit  */
184     memcpy (&i_fds, input_fds, sizeof i_fds);
185     memcpy (&o_fds, output_fds, sizeof i_fds);
186     memcpy (&e_fds, exception_fds, sizeof i_fds);
187     tv.tv_sec = 0;
188     tv.tv_usec = 0;
189     select (max_fd + 1, &i_fds, &o_fds, &e_fds, &tv);
190     for (count = 0; count < num_iter; ++count)
191     {
192 	total_bits = 0;
193 	gettimeofday (&time1, NULL);
194 	memcpy (&i_fds, input_fds, sizeof i_fds);
195 	memcpy (&o_fds, output_fds, sizeof i_fds);
196 	memcpy (&e_fds, exception_fds, sizeof i_fds);
197 	tv.tv_sec = 0;
198 	tv.tv_usec = 0;
199 	nready = select (max_fd + 1, &i_fds, &o_fds, &e_fds, &tv);
200 	if (nready == -1)
201 	{
202 	    fprintf (stderr, "Error selecting\t%s\n", ERRSTRING);
203 	    exit (2);
204 	}
205 	if (nready < 1)
206 	{
207 	    fprintf (stderr, "Error: nready: %d\n", nready);
208 	    exit (1);
209 	}
210 	/*  Scan the output  */
211 	for (fd = find_first_set_bit (&e_fds, sizeof e_fds * 8); fd <= max_fd;
212 	     fd = find_next_set_bit (&e_fds, sizeof e_fds * 8, fd) )
213 	{
214 	    (*callbacks[fd].exception_func) (callbacks[fd].info);
215 	}
216 	for (fd = find_first_set_bit (&i_fds, sizeof i_fds * 8); fd <= max_fd;
217 	     fd = find_next_set_bit (&i_fds, sizeof i_fds * 8, fd) )
218 	{
219 	    (*callbacks[fd].input_func) (callbacks[fd].info);
220 	}
221 	for (fd = find_first_set_bit (&o_fds, sizeof o_fds * 8); fd <= max_fd;
222 	     fd = find_next_set_bit (&o_fds, sizeof o_fds * 8, fd) )
223 	{
224 	    (*callbacks[fd].output_func) (callbacks[fd].info);
225 	}
226 	gettimeofday (&time2, NULL);
227 	times[count] = (time2.tv_sec - time1.tv_sec) * 1000000;
228 	times[count] += time2.tv_usec - time1.tv_usec;
229     }
230 }   /*  End Function time_select  */
231 #endif  /* HAS_SELECT */
232 
233 #ifdef HAS_POLL
time_poll(struct pollfd * pollfd_array,int start_index,int num_to_test,int num_iter,long * times)234 static void time_poll (struct pollfd *pollfd_array, int start_index,
235 		       int num_to_test, int num_iter, long *times)
236 /*  [SUMMARY] Time how long it takes to poll(2) file descriptors.
237     <pollfd_array> The array of pollfd structures.
238     <start_index> The start index in the array of pollfd structures.
239     <num_to_test> The number of file descriptors to test.
240     <num_iter> The number of iterations.
241     <times> The time taken (in microseconds) for each iteration.
242     [RETURNS] Nothing.
243 */
244 {
245     short revents;
246     int fd, count, nready;
247     struct timeval time1, time2;
248     struct pollfd *pollfd_ptr, *stop_pollfd;
249 
250     /*  Warm the cache a bit  */
251     poll (pollfd_array + start_index, num_to_test, 0);
252     for (count = 0; count < num_iter; ++count)
253     {
254 	total_bits = 0;
255 	gettimeofday (&time1, NULL);
256 	nready = poll (pollfd_array + start_index, num_to_test, 0);
257 	if (nready == -1)
258 	{
259 	    fprintf (stderr, "Error polling\t%s\n", ERRSTRING);
260 	    exit (2);
261 	}
262 	if (nready < 1)
263 	{
264 	    fprintf (stderr, "Error: nready: %d\n", nready);
265 	    exit (1);
266 	}
267 	stop_pollfd = pollfd_array + start_index + num_to_test;
268 	for (pollfd_ptr = pollfd_array + start_index; TRUE; ++pollfd_ptr)
269 	{
270 	    if (pollfd_ptr->revents == 0) continue;
271 	    /*  Have an active descriptor  */
272 	    revents = pollfd_ptr->revents;
273 	    fd = pollfd_ptr->fd;
274 	    if (revents & POLLPRI)
275 		(*callbacks[fd].exception_func) (callbacks[fd].info);
276 	    if (revents & POLLIN)
277 		(*callbacks[fd].input_func) (callbacks[fd].info);
278 	    if (revents & POLLOUT)
279 		(*callbacks[fd].output_func) (callbacks[fd].info);
280 	    if (--nready == 0) break;
281 	}
282 	gettimeofday (&time2, NULL);
283 	times[count] = (time2.tv_sec - time1.tv_sec) * 1000000;
284 	times[count] += time2.tv_usec - time1.tv_usec;
285     }
286 }   /*  End Function time_poll  */
287 #endif  /* HAS_POLL */
288 
289 #ifdef HAS_POLL2
time_poll2(struct poll2ifd * poll2ifd_array,int start_index,int num_to_test,int num_iter,long * times)290 static void time_poll2 (struct poll2ifd *poll2ifd_array, int start_index,
291 			int num_to_test, int num_iter, long *times)
292 /*  [SUMMARY] Time how long it takes to poll2(2) file descriptors.
293     <poll2ifd_array> The array of poll2ifd structures.
294     <start_index> The start index in the array of pollfd structures.
295     <num_to_test> The number of file descriptors to test.
296     <num_iter> The number of iterations.
297     <times> The time taken (in microseconds) for each iteration.
298     [RETURNS] Nothing.
299 */
300 {
301     short revents;
302     int fd, count, nready, i;
303     struct timeval time1, time2;
304     struct poll2ofd poll2ofd_array[MAX_FDS];
305 
306     /*  Warm the cache a bit  */
307     poll2 (poll2ifd_array + start_index, poll2ofd_array, num_to_test, 0);
308     for (count = 0; count < num_iter; ++count)
309     {
310 	total_bits = 0;
311 	gettimeofday (&time1, NULL);
312 	nready = poll2 (poll2ifd_array + start_index, poll2ofd_array,
313 			num_to_test, 0);
314 	if (nready == -1)
315 	{
316 	    times[count] = -1;
317 	    if (errno == ENOSYS) return;  /*  Must do this first  */
318 	    fprintf (stderr, "Error calling poll2(2)\t%s\n", ERRSTRING);
319 	    exit (2);
320 	}
321 	if (nready < 1)
322 	{
323 	    fprintf (stderr, "Error: nready: %d\n", nready);
324 	    exit (1);
325 	}
326 	for (i = 0; i < nready; ++i)
327 	{
328 	    revents = poll2ofd_array[i].revents;
329 	    fd = poll2ofd_array[i].fd;
330 	    if (revents & POLLPRI)
331 		(*callbacks[fd].exception_func) (callbacks[fd].info);
332 	    if (revents & POLLIN)
333 		(*callbacks[fd].input_func) (callbacks[fd].info);
334 	    if (revents & POLLOUT)
335 		(*callbacks[fd].output_func) (callbacks[fd].info);
336 	}
337 	gettimeofday (&time2, NULL);
338 	times[count] = (time2.tv_sec - time1.tv_sec) * 1000000;
339 	times[count] += time2.tv_usec - time1.tv_usec;
340     }
341 }   /*  End Function time_poll2  */
342 #endif  /* HAS_POLL2 */
343 
344 
main(argc,argv)345 int main (argc, argv)
346 int	argc;
347 char	*argv[];
348 {
349     flag failed = FALSE;
350     flag verbose = FALSE;
351     int first_fd = -1;
352     int fd, max_fd, count, total_fds;
353     int num_to_test, num_active;
354 #ifdef UNIXBENCH
355     int max_iter = 1000;
356 #else
357     int max_iter = 10;
358 #endif
359 #ifdef HAS_SELECT
360     long select_total = 0;
361     fd_set input_fds, output_fds, exception_fds;
362     long select_times[MAX_ITERATIONS];
363 #endif
364 #ifdef HAS_POLL
365 	int start_index;
366     long poll_total = 0;
367     struct pollfd pollfd_array[MAX_FDS];
368     long poll_times[MAX_ITERATIONS];
369 #endif
370 #ifdef HAS_POLL2
371     long poll2_total = 0;
372     struct poll2ifd poll2ifd_array[MAX_FDS];
373     struct poll2ofd poll2ofd_array[MAX_FDS];
374     long poll2_times[MAX_ITERATIONS];
375 #endif
376 #if 0
377     extern char *sys_errlist[];
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