egray@fthood.UUCP (10/02/88)
Howdy... This is a weird story, so bear with me... Introduction: I've noticed that Pcomm doesn't appear to display characters on the screen as fast as I think it ought to. For example, if you've got a 2400 baud modem attached to tty000, the screen still appears to display things at a rate closer to 1200 baud. This only seems to affect the screen display, not the file transfers. In an effort to make the Pcomm screens faster, I've been performing some simple tests to determine the best method of doing unbuffered character writes. What I've found: 1) There is a *huge* difference in speed between sending a character to the console at 9600 and sending it to a terminal on /dev/tty000 at 9600 baud. For example, the average speed on the console is in the range of 120 to 135 cps (characters per second). However, the average speed using tty000 is in the range 620 to 700 cps. Obviously, using buffered writes to the screen you can obtain numbers much greater than this... but that's not an alternative in a telecommunications program. 2) Methods using the normal tty interface (the "cooked mode") did better than the "raw" mode. This is strange since the raw mode doesn't have to do output postprocessing. I had bet that the raw mode would have been faster.... 3) I had assumed that since "putchar(c)" is a macro to "putc(c, stdout)" that the times would have been identical. They are *very* close, but still different... 4) The "control" used in the test was a method using full buffered fwrite()s. On the average, the console obtained about 32% of its control value, and the tty000 obtained about 70% of its control value. Conclusions: The tty driver for the console is a strange beast. It doesn't like any method of unbuffered character writes (it doesn't do all that hot using full buffered writes either). The details of the test: Unix PC with 2 Meg RAM and Unix 3.51 (console at 9600 baud) Toshiba T1200 on tty000 running ProComm Plus at 9600 baud. The test program (that is contained below) uses the following methods of writing to the screen: 1) cooked, setbuf, fputc: Uses the normal tty interface (the cooked mode), uses a "setbuf(stdout, (char *) NULL)" at the beginning to make the output unbuffered, uses "fputc(c, stdout)" to output the characters. 2) cooked, setbuf, putc: Identical to above except it uses "putc(c, stdout)" instead of "fputc()". 3) cooked, setbuf, putchar: Same as above, but uses "putchar()". 4) cooked, fputc, fflush: Uses normal tty buffering (without the "setbuf()"), but follows every "fputc(c, stdout)" with a "fflush(stdout)" to make it unbuffered. 5) cooked, putc, fflush: Same as above but uses "putc()" instead of "fputc()" 6) cooked, putchar, fflush: Same as above but uses "putchar()". 7) cooked, write: Uses just "write(1, &c, 1)". 8) cooked fwrite: Uses full block buffered fwrite()s. Not really a test method... used as a control. 9-16) Same as above but done in the "cbreak" (raw) mode with no post processing. Since the test is to measure what is actually sent to the screen, the "cooked" mode test results take into account that the tty driver has added a CR to every NL. All tests were done on a test file that was 3994 characters long (the source to the test program). The test was performed as: testit testfile 2> results You'll notice that the CPS in one case exceeds the 960 cps theoretical limit (9600 baud / 10 bits per byte = 960 cps). So, don't go to the bank on any of these numbers... they are only for comparing the different write methods. The test results on the console (at 9600 baud): Method real user sys CPS cooked, setbuf, fputc 30.52 0.50 4.70 137.4 cooked, setbuf, putc 30.78 0.78 4.85 136.2 cooked, setbuf, putchar 30.55 0.62 4.82 137.3 cooked, fputc, fflush 31.23 1.28 5.00 134.3 cooked, putc, fflush 31.17 1.08 5.40 134.6 cooked, putchar, fflush 31.05 0.98 4.93 135.1 cooked, write 30.42 0.47 5.42 137.9 cooked, fwrite 9.28 0.00 0.42 451.8 raw, setbuf, fputc 33.00 0.63 4.92 121.0 raw, setbuf, putc 33.07 0.72 4.87 120.8 raw, setbuf, putchar 32.97 0.68 4.40 121.2 raw, fputc, fflush 33.63 1.48 4.58 118.8 raw, putc, fflush 33.43 1.27 4.52 119.5 raw, putchar, fflush 33.57 1.45 3.93 119.0 raw, write 33.17 0.53 4.95 120.4 raw, fwrite 11.35 0.00 0.08 351.9 cooked file length = 4194 raw file length = 3994 The test results on tty000 at 9600 baud: Method real user sys CPS cooked, setbuf, fputc 5.92 0.30 5.62 708.8 cooked, setbuf, putc 5.83 0.20 5.63 719.0 cooked, setbuf, putchar 5.82 0.12 5.70 721.0 cooked, fputc, fflush 6.57 0.72 5.68 638.7 cooked, putc, fflush 6.28 0.47 5.82 667.5 cooked, putchar, fflush 6.28 0.43 5.85 667.5 cooked, write 5.63 0.08 5.55 744.5 cooked, fwrite 4.40 0.00 0.47 953.2 raw, setbuf, fputc 6.00 0.45 5.55 665.7 raw, setbuf, putc 5.92 0.23 5.68 675.0 raw, setbuf, putchar 5.92 0.32 5.60 675.0 raw, fputc, fflush 6.45 0.72 5.73 619.2 raw, putc, fflush 6.37 0.47 5.90 627.3 raw, putchar, fflush 6.48 0.57 5.82 616.0 raw, write 5.75 0.02 5.73 694.6 raw, fwrite 4.02 0.00 0.07 994.4 cooked file length = 4194 raw file length = 3994 Emmet P. Gray US Army, HQ III Corps & Fort Hood ...!uunet!uiucuxc!fthood!egray Attn: AFZF-DE-ENV Directorate of Engineering & Housing Environmental Management Office Fort Hood, TX 76544-5057 ------------------------------------------------------------------------------- /* * Test the speed of the screen write methods. */ #include <stdio.h> #include <termio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/times.h> #define TICK 60.0 int num_lines, num_chars; main(argc, argv) int argc; char *argv[]; { FILE *fp; int i, j, n, num, len_cooked, len_raw; long start, stop; float real, user, system, cps; char buf[1024]; struct termio tbuf, thold; struct tms startbuf, stopbuf; if (argc != 2) { printf("requires a text file argument to be used as a test\n"); exit(1); } if (!(fp = fopen(argv[1], "r"))) { perror("fopen"); exit(1); } /* get raw length */ get_nums(argv[1]); len_cooked = num_chars + num_lines; len_raw = num_chars; ioctl(0, TCGETA, &tbuf); ioctl(0, TCGETA, &thold); fprintf(stderr, " Method real user sys CPS\n\n"); for (j=0; j<16; j++) { switch(j) { case 0: fprintf(stderr, "cooked, setbuf, fputc "); break; case 1: fprintf(stderr, "cooked, setbuf, putc "); break; case 2: fprintf(stderr, "cooked, setbuf, putchar "); break; case 3: fprintf(stderr, "cooked, fputc, fflush "); break; case 4: fprintf(stderr, "cooked, putc, fflush "); break; case 5: fprintf(stderr, "cooked, putchar, fflush "); break; case 6: fprintf(stderr, "cooked, write "); break; case 7: fprintf(stderr, "cooked, fwrite "); break; case 8: fprintf(stderr, "raw, setbuf, fputc "); break; case 9: fprintf(stderr, "raw, setbuf, putc "); break; case 10: fprintf(stderr, "raw, setbuf, putchar "); break; case 11: fprintf(stderr, "raw, fputc, fflush "); break; case 12: fprintf(stderr, "raw, putc, fflush "); break; case 13: fprintf(stderr, "raw, putchar, fflush "); break; case 14: fprintf(stderr, "raw, write "); break; case 15: fprintf(stderr, "raw, fwrite "); break; } n = j; if (n >= 8) { n = n -8; tbuf.c_cc[4] = 1; tbuf.c_cc[5] = 0; tbuf.c_iflag = 0; tbuf.c_oflag = 0; tbuf.c_lflag = 0; tbuf.c_cflag &= ~PARENB; tbuf.c_cflag &= ~CSIZE; tbuf.c_cflag |= CS8; } ioctl(0, TCSETAW, &tbuf); freopen("/dev/tty", "w", stdout); if (n <= 2) setbuf(stdout, (char *) NULL); /* start the timer */ start = times(&startbuf); while ((num = fread(buf, sizeof(char), BUFSIZ, fp)) > 0) { switch(n) { case 0: for (i=0; i<num; i++) fputc(buf[i], stdout); break; case 1: for (i=0; i<num; i++) putc(buf[i], stdout); break; case 2: for (i=0; i<num; i++) putchar(buf[i]); break; case 3: for (i=0; i<num; i++) { fputc(buf[i], stdout); fflush(stdout); } break; case 4: for (i=0; i<num; i++) { putc(buf[i], stdout); fflush(stdout); } break; case 5: for (i=0; i<num; i++) { putchar(buf[i]); fflush(stdout); } break; case 6: for (i=0; i<num; i++) write(1, &buf[i], 1); break; case 7: fwrite(buf, sizeof(char), num, stdout); break; } } /* stop the timer */ stop = times(&stopbuf); real = (float) (stop - start) / TICK; user = (float) (stopbuf.tms_utime - startbuf.tms_utime) / TICK; system = (float) (stopbuf.tms_stime - startbuf.tms_stime) / TICK; if (j >= 8) cps = len_raw / real; else cps = len_cooked / real; fprintf(stderr, "\t%10.2f%10.2f%10.2f%11.1f\n", real, user, system, cps); rewind(fp); ioctl(0, TCSETAW, &thold); } fprintf(stderr, "\ncooked file length = %d\n", len_cooked); fprintf(stderr, "raw file length = %d\n", len_raw); printf("\r\n"); } get_nums(file) char *file; { FILE *fp; char buf[BUFSIZ]; struct stat sbuf; num_chars = 0; num_lines = 0; if (stat(file, &sbuf) < 0) return; num_chars = sbuf.st_size; if (!(fp = fopen(file, "r"))) return; while (fgets(buf, BUFSIZ, fp) != NULL) num_lines++; fclose(fp); return; }