File: SCRNIO.TXT on CLUBware Software Diskette #1 Author: Stan Radzio Copyright (C) 1984. RAYHAWK AUTOMATION, N W, INC Introductory User Instructions: To use the speed-up routines (BASPRINT, COMPRINT or PRSLASHO), do the following: 1)Bring your system up with either DOS 1.1 or DOS 2.0. 2)Have access to the routine you need on disk. 3)Key the name of the routine you need and press ENTER. (For example: BASPRINT ) The routine will be loaded by DOS. It will initialize itself, and exit back to DOS, leaving itself resident. That is, it becomes a part of DOS until the next system initialization. If you need the routine for normal work, you should make this invocation part of your AUTOEXEC.BAT. 4)From now on when you invoke your application program, the newly resident code will display data much faster. --------------------------------------------------------------------- The rest of the this article is divided into two sections. The first discusses some of the technical issues involved in developing the Whizzard Screen I/O Routines. This section is probably above anyone who is not familiar with Assembly language. The second section (which starts on Page 4) gives a detailed description of how the individual routines work. You need to read this only if you are interested or if the above description is a little too concise. --------------------------------------------------------------------- Motivation For the Effort I've seen a fair number of articles with titles such as: "What IBM Did Right/Wrong With the PC, PC/XT, PCjr". Several talk about the slow BIOS screen interface. I wanted to do something about those complaints. So, I rewrote the BIOS screen interface, a complete replacement for interrupt 10. I squeezed every bit of speed I could out of that code. I used faster instructions, table lookups instead of multiply instructions, twists in the logic to avoid a divide instruction, and very tight code. What did I get for my trouble? An 8% improvement in the TYPE command, an 11% improvement in BASICA, a 10% improvement in WORDSTAR. We're talking about something not worth the money here. There is a principle of Physics involved, the Conservation of Difficulty. Given the need to support both the graphics and monochrome card, given the ability to segment the buffer on the graphics card into 4 or 8 pages, there is a certain amount of work required to setup the registers for screen I/O, and that work is a conserved quantity. Aha, you say! If I can't eliminate that work, what happens if I send a whole stream of characters to the screen at once instead of a single letter at a time? File: SCRNIO.TXT Page 2 This is a great deduction on your part, Sherlock. We can do the register setup once, then send out a whole stream of characters. As it is now, the BIOS screen interface will only print one character at a time on the screen. (BIOS will repeat a single character several times but this facility is not terribly useful except to blank out a line.) There is a DOS call (AH=9) which appears to handle a stream of characters. Actually, this call breaks the string up and sends it to BIOS a character at a time. Speaking of this DOS call, whose idea was it to use the dollar sign ($) as the string terminator? (Miss Marfle, please get that man on the phone and bring in my air horn.) Besides sitting around complaining, we can respond by writing a subroutine to take the place of the missing BIOS call. Programs that do this, like the IBM Personal Editor, are called memory mapped in some magazines. This makes for very snappy screens for programs that you write yourself. We have provided a set of routines that do this if you are willing to code the CALLs in your BASIC programs. They are included on this diskette. They were the easy part. I also got very tired of the hassles of doing CALLs in the interpreter and created a technique to make it source-compatible with the compiler approach. This technique is also included on this diskette. Now, what can we do for programs we have bought off the shelf? With a little patience and a little luck, we can discover a critical area in many programs where the data to be displayed on the screen is still tied together as a string. The program area will probably look like: (type A) MOV CX,CHAR_COUNT MOV SI,STRING_ADDRESS LABEL: LODSB ; load a character CALL OUTPUT_CHAR LOOP LABEL The subroutine OUTPUT_CHAR will typically PUSH CX, move a 1 into CX (display one character per call), issue an INT 10, move the cursor over one to the right, POP CX, then return. The other usual layout looks like: (type B) MOV SI,STRING_ADDRESS LABEL: LODSB ; load a character OR AL,AL JZ DONE CALL OUTPUT_CHAR JMP LABEL DONE: This piece of code operates on a string where the length is unknown but the end of the string is tagged by a zero (null). Only someone with tapioca for brains would use a dollar sign as a terminator. File: SCRNIO.TXT Page 3 Once you find the critical piece of code within a program, you can modify the CALL OUTPUT_CHAR to call your own subroutine, or you can replace the call with an unused interrupt, like INT 68. Your video subroutine or interrupt handler can then grab up the whole string, send it to the screen in one shot, then return to the main program with conditions set to terminate the loop (leave a 1 in CX or leave SI pointing to a 0). On CLUBware Software Diskette #1, there are three examples of this kind of program modification - one for the BASIC Interpreter, one for Compiled BASIC programs that use BASRUN.EXE, and one for Compiled BASIC programs which do not use BASRUN.EXE. Now we leave theory behind and enter the very complicated real world. Real programs do not have just a type A structure or just a type B structure; most use both techniques. For instance, the BASIC Interpreter uses type A for PRINT statements writing text and type B for error messages. The BASIC compiler uses type A for printing character strings and type B for printing numbers. Both the BASIC Interpreter and the compiler also have a type C structure which looks like: (type C) MOV AL,0D CALL OUTPUT_CHAR to be used when putting a carriage return on the end of a line. Both the Compiler and the Interpreter use such a weird technique for keeping track of where carriage returns have been written and where they might be needed (and when the screen should scroll) that we don't want to mess with type C. This brings up the next point, if you replace a subroutine like OUTPUT_CHAR in someone else's program, keep a copy of the original address in your back pocket. If you get into a situation that your replacement subroutine can't handle, you can pass the call off to the original code. File: SCRNIO.TXT Page 4 OVERVIEWS How BASPRINT can be used to speed up Inpterpreter PRINT statements: 1) Execute BASPRINT to make our code resident. (Do this once after booting the system.) 2) Bring up the BASIC Interpreter by invoking either: BASIC or BASICA. 3) When BASIC clears the screen, BASPRINT intercepts the I/O and takes this opportunity to point the interrupt vector for INT B4 to itself. 4) The OUTPUT_CHAR subroutine for BASIC is burned into system ROM so it can't be changed. However, the first instruction of the OUTPUT_CHAR subroutine is INT B4 which will allow us to intercept the output string and speed it to the screen. 5) When a BASIC PRINT statement executes, it will generate an INT B4. BASPRINT will service the interrupt, intercept the output string, put it on the screen, then nudge the stack a little so control returns to the OUTPUT_CHAR caller with conditions set to terminate the type A loop. 6) The increase in PRINT speed is a function of the string length to be displayed. A seven-fold increase in speed is demonstrated by the TIMEDEMO program on the CLUBware diskette. How COMPRINT is used to speed up PRINT Statements compiled non-/O. 1) Execute COMPRINT to make our code resident. (Do this once after booting the system.) 2) Execute your compiled and linked BASIC program. 3) When the BASIC module, BASRUN.EXE, clears the screen, COMPRINT intercepts the I/O and uses the break in the action to change the first instructions of the OUTPUT_CHAR subroutine into a call to the PRINTER subroutine inside COMPRINT. The OUTPUT_CHAR subroutine inside BASRUN.EXE is loaded into high memory by the initialization code generated by the BASIC compiler and linked from BASRUN.LIB. The exact location of OUTPUT_CHAR is a function of the amount of memory in your machine. COMPRINT tracks it down and modifies it at execution time. This is not terribly difficult since the routine that tried to clear the screen is a near relative to OUTPUT_CHAR (same code segment). 4) When a BASIC PRINT executes, the control will filter down to the OUTPUT_CHAR subroutine who will pass the string to COMPRINT for display on the screen. 5) COMPRINT will increase PRINT speed in the TIMEDEMO program by a factor of eleven. File: SCRNIO.TXT Page 5 How PRSLASHO is used to speed up PRINT Statements compiled with /O. There is a similar module for BASIC compiled with the /O option. The module is called PRSLASHO and the flow of control is close to that in COMPRINT with the extra complication that the data areas accessed by PRSLASHO are not fixed relative to the BASIC library routines. Their final position is set by the LINK command and those data areas must be located by PRSLASHO at execution time. Storage Requirements: You can use all of the modules at the same time. Execute each one after booting the system, then run the BASIC Interpreter, execute a BASIC program compiled with or without the /O option, go back to the Interpreter, whatever. Each module uses about 1056 bytes of memory. Some Limitations: 1) Modules that write directly to the screen buffer will not be sped up. 2) DOS 2.0 pipelining will not pick up the screen output while these programs are running. 3) WINDOW type packages will probably not work properly. 4) BASPRINT will only work with the IBM versions of BASIC and BASICA. It will not work with any version of BASIC that does not use the IBM ROM routines. This should eliminate all other machines.