[unix-pc.general] Slow console driver

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;
}