Michael Abrash‘s Graphics Programming Black Book--chapter1

博客介绍了如何通过一系列优化步骤,从使用C语言的read()函数到直接调用DOS系统调用,再到引入内部缓冲区和汇编优化,逐步提高计算文件16位校验和的效率。文章详细展示了每个阶段的代码实现,并通过实际运行对比展示了优化效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Michael Abrash's Graphics Programming Black Book--chapter1

https://www.jagregory.com/abrash-black-book/
https://github.com/jagregory/abrash-black-book
https://www.gamedev.net/tutorials/_/technical/graphics-programming-and-theory/graphics-programming-black-book-r1698/
https://github.com/jagregory/abrash-black-book/blob/master/src/chapter-01.md
GitCode - 开发者的代码家园

Graphics Programming Black Book | Dr Dobb's

Learning Materials for getting into C programming for MS-DOS/PC-DOS/DR-DOS/FreeDOS | Stephan Sokolow's Blog

https://github.com/jeffpar/abrash-black-book/tree/master/code

首先安装好dosbox和borlandc3.1,然后开始编译相关程序。

本书的第一章就很精彩,举了一个例子,说明优化的步骤:

  • 明确目标
  • 确定总体 bigmap
  • 确定小计划 little map
  • 明确布局--知道计算机如何执行
  • 知道重要部分--哪里影响性能
  • 考虑多种方法--思路多变
  • 精益求精--上汇编了

LISTING 1.1 L1-1.C
/*
* Program to calculate the 16-bit checksum of all bytes in the
* specified file. Obtains the bytes one at a time via read(),
* letting DOS perform all data buffering.
*/
a checksum of the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in size)
BC3.1的编译命令:bcc L1-1.C 或者 在bc图形界面中菜单里面选Make,就可以生产L1-1.exe


L1-1.exe wb.bmp -- 随便找了一个文件

LISTING 1.2 L1-2.C
/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Obtains the bytes one at a time in
* assembler, via direct calls to DOS.
*/
LISTING 1.3 L1-3.ASM
; Assembler subroutine to perform a 16-bit checksum on the file
; opened on the passed-in handle. Stores the result in the
; passed-in checksum variable. Returns 1 for success, 0 for error.
;
; Call as:
;           int ChecksumFile(unsigned int Handle, unsigned int *Checksum);
;
; where:
;           Handle = handle # under which file to checksum is open
;           Checksum = pointer to unsigned int variable checksum is
;           to be stored in
bcc L1-2.C L1-3.ASM
L1-2.exe wb.bmp
The checksum is: 11325  --- 运行明显比L1-1快一些

LISTING 1.4 L1-4.C
采用DOS调用,带缓冲区的,用getc()代替了read()
/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Obtains the bytes one at a time via
* getc(), allowing C to perform data buffering.
*/
bcc L1-4.C
L1-4.exe wb.bmp
明显又快了一些。

LISTING 1.5 L1-5.C
#define BUFFER_SIZE  0x8000   /* 32Kb data buffer */
自己开一个32K缓冲区。换回read()
/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Buffers the bytes internally, rather
* than letting C or DOS do the work.
*/
bcc L1-5.C
L1-5.exe wb.bmp
速度又快了

LISTING 1.6 L1-6.C
/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Buffers the bytes internally, rather
* than letting C or DOS do the work, with the time-critical
* portion of the code written in optimized assembler.
*/
LISTING 1.7 L1-7.ASM

; Assembler subroutine to perform a 16-bit checksum on a block of
; bytes 1 to 64K in size. Adds checksum for block into passed-in
; checksum.
;
; Call as:
;     void ChecksumChunk(unsigned char *Buffer,
;     unsigned int BufferLength, unsigned int *Checksum);
;
; where:
;     Buffer = pointer to start of block of bytes to checksum
;     BufferLength = # of bytes to checksum (0 means 64K, not 0)
;     Checksum = pointer to unsigned int variable checksum is
;stored in
ChecksumLoop:
      lodsb                  ;get the next byte
      add  dx,ax             ;add it into the checksum total
      loop ChecksumLoop      ;continue for all bytes in block
      mov  [bx],dx           ;save the new checksum
lodsb速度很快,比c的循环Checksum += (unsigned int) *WorkingPtr++; 快
bcc L1-6.C L1-7.ASM
L1-6.exe wb.bmp      
得到最快的版本。
程序是逐步优化的,很经典的例子,虽然DOS比较老了,但是思想不老。

lodsb取字符串数据指令(Load String Instruction):从由指针DS:SI所指向的内存单元开始,取一个字节、字或双字进入AL、AX或EAX中,并根据标志位DF对寄存器SI作相应增减。该指令的执行不影响任何标志位。

lodsb指令,将esi指向的地址处的数据取出来赋给AL寄存器,esi=esi+1;

lodsb //作用 mov al,byte ptr [esi] ; esi=esi+sizeof( byte);

和loop配合遍历数组:loop指令会使每循环一次,cx就对自身值减1操作,直到等于0为止,在此之前,一直重复执行标识符到loop间的代码。

附上各阶段程序

L1-1.C--增加了time计时器

/*
* Program to calculate the 16-bit checksum of all bytes in the
* specified file. Obtains the bytes one at a time via read(),
* letting DOS perform all data buffering.
*/
#include <stdio.h>
#include <fcntl.h>
#include <time.h>


main(int argc, char *argv[]) {
     int Handle;
     unsigned char Byte;
     unsigned int Checksum;
     int ReadLength;
     time_t begint, endt;

     if ( argc != 2 ) {
          printf("usage: checksum filename\n");
          exit(1);
     }
     if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
          printf("Can't open file: %s\n", argv[1]);
          exit(1);
     }

     /* Initialize the checksum accumulator */
     Checksum = 0;

     begint = time(NULL);
     /* Add each byte in turn into the checksum accumulator */
     while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) {
          Checksum += (unsigned int) Byte;
     }
     endt = time(NULL);
     if ( ReadLength == -1 ) {
          printf("Error reading file %s\n", argv[1]);
          exit(1);
     }


     /* Report the result */
     printf("The checksum is: %u\n", Checksum);
     printf("diff time: %f\n", difftime(endt,begint));
     exit(0);
}

L1-2.C L1-3.ASM

/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Obtains the bytes one at a time in
* assembler, via direct calls to DOS.
*/

#include <stdio.h>
#include <fcntl.h>

main(int argc, char *argv[]) {
      int Handle;
      unsigned char Byte;
      unsigned int Checksum;
      int ReadLength;

      if ( argc != 2 ) {
            printf("usage: checksum filename\n");
            exit(1);
      }
      if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
            printf("Can't open file: %s\n", argv[1]);
            exit(1);
      }
      if ( !ChecksumFile(Handle, &Checksum) ) {
            printf("Error reading file %s\n", argv[1]);
            exit(1);
      }

      /* Report the result */
      printf("The checksum is: %u\n", Checksum);
      exit(0);
}
; Assembler subroutine to perform a 16-bit checksum on the file
; opened on the passed-in handle. Stores the result in the
; passed-in checksum variable. Returns 1 for success, 0 for error.
;
; Call as:
;           int ChecksumFile(unsigned int Handle, unsigned int *Checksum);
;
; where:
;           Handle = handle # under which file to checksum is open
;           Checksum = pointer to unsigned int variable checksum is
;           to be stored in
;
; Parameter structure:
;
Parms      struc
                 dw        ?       ;pushed BP
                 dw        ?       ;return address
Handle           dw        ?
Checksum         dw        ?
Parms      ends
;
                 .model small
                 .data
TempWord label   word
TempByte         db        ?       ;each byte read by DOS will be stored here
                 db        0       ;high byte of TempWord is always 0
                                   ;for 16-bit adds
;
                 .code
                 public _ChecksumFile
_ChecksumFile    proc near
                 push      bp
                 mov       bp,sp
                 push      si                  ;save C's register variable
;
                 mov       bx,[bp+Handle]       ;get file handle
                 sub       si,si                ;zero the checksum ;accumulator
                 mov       cx,1                 ;request one byte on each ;read
                 mov       dx,offset TempByte   ;point DX to the byte in
                                                ;which DOS should store
                                                ;each byte read
ChecksumLoop:
                 mov       ah,3fh               ;DOS read file function #
                 int       21h                  ;read the byte
                 jc        ErrorEnd             ;an error occurred
                 and       ax,ax                ;any bytes read?
                 jz        Success              ;no-end of file reached-we're done
                 add       si,[TempWord]        ;add the byte into the
                                                ;checksum total
                 jmp       ChecksumLoop
ErrorEnd:
                 sub       ax,ax                ;error
                 jmp       short Done
Success:
                 mov       bx,[bp+Checksum] ;point to the checksum variable
                 mov       [bx],si              ;save the new checksum
                 mov       ax,1                 ;success
;
Done:
                 pop       si                   ;restore C's register variable
                 pop       bp
                 ret
_ChecksumFile    endp
                 end
                 

L1-4.C

/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Obtains the bytes one at a time via
* getc(), allowing C to perform data buffering.
*/
#include <stdio.h>

main(int argc, char *argv[]) {
      FILE *CheckFile;
      int Byte;
      unsigned int Checksum;

      if ( argc != 2 ) {
            printf("usage: checksum filename\n");
            exit(1);
      }
      if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) {
            printf("Can't open file: %s\n", argv[1]);
            exit(1);
      }

      /* Initialize the checksum accumulator */
      Checksum = 0;

      /* Add each byte in turn into the checksum accumulator */
      while ( (Byte = getc(CheckFile)) != EOF ) {
            Checksum += (unsigned int) Byte;
      }

      /* Report the result */
      printf("The checksum is: %u\n", Checksum);
      exit(0);
}

L1-5.C

/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Buffers the bytes internally, rather
* than letting C or DOS do the work.
*/
#include <stdio.h>
#include <fcntl.h>
#include <alloc.h>   /* alloc.h for Borland,
                                malloc.h for Microsoft  */

#define BUFFER_SIZE  0x8000   /* 32Kb data buffer */

main(int argc, char *argv[]) {
      int Handle;
      unsigned int Checksum;
      unsigned char *WorkingBuffer, *WorkingPtr;
      int WorkingLength, LengthCount;

      if ( argc != 2 ) {
            printf("usage: checksum filename\n");
            exit(1);
      }
      if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
            printf("Can't open file: %s\n", argv[1]);
            exit(1);
      }

      /* Get memory in which to buffer the data */
      if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
            printf("Can't get enough memory\n");
            exit(1);
      }

      /* Initialize the checksum accumulator */
      Checksum = 0;

      /* Process the file in BUFFER_SIZE chunks */
      do {
            if ( (WorkingLength = read(Handle, WorkingBuffer,
                  BUFFER_SIZE)) == -1 ) {
                  printf("Error reading file %s\n", argv[1]);
                  exit(1);
            }
            /* Checksum this chunk */
            WorkingPtr = WorkingBuffer;
            LengthCount = WorkingLength;
            while ( LengthCount-- ) {
            /* Add each byte in turn into the checksum accumulator */
                  Checksum += (unsigned int) *WorkingPtr++;
            }
      } while ( WorkingLength );

      /* Report the result */
      printf("The checksum is: %u\n", Checksum);
      exit(0);
}

L1-6.C L1-7.ASM

/*
* Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Buffers the bytes internally, rather
* than letting C or DOS do the work, with the time-critical
* portion of the code written in optimized assembler.
*/
#include <stdio.h>
#include <fcntl.h>
#include <alloc.h>   /* alloc.h for Borland,
                         malloc.h for Microsoft  */

#define BUFFER_SIZE  0x8000   /* 32K data buffer */

main(int argc, char *argv[]) {
      int Handle;
      unsigned int Checksum;
      unsigned char *WorkingBuffer;
      int WorkingLength;

      if ( argc != 2 ) {
            printf("usage: checksum filename\n");
            exit(1);
      }
      if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
            printf("Can't open file: %s\n", argv[1]);
            exit(1);
      }

      /* Get memory in which to buffer the data */
      if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
            printf("Can't get enough memory\n");
            exit(1);
      }

      /* Initialize the checksum accumulator */
      Checksum = 0;

      /* Process the file in 32K chunks */
      do {
            if ( (WorkingLength = read(Handle, WorkingBuffer,
            BUFFER_SIZE)) == -1 ) {
                  printf("Error reading file %s\n", argv[1]);
                  exit(1);
            }
            /* Checksum this chunk if there's anything in it */
            if ( WorkingLength )
                  ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
            } while ( WorkingLength );

            /* Report the result */
            printf("The checksum is: %u\n", Checksum);
            exit(0);
}
; Assembler subroutine to perform a 16-bit checksum on a block of
; bytes 1 to 64K in size. Adds checksum for block into passed-in
; checksum.
;
; Call as:
;     void ChecksumChunk(unsigned char *Buffer,
;     unsigned int BufferLength, unsigned int *Checksum);
;
; where:
;     Buffer = pointer to start of block of bytes to checksum
;     BufferLength = # of bytes to checksum (0 means 64K, not 0)
;     Checksum = pointer to unsigned int variable checksum is
;stored in
;
; Parameter structure:
;
Parms struc
                    dw    ?    ;pushed BP
                    dw    ?    ;return address
Buffer              dw    ?
BufferLength        dw    ?
Checksum            dw    ?
Parms ends
;
     .model small
     .code
     public _ChecksumChunk
_ChecksumChunk proc near
     push  bp
     mov   bp,sp
     push  si                        ;save C's register variable
;
     cld                             ;make LODSB increment SI
      mov  si,[bp+Buffer]            ;point to buffer
      mov  cx,[bp+BufferLength]      ;get buffer length
      mov  bx,[bp+Checksum]          ;point to checksum variable
      mov  dx,[bx]                   ;get the current checksum
      sub  ah,ah                     ;so AX will be a 16-bit value after LODSB
ChecksumLoop:
      lodsb                  ;get the next byte
      add  dx,ax             ;add it into the checksum total
      loop ChecksumLoop      ;continue for all bytes in block
      mov  [bx],dx           ;save the new checksum
;
      pop  si                ;restore C's register variable
      pop  bp
      ret
_ChecksumChunk endp
      end
      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值