组合语言之艺术
附录二 程序语言效率分析

  以下为利用ASSEMBLY,BASIC,PASCAL,C,FORTRAN 等程序语言,将一个24x 24之点阵
字形,放大成为48x 48,并分别比较其处理速度、占用空间以及制作时间。
    为了正确计算执行时间,特意作 10,000 次处理,至于指定的24x 24字形,则假设
为一空格。
一、ASSEMBLY
    汇编语言变化无穷,先以一般的作法,用点阵位移来处理。
    1: PAGE   60, 132
    2: CG   SEGMENT
    3: BUFIN  DB  72 DUP(0)
    4: BUFOT  DB  72*4 DUP(0)
    5:    ASSUME CS:CG,DS:CG,ES:CG
    6: START:
    7:    MOV   AX,CG
    8:    MOV   DS,AX
    9:    MOV   ES,AX
   10:    CLD
   11:    MOV   BP,10000  ; 处理10,000次
   12: S3:
   13:    SUB   CX,CX
   14:    MOV   BX,CX
   15:    MOV   DX,1803H    ; 计数用
   16:    MOV   SI,OFFSET BUFIN  ; 24*24 点阵起始地址
   17:    MOV   DI,OFFSET BUFOT  ; 预定48*48储存地址
   18: MVBYTE:
   19:    MOV   BH,DL    ; 做三列
   20: MVDB:
   21:    LODSB     ; 取原点阵
   22:    MOV   BL,AL
   23:    MOV   CL,8    ; 做八位
   24: MVDB1:
   25:    RCL   BL,1    ; 左移一次
   26:    PUSHF     ; 保存状态
   27:    RCL   AX,1    ; 两字同时左移一次
   28:    POPF     ; 取出原移位状态
   29:    RCL   AX,1    ; 再一次,得双位点值
   30:    LOOP  MVDB1    ; 八次回路
   31:    STOSW     ; 存入
   32:    MOV   [DI+4],AX    ; 上下放大一行
   33:    DEC   BH    ; 共 3列
   34:    JNZ   MVDB
   35:    ADD   DI,6    ; 移向次行
   36:    DEC   DH
   37:    JNZ   MVBYTE    ; 共24行
   38:    DEC   BP    ; 执行10,000次
   39:    JNZ   S3    ; 完成
   40:    MOV   AX,4C00H
   41:    INT   21H
   42: CG   ENDS
   43:    END   START
    本程序制作时间,为十五分钟。
    经汇编后,得934 字符的执行程序,执行耗时14.5秒。
    若将上段程序加以分析,可以发现到此段程序执行时间全部浪费在23至30这一段
「回路」中。为了增加速度,可以将空间加大,避开回路,连续执行八次「移位」动作
如次:
   23:    RCL   BL,1
   24:    RCL   AX,1
   25:    SHL   AX,1
   26:    同上共八次
   ...
   47:    MOV   CX,AX    ; AX中为单位元值
   48:    SHR   CX,1    ; CX得到双位点阵值
   49:    OR   AX,CX    ; 双位点阵合并
    似此,程序增大了36字符,但执行时间却减少为 7.1秒,速度快了一倍!
    是不是还是更好的方法呢?相信一定多得不计其数。比如说,我们已知原点阵放大
一倍后点形为「双点」,以双点做表,取其对应之值,即可免除各点移位的手续,再将
原程序第18条以下改为:
   18: VT2:
   19:    CALL  MVBYTE    ; 放大一行
   20:    SUB   SI,3    ; 纵向尚须放大一次
   21:    CALL  MVBYTE    ; 再放大一行
   22:    DEC   DH    ; 完成否?
   23:    JNZ   VT2    ; 再做
   24:    RET     ; 完成
   25: MVBYTE:
   26:    MOV   CL,DL    ; 一行有三字符
   27: MVDB:
   28:    LODSB     ; 取一字符
   29:    MOV   AH,AL    ; 分置两处
   30:    AND   AX,0FF0H    ; AH,AL 各取四位
   31:    SHR   AL,1    ; 右移四次还原
   32:    SHR   AL,1
   33:    SHR   AL,1
   34:    SHR   AL,1
   35:    MOV   BL,AL
   36:    MOV   AL,BYTETB[BX]  ; 左字符取预设表值
   37:    MOV   BL,AH
   38:    MOV   AH,BYTETB[BX]  ; 右字符取表值
   39:    STOSW     ; 得二字符置缓冲器中
   40:    LOOP  MVDB    ; 做三次
   41:    RET
   42      ; 转换表
   43: BYTETB DB  000H,003H,00CH,00FH,030H,033H,03CH,03FH
   44:    DB  0C0H,0C3H,0CCH,0CFH,0F0H,0F3H,0FCH,0FFH
   45: CG   ENDS
   46:    END   START
    再换个方法,因为有个XALT的指令,是专为这种程序所设计的。由第25条起,调整
如下:
   25: MVBYTE:
   26:    MOV   CL,4    ; 供AL左移四位用
   27:    MOV   BX,OFFSET BYTETB
   28: MVDB:
   29:    LODSB     ; 取一字符
   30:    MOV   AH,AL    ; 分置两处
   31:    AND   AX,0F00FH    ; AH,AL 各取四位
   32:    SHR   AL,CL
   33:    XLAT       ; 将[BX+AL]值放AL中
   34:    XCHG  AL,AH
   35:    XLAT
   36:    STOSW
   37:    DEC   DL
   38:    JNZ   MVDB
    如此,执行程序959 字符,执行速度3.2 秒,效率更佳。
    上述程序的缺点为:在循环过程中,速度有所损失,而且用四位查表也费事耗时。
如果用一字符查表,则需增大「表」的对应值,再改为「总表」的方式,一次即可查到。
且由第20行改起,并力求指令的精简,如:
   20:    MOV   DX,OFFSET BYTETB
   21: MVDB:
   22:    LODSB
   23:    SUB   AH,AH
   24:    SHL   AX,1    ; 一字符须变为二字符
   25:    ADD   AX,DX      ; 之位置以查表
   26:    MOV   BX,AX    ; BX可供间接寻址用
   27:    MOV   AX,[BX]    ; 以一字符查表值
   28:    STOSW     ; 查妥存入第一行
   29:    MOV   [DI+4],AX    ; 上下再重复一行
   30:    LODSB
   31:    SUB   AH,AH    ; 处
   32:    SHL   AX,1    ; 理
   33:    ADD   AX,DX
   34:    MOV   BX,AX    ; 第
   35:    MOV   AX,[BX]    ; 二
   36:    STOSW     ; 列
   37:    MOV   [DI+4],AX    ;
   38:    LODSB     ;
   39:    SUB   AH,AH    ; 处
   40:    SHL   AX,1    ; 理
   41:    ADD   AX,DX
   42:    MOV   BX,AX    ; 第
   43:    MOV   AX,[BX]    ; 三
   44:    STOSW     ; 列
   45:    MOV   [DI+4],AX    ;
   46:    ADD   DI,6    ; 再处理下一行
   47:    LOOP  MVDB    ; 共24次
   48:    DEC   BP    ; 做10,000次
   49:    JNZ   S3    ; 完成
   50:    MOV   AX,4C00H
   51:    INT   21H
   52:    RET
    程序到此为止,下面还有一转换总表,可供各程序共享。
    1:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    2:; 转  换  表    ;
    3:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    4: BYTETB LABEL WORD
    5: DB   000H,000H,000H,003H,000H,00CH,000H,00FH
    6: DB   000H,030H,000H,033H,000H,03CH,000H,03FH
    7: DB   000H,0C0H,000H,0C3H,000H,0CCH,000H,0CFH
    8: DB   000H,0F0H,000H,0F3H,000H,0FCH,000H,0FFH
    9: DB   003H,000H,003H,003H,003H,00CH,003H,00FH
   10: DB   003H,030H,003H,033H,003H,03CH,003H,03FH
   11: DB   003H,0C0H,003H,0C3H,003H,0CCH,003H,0CFH
   12: DB   003H,0F0H,003H,0F3H,003H,0FCH,003H,0FFH
   13: DB   00CH,000H,00CH,003H,00CH,00CH,00CH,00FH
   14: DB   00CH,030H,00CH,033H,00CH,03CH,00CH,03FH
   15: DB   00CH,0C0H,00CH,0C3H,00CH,0CCH,00CH,0CFH
   16: DB   00CH,0F0H,00CH,0F3H,00CH,0FCH,00CH,0FFH
   17: DB   00FH,000H,00FH,003H,00FH,00CH,00FH,00FH
   18: DB   00FH,030H,00FH,033H,00FH,03CH,00FH,03FH
   19: DB   00FH,0C0H,00FH,0C3H,00FH,0CCH,00FH,0CFH
   20: DB   00FH,0F0H,00FH,0F3H,00FH,0FCH,00FH,0FFH
   21: DB   030H,000H,030H,003H,030H,00CH,030H,00FH
   22: DB   030H,030H,030H,033H,030H,03CH,030H,03FH
   23: DB   030H,0C0H,030H,0C3H,030H,0CCH,030H,0CFH
   24: DB   030H,0F0H,030H,0F3H,030H,0FCH,030H,0FFH
   25: DB   033H,000H,033H,003H,033H,00CH,033H,00FH
   26: DB   033H,030H,033H,033H,033H,03CH,033H,03FH
   27: DB   033H,0C0H,033H,0C3H,033H,0CCH,033H,0CFH
   28: DB   033H,0F0H,033H,0F3H,033H,0FCH,033H,0FFH
   29: DB   03CH,000H,03CH,003H,03CH,00CH,03CH,00FH
   30: DB   03CH,030H,03CH,033H,03CH,03CH,03CH,03FH
   31: DB   03CH,0C0H,03CH,0C3H,03CH,0CCH,03CH,0CFH
   32: DB   03CH,0F0H,03CH,0F3H,03CH,0FCH,03CH,0FFH
   33: DB   03FH,000H,03FH,003H,03FH,00CH,03FH,00FH
   34: DB   03FH,030H,03FH,033H,03FH,03CH,03FH,03FH
   35: DB   03FH,0C0H,03FH,0C3H,03FH,0CCH,03FH,0CFH
   36: DB   03FH,0F0H,03FH,0F3H,03FH,0FCH,03FH,0FFH
   37: DB   0C0H,000H,0C0H,003H,0C0H,00CH,0C0H,00FH
   38: DB   0C0H,030H,0C0H,033H,0C0H,03CH,0C0H,03FH
   39: DB   0C0H,0C0H,0C3H,0C0H,0CCH,0C0H,0CFH,0C0H
   40: DB   0C0H,0F0H,0C0H,0F3H,0C0H,0FCH,0C0H,0FFH
   41: DB   0C3H,000H,0CH3,003H,0C3H,00CH,0C3H,00FH
   42: DB   0C3H,030H,0C3H,033H,0C3H,03CH,0C3H,03FH
   43: DB   0C3H,0C0H,0C3H,0C3H,0C3H,0CCH,0C3H,0CFH
   44: DB   0C3H,0F0H,0C3H,0F3H,0C3H,0FCH,0C3H,0FFH
   45: DB   0CCH,000H,0CCH,003H,0CCH,00CH,0CCH,00FH
   46: DB   0CCH,030H,0CCH,033H,0CCH,03CH,0CCH,03FH
   47: DB   0CCH,0C0H,0CCH,0C3H,0CCH,0CCH,0CCH,0CFH
   48: DB   0CCH,0F0H,0CCH,0F3H,0CCH,0FCH,0CCH,0FFH
   49: DB   0CFH,000H,0CFH,003H,0CFH,00CH,0CFH,00FH
   50: DB   0CFH,030H,0CFH,033H,0CFH,03CH,0CFH,03FH
   51: DB   0CFH,0C0H,0CFH,0C3H,0CFH,0CCH,0CFH,0CFH
   52: DB   0CFH,0F0H,0CFH,0F3H,0CFH,0FCH,0CFH,0FFH
   53: DB   0F0H,000H,0F0H,003H,0F0H,00CH,0F0H,00FH
   54: DB   0F0H,030H,0F0H,033H,0F0H,03CH,0F0H,03FH
   55: DB   0F0H,0C0H,0F0H,0C3H,0F0H,0CCH,0F0H,0CFH
   56: DB   0F0H,0F0H,0F0H,0F3H,0F0H,0FCH,0F0H,0FFH
   57: DB   0F3H,000H,0F3H,003H,0F3H,00CH,0F3H,00FH
   58: DB   0F3H,030H,0F3H,033H,0F3H,03CH,0F3H,03FH
   59: DB   0F3H,0C0H,0F3H,0C3H,0F3H,0CCH,0F3H,0CFH
   60: DB   0F3H,0F0H,0F3H,0F3H,0F3H,0FCH,0F3H,0FFH
   61: DB   0FCH,000H,0FCH,003H,0FCH,00CH,0FCH,00FH
   62: DB   0FCH,030H,0FCH,033H,0FCH,03CH,0FCH,03FH
   63: DB   0FCH,0C0H,0FCH,0C3H,0FCH,0CCH,0FCH,0CFH
   64: DB   0FCH,0F0H,0FCH,0F3H,0FCH,0FCH,0FCH,0FFH
   65: DB   0FFH,000H,0FFH,003H,0FFH,00CH,0FFH,00FH
   66: DB   0FFH,030H,0FFH,033H,0FFH,03CH,0FFH,03FH
   67: DB   0FFH,0C0H,0FFH,0C3H,0FFH,0CCH,0FFH,0CFH
   68: DB   0FFH,0F0H,0FFH,0F3H,0FFH,0FCH,0FFH,0FFH
   69: CG   ENDS
   70: END  START
    本程序因为加了个转换表,空间增大为1471字符,但速度却加快为2.5 秒,这是空
间换时间的最佳例证。
二、C
    C近来极受美国各系统公司的推崇,我们特以之与汇编语言作个比较,但不幸的是
在指令的精简上,就显得力不从心,不像汇编语言那样可以斤斤计较。
    因此,我们只能就点阵移位、查小表及查总表的方式,测试其效率。首先,利用查
大表的方式如下:
  1: main()
  2: {
  3:  unsigned char  s[24][3];
  4:  unsigned short  tab[256], d[48][3], count;
  5:  register short  i,j,k;
  6:
  7:  for (count = 0; count < 10000; count++)
  8:  {
  9:   k = 0;
 10:   for (i = 0; i < 24; i++)
 11:   {
 12:   for (j = 0; j < 3; j++)
 13:   d[k][j] = d[k + 1][j] = tab[s[i][j]];
 14:   k += 2;
 15:   }
 16:  }
 17: }
    程序制作时间10分钟,较汇编语言稍快;占用空间4575字符,则大了三倍,至于执
行速度为18秒,慢了七倍之多。
    再换个方法,试一试查小表如次:
  1: main()
  2: {
  3:  unsigned char i,j, s[24][3], d[48][6], tab[16];
  4:  unsigned short  count;
  5:  register short  k, l, x;
  6:
  7:  for (count = 0; count < 10000; count++)
  8:  {
  9:   k = 0;
 10:   for (i = 0; i < 24; i++)
 11:   {
 12:   l = 0;
 13   for (j = 0; j < 3; j++)
 14:   {
 15:    x = s[i][j];
 16:    d[k][l] = d[k + 1][l] = tab[x & 0360 >> 4];
 17:    d[k][l+1] = d[k + 1][l + 1] = tab[x & 017];
 18:    l += 2;
 19:   }
 20:   k += 2;
 21:   }
 22:  }
 23: }
    占用空间为4,693 字符,比汇编语言大了五倍;速度为30秒,则慢了四倍多。这证
明了汇编语言的灵活性,在空时效率交换的技术运用下,可以选择最有利的条件。再看
利用位置的方式,结果如何?
  1: main()
  2: {
  3:  unsigned char  ss[24][3];
  4:  unsigned short  dd[48][3];
  5:  int   i, k, count;
  6:  register short  d, j;
  7:  register unsigned char   s;
  8:
  9:  for (count = 0; count < 10000; count++)
 10:  {
 11:   k = 0;
 12:   for (i = 0; i < 24; i++)
 13:   {
 14:   for (j = 0; j < 3; j++)
 15:   {
 16:    s = ss[i][j];
 17:    d = 0;
 18:    if (s & 01)
 19:  d |= 03;
 20:    if (s & 02)
 21:  d |= 014;
 22:    if (s & 04)
 23:  d |= 060;
 24:    if (s & 010)
 25:  d |= 0300;
 26:    if (s & 020)
 27:  d |= 01400;
 28:    if (s & 040)
 29:  d |= 06000;
 30:    if (s & 0100)
 31:  d |= 030000;
 32:    if (s & 0200)
 33:  d |= 0140000;
 34:    dd[k + 1][j] = dd[k][j] = d;
 35:   }
 36:   k += 2;
 37:   }
 38:  }
 39:}
    占用的空间为 4,727字符,较汇编语言大四倍,执行时间29秒,差不多是四倍的差
异。这种采用高阶指令的方式,拉近了C与汇编语言的距离。足证纵然使用汇编语言,
若不采用精简指令的技巧,其效率不彰。一般程序员很少在汇编语言的技巧上下功夫,
以致不能认识汇编语言的真面目。
三、BASIC
 10: DIM wd24(23,2),WD48(47,5),table(255),mask(7)
 20: r1=0
 30: r2=0
 40: REM  用测试点的方式,每字符分八次处理。
 50: mask(0)=0
 60: mask(1)=2
 70: FOR i=2 TO 7
 80: mask(i)=mask(i-1)*2
 90: NEXT i
100: INPUT A$
110: FOR count=1 TO 10
120: K=0
130: FOR i=O TO 23
140: T=0
150: FOR j=0 TO 2
160: FOR m=0 TO 7
170: temp=table(wd24(i,j))
180: temp=temp AND mask(m)
190: IF temp=128 THEN r1=192 AND r1
200: IF temp=64 THEN r1=48 AND r1
210: IF temp=32 THEN r1=12 AND r1
220: IF temp=16 THEN r1=3 AND r1
230: IF temp=8 THEN r2=192 AND r2
240: IF temp=128 THEN r2=48 AND r2
250: IF temp=64 THEN r2=12 AND r2
260: IF temp=32 THEN r2=3 AND r2
270: NEXT m
280: wd48(K,T)=r1
290: wd48(K,T+1)=r2
300: wd48(K+1,T)=r1
310: wd48(K+1,T+1)=r2
320: T=T+2
330: NEXT j
340: K=K+2
350: NEXT i
360: NEXT count
370: PRINT "FINISHED"
380: END
    本程序制作时间为10分钟,执行程序共占12,764字符,执行时间为23,000秒!
    足证BASIC 不适用于点阵处理,由于上述的处理方法是以移位为主,因BASIC 没有
专用的指令,所以非常不利。现在改用查表方法,再看如何。
 10: REM  本程序将24*24的点阵以查表方式转为48*48
 20: REM  本程序用QuickBasic Version 4.00 Microsoft inc.
 30: DIM WD24(23,2),WD48(47,2).TABLE(255)
 40:  FOR K=1 TO 100
 50:   T=0
 60:   FOR I=0 TO 23
 70:   FOR J=0 TO 2
 80:    A=TABLE(WD24(I,J))
 90:    WD48(T,J)=A
100:    WD48(T+1,J)=A
110:   NEXT J
120:   NEXT I
130:  NEXT K
140: END
    本程序所用对照表与一、同,执行程序占11,642字符,执行时间共计1,800 秒。
    其它的改进方法当然还有,可是看来已接近极限。
四、PASCAL
    PASCAL仅适用于查总表的方式,在我们没有发展出「制表法」以前,几乎要放弃这
个试验。现在,且沿用汇编语言所用的总表,看其效率如何吧!
  1: PROGRAM PASTABLE;
  2: VAR
  3:  SOURCE :PACKED ARRAY[1...24,1...3] OF -128...127;
  4:  OBJCT :ARRAY[1...48,1...3] OF INTEGER;
  5:  TABLE :ARRAY[0...255] OF INTEGER;
  6:  I,J,K,N:INTEGER;
  7: BEGIN
  8:  FOR N:=1 TO 10000 DO
  9:  BEGIN
 10:   K:=O;
 11:   FOR I:=1 TO 24 DO
 12:   BEGIN
 13:   FOR J:=1 TO 3 DO
 14:   BEGIN
 15:    OBJCT[K,J]=TABLE[SOURCE[I,J];
 16:    OBJCT[K+1,J]=OBJCT[K,J]
 17:   END;
 18:   K:=K+2
 19:   END
 20:  END
 21: END.
    本程序制作需时10分钟,空间占11,650字符,执行时间为17秒,较BASIC 为佳。
    显然 PASCAL 的效率较C及汇编语言为差,但若不计总表,程序仅21条,差强人意。
五、FORTRAN
    同样的,FORTRAN 也只能用查表的方法,程序如下:
  1: DIMENSION IT1(24,3(,IT2(48,6),IT3(256)
  2: DO 40 II=1,10000
  3: DO 30 I=1,24
  4: M=I+I
  5: DO 30 J=1,3
  6: K=IT3(IT1(I,J))
  7: IT2(M-1,J)=K
  8: 30 IT2(M,J)=K
  9: 40 CONTINUE
 10: END
    这段程序也是用查表的方式,制作时间7分钟,执行程序 9,959字符,比C稍大,
执行速度也较慢,为20秒。另外,在 FORTRAN中也没有找到适合的位控制指令,因此很
难再加改进。
    从上述的试验中,可以看出这几种语言的效率差异。不论用什么方法,汇编语言明
显地遥遥领先。
    就制作时间而言,因为程序简单,看不出很大分别。事实上,汇编语言的确比较复
杂,只是我们习惯成自然,有了经验,所以制作时显得轻松。
    以下为上述测试的统计表:
 ┌────┬────┬────┬──────┬─────┬──────┐
 │处理方式│程序语言│制作时间│  程序空间  │执行速度 │  备  注  │
 │    │    │(分钟)│  (字符)  │ (秒)  │      │
 ├────┼────┼────┼──────┼─────┼──────┤
 │点阵位移│汇编语言│   15   │   970   │   7.1 │      │
 │    │C    │   10   │  4,727   │   29.0 │      │
 │    │BASIC?  │   10   │   12,764   │ 23,000.0 │      │
 ├────┼────┼────┼──────┼─────┼──────┤
 │查小表法│汇编语言│   15   │   949   │   3.2 │边际效益最高│
 │    │C    │   10   │  4,693   │   30.0 │      │
 ├────┼────┼────┼──────┼─────┼──────┤
 │查总表法│汇编语言│   15   │  1,441   │   2.5 │速度效益最高│
 │    │C    │   10   │  4,575   │   18.0 │      │
 │    │PASCAL  │   10   │   11,650   │   17.0 │      │
 │    │FORTRCN │   ?7   │  9,959   │   20.0 │      │
 │    │BASIC   │   10   │   11,692   │  1,800.0 │      │
 └────┴────┴────┴──────┴─────┴──────┘

上一页    下一页