组合语言之艺术
第二节 研究改进

  想要把程序写好,一定要不断地研究、改进,由错误中学习,由改进中得到经验,
培养出敏锐的观察能力和良好的写作习惯。
    在开始时,这种过程需要付出不少时间,但对一位程序员来说,写程序是终身职业,
能不精益求精吗?
    以下举两个实例,以说明如何研究改进已完成的程序。
  1,指令的运用:
 以下面这段通讯处理程序而论,不仅语法及指令完全正确,执行时也毫无错误,是不是
还可以加以改进呢?
    1-1 按照前面规定,说明项中已用简化的字符串:
 SND-传送  RCV-接收  LET-左
      RGT-右  VER-直  HOR-横
    1-2 程序员代号为'C'。
    1-3 段名省略。
    1: CSND0:
    2:  MOV DX,03FDH   ; 输出埠
    3:  MOV AL,80H
    4:  OUT DX,AL    ; 输出指令
    5:  MOV DX,03F8H   ; LSB 速度控制
    6:  MOV AL,06H    ; 速度=19200/秒
    7:  OUT DX,AL
    8:  MOV DX,03F9H   ; MSB 速度控制
    9:  MOV AL,0    ; 速度=19200/秒
   10:  OUT DX,AL
   11:  MOV DX,03FBH   ; 行控制暂存器
   12:  MOV AL,03H    ; NO PARITY,1
                     ; STOP,8
   13:  OUT DX,AL
   14:  MOV DX,03FCH   ; 通讯控制
   15:  OUT DX,AL
   16:  MOV DX,03F9H   ; 中断有效
   17:  MOV AL,0
   18:  OUT DX,AL
   19: CSND1:
   20:  MOV DX,03FDH   ; 状态暂存器
   21:  IN AL,DX
   22:  TEST AL,10H    ; 是否可接收?
   23:  JNZ CRCV0    ; 可
   24:  TEST AL,20H    ; 信道已清否?
   25:  JZ CSND1    ; 8250未清
   26:  MOV AH,1    ; 键盘有输入?
   27:  INT 16H
   28:  CMP AL,07H    ;  ='CTRL+G'
   29:  JE CEND    ; 是,完毕
   30:  MOV DX,03F8H
   31:  OUT DX,AL    ; 送输入字符
   32:  JMP CSND1
   33: CRCV0:     ; 接收
   34:  MOV DX,03FCH   ; 通讯控制
   35:  MOV AL,08H    ; 暂停中断
   36:  OUT DX,AL
   37:  MOV DX,3F8H
   38:  IN AL,DX    ; 收字符
   39:  MOV AH,0EH
   40:  INT 10H    ; 萤屏显示
   41:  MOV DX,03FCH
   42:  MOV AL,0BH
   43:  OUT DX,AL    ; 继续接受
   44:  JMP CSND1    ; 循环工作
   45: CEND:
   46:  RET    ; 完成
 本段程序共 84 个字符,非常精简,但仍然有节省的余地,要点在DX的数值上。
 DX值由 03F8H到 03FDH,可知 DH 之值不变,只需改变 DL 即可。每改变DX一次,需要
三个字符,如仅变DL,只需两个字符。这一指令共享了十一次,除第一次有必要外,其
它十次就可以省下10个字符。
 再要斤斤计较,还可以榨出二个字符来,在5至8条中,若用INC  DX 只需要一个字符。
 此外,31,32及43 ,44是浪费的作法,只要在第18条加一标号,就可以省却两个字符输
出的指令。另外,还有35及39两条指令,应该合并,一次即将AX设妥,于是,又省下了
一个字符。
      先令 DH=3
    1: CSEND0:
    2:  MOV DL,0FDH    ; 输出埠
    3:  MOV AL,80H
    4:  OUT DX,AL    ; 输出指令
    5:  MOV DL,0F8H    ; LSB 速度控制
    6:  MOV AL,06H    ; 速度=19200/秒
    7:  OUT DX,AL
    8:  INC DX    ; MSB 速度控制
    9:  SUB AL,AL    ; 速度=19200/秒
   10:  OUT DX,AL
   11:  MOV DL,0FBH    ; 行控制暂存器
   12:  MOV AL,DH    ;NO PARITY,1
       ; STOP,8
   13:  OUT DX,AL
   14:  INC DX    ; 通讯控制
   15:  OUT DX,AL
   16:  MOV DL,0F9H    ; 中断有效
   17:  SUB AL,AL
   18: CSNDA:
   19:  OUT DX,AL
   20: CSND1:
   21:  MOV DL,0FDH    ; 状态暂存器
   22:  IN AL,DX
   23:  TEST AL,10H    ; 是否可接收?
   24:  JNZ CRCV0    ; 可
   25:  TEST AL,20H    ; 信道已清否?
   26:  JZ CSND1    ; 8250未清
   27:  MOV AH,1    ; 键盘有输入?
   28:  INT 16H
   29:  CMP AL,07H    ;  ='CTRL+G'
   30:  JE CEND    ; 是,完毕
   31:  MOV DL,0F8H
   32:  JMP CSNDA    ; 送输入字符
   33: CRCV0:     ; 接收
   34:  MOV DL,0FCH    ; 通讯控制
   35:  MOV AX,0E08H   ; 暂停中断
   36:  OUT DX,AL    ; 及显示
   37:  MOV DL,0F8H
   38:  IN AL,DX    ; 收字符
   39:  INT 10H    ; 萤屏显示
   40:  MOV DL,0FCH
   41:  MOV AL,0BH
   42:  JMP CSNDA    ; 循环工作
   43: CEND:
   44:  RET    ; 完成
 看来似乎这样太小气,可是所谓艺术,就要具备丝毫不苟且的态度,再说由84个字符变
成66个字符,省了近百分之廿,而且,速度也快了。这种程序原本就很精简,只有训练
有素,追求完美的程序员,才做得到。
 另一种做法,便是将重复的过程写成回路,约可节省廿几个字符。但是,由于时间定律
限制,通讯程序颇重时效,回路是否值得,尚要多方面分析,不可轻率决定。
  2,回路的实例:
 前面曾经讨论过,程序的效率,经常决定于回路的处理方式及其技巧。其对空间上影响
比较小,但是良好的设计理念,常使速度上有高达十倍,甚至百倍的差异,读者想必已
经知道,但是如何能应用已知的技巧,来改进设计的程序呢?
 回路是利用计数器,反复进行相同的程序作业,这种程序,目的就是为了节省空间,相
对地,时间上难免有所损失。
      因此,在设计回路时,必须先行考虑清楚: 空间的节省与时间的交换是否值得?
 其次,则要充份掌握回路的特色,要用得恰到好处,不可掉以轻心。
      原则上,在回路中,指令要用得精简,流程要非常明确,尤其重要的是,应力
求避免在回路中使用缓冲器,最好充份利用暂存器。如果时间效率极为重要,则不妨放
弃回路方式。
      有一个显示程序,目的是要将 16*16点阵字形送到屏幕上。对象是Hercules 6
40*400的图形卡,计分四区交互传送,这是另一个「高科技」界的新鲜奇事,在IBM PC
推出时,最高密度的图形态,只有 640X200点阵,那是迁就电视屏幕的扫描方式,先送
单线的水平讯号,再送双线,故分两区。Hercules卡为了加高密度,应用Interlace 技
术,又在单双水平扫描线中各加了一行,遂成了四区。
      Hercules很适宜中文的显示,如用 16X16字形,正好显示25行,每行40字,与
英文完全兼容。若希望有一状态显示栏,则可用 15X15字形,留出24条线供做状态栏。
      遗憾的是在最需要中文的国内,却偏爱CGA,EGA 等密度不足的显示设备。不但
售价偏高,功能也不足,弄得不伦不类。
      最理想的还是VGA 显示,计有 640X480之屏幕点阵,不仅空间大,在存贮器中,
只有一区,应用非常灵活。
      下面,我们先介绍 Hercules 的显示方法,同时探讨回路的处理方式。
    1: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    2: ;HERCULES 中文显示处理程序。   ;
    3: ;输入参数:SI=点阵字形,DI=屏幕位置。   ;
    4: ;     DS =CG,ES= 0B800H(屏幕段)。   ;
    5: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    6: CDSP0:
    7:   MOV   CX,16  ;高16点
    8: CDSP1:
    9:   MOVSW   ;移至屏幕上
   10:   ADD   DI,1FFEH  ;加一区,每区=2000H
   11:   JNS   CDSP2  ;未超越区限,继续
   12:   ADD   DI,8050H  ;超越区限,换区加行
   13: CDSP2:
   14:   LOOP  CDSP1
   15:   RET
 程序到此结束,相当精简,技巧在第10至12条区限的检测方式。一般做法是在检查区限
时,用:
     ADD   DI,1FFEH  ; 加区值
     CMP   DI,8000H  ; 最大区限值
     JB   CDSP2  ; 未超过
     SUB   DI,8000H  ; 减去区限
 如此则多了一条4字符的指令,加上4个时钟脉冲,做16次回路就损失64个时钟脉冲值。
在全屏幕显示时,以1,000 个字来算,为数就不少了。
 当然,取消了回路速度还可以加快,其结果,则要增加130 个字符,时间则快了 272个
时钟脉冲,是否值得,就要看实际需要而定了。
 另一个方法,要增加2个字符,但可快上36个时钟脉冲,其法在第11条上:
   11:   JS   CDSP3
   12:CDSP2:
   13:   LOOP  CDSP1
   14:   RET
   15:CDSP3:
   16:   ADD   DI,8050H
   17:   JMP   CDSP2
 再换一个方法,如果先使 BX 为1FFEH,DX为8050H,则在原程序中,将第10条及12条分
别改为:
   10:   ADD   DI,BX
   12:   ADD   DI,DX
 这一来,时钟脉冲快了2个,16次则快更多,如果再加上取消回路,其意义更大。空间
原增加 130字符,现仅94字符,时间则省下 304个时钟脉冲。如果全屏幕显示了1,000
个字,在8MHZ频率下,将会加快 1/25 秒的速度。
 在回路中,如果讲求时间效益,应极力避免使用PUSH及POP ,因PUSH需15个时钟脉冲,
而POP 则要12个,两者相加是27个时钟脉冲,非常不值得。
 解决方法之一是:设法将欲保留之值贮存在没有用到的寄存器中;再若是固定的常数,
也不妨在每次要用时重新置入,只不过是4个时钟而已。最麻烦是变量值,除了在设计
模块之前,妥当地安排外,别无良策。

上一页    下一页