我对程序集很陌生,我想知道如何通过速度和/或二进制大小优化以及使代码易于读取和支持来改进这个实现。
平台:Linux,x64
汇编程序:NASM
代码节.data
; Consts
%define XChar 'X'
%define OChar 'O'
%define filler '+'
%define LF 0x0a
%define separatorChars " -:,.;"
%define stdin 0x00
%define stdout 0x01
X: db XChar
O: db OChar
currentTurn: db XChar
; Buffers.
field: times 9 db filler
; Strings.
lineFeed: db LF
coordSeparators: db separatorChars
coordSeparatorsL: equ $ - coordSeparators
choosePlayerStr: db "Choose your character - type X or O: "
choosePlayerStrL: equ $ - choosePlayerStr
makeTurn: db 'Enter cell coord delimited by space: ',
makeTurnL: equ $ - makeTurn
boundsError: db 'Coords should be in range 1-3.', LF
boundsErrorL: equ $ - boundsError
cellUsedError: db 'Cell on specified coords is already used.', LF
cellUsedErrorL: equ $ - cellUsedError
separatorNotExistsError: db "Use one of '", separatorChars, "' characters between the coords.", LF
separatorNotExistsErrorL: equ $ - separatorNotExistsError
winStr: db " win!", LF
winStrL: equ $ - winStr
separatorStr: db "---------------------------", LF
separatorStrL: equ $ - separatorStr
drawStr: db "Draw!", LF
drawStrL: equ $ - drawStr
section .bss
buf: resb 256 ; Buffer for user input.
section .text
%macro writeChar 1
mov rax, 0x01
mov rdi, stdout
mov rsi, %1
mov rdx, 0x01
syscall
%endmacro
%macro writeConsole 1
mov rax, 0x01
mov rdi, stdout
mov rsi, %1
mov rdx, %1L
syscall
%endmacro
%macro readConsole 0
mov rax, 0x00
mov rdi, stdin
mov rsi, buf
mov rdx, 256
syscall
%endmacro
global _start
_start:
call printField
chooseCharacter:
writeConsole choosePlayerStr
readConsole
cmp byte [buf], XChar
je characterChosen
cmp byte [buf], OChar
je characterChosen
jmp chooseCharacter
characterChosen:
mov al, [buf]
mov byte [currentTurn], al
nextTurn:
writeConsole makeTurn
readConsole
mov rcx, coordSeparatorsL
checkCoordSeparator:
mov al, byte [buf + 1]
cmp al, byte [rcx + coordSeparators - 1]
je processUserInput
dec rcx
cmp rcx, 0
jg checkCoordSeparator
writeConsole separatorNotExistsError
jmp nextTurn
processUserInput:
movzx r8, byte [buf]
movzx r9, byte [buf + 2]
sub r8, '0'
sub r9, '0'
call makeMove
; Save move result (success/fail) in r12, because other registers will
; be used by printField of affected by syscall.
mov r12, rax
call printField
; If move was failed - make turn again.
cmp r12, 0
jne nextTurn
checkForDraw:
mov rcx, 9 ; Max number of loops
mov al, filler
mov rdi, field
repne scasb
cmp rcx, 0
je draw
; Check for win.
; 1. Check columns.
xor cl, cl; Counter.
nextColumn:
mov al, byte [rcx + field] ; Copy 1st cell to al.
; Cell shouldn't contain filler.
cmp al, filler
je endIteration
cmp al, byte [rcx + field + 3]
jne endIteration
cmp al, byte [rcx + field + 6]
je win
endIteration:
inc rcx
cmp rcx, 0x03
jl nextColumn
; 2. Check rows.
xor cl, cl ; Counter.
nextRow:
mov al, byte [rcx * 3 + field] ; Copy 1st cell to al.
; Cell shouldn't contain filler.
cmp al, filler
je endRowIteration
cmp al, byte [rcx * 3 + field + 1]
jne endRowIteration
cmp al, byte [rcx * 3 + field + 2]
je win
endRowIteration:
inc rcx
cmp rcx, 0x03
jl nextRow
; 3. Check diagonals.
; 3.1. Check 1st (\) diagonal.
mov al, byte [field] ; Copy 1st cell to al.
; Cell shouldn't contain filler.
cmp al, filler
je secondDiagonalCheck
cmp al, byte [field + 4]
jne secondDiagonalCheck
cmp al, byte [field + 8]
je win
; 3.2. Check 2nd (/) diagonal.
secondDiagonalCheck:
mov al, byte [field + 2] ; Copy 1st cell to al.
; Cell shouldn't contain filler.
cmp al, filler
je endDiagonalCheck
cmp al, byte [field + 4]
jne endDiagonalCheck
cmp al, byte [field + 6]
je win
endDiagonalCheck:
; Change current player.
cmp byte [currentTurn], XChar
cmove rax, [O]
cmovne rax, [X]
mov byte [currentTurn], al
jmp nextTurn
draw:
writeConsole separatorStr
writeConsole drawStr
jmp exit
win:
writeConsole separatorStr
writeChar currentTurn
writeConsole winStr
exit:
; return 0
mov rax, 60
mov rdi, 0x00
syscall
; ================================================
; void printField()
printField:
xor bx, bx
printRow:
mov rax, 0x01
mov rdi, 0x01
lea rsi, [ebx + ebx * 2 + field]
mov rdx, 0x03
syscall
writeChar lineFeed
inc bx
cmp bx, byte 3
jnz printRow
ret
; ================================================
; ================================================
; makeMove(int8 row, int8 col)
;
; r8: row coord
; r9: column coord
makeMove:
; Indexing start with 0, then we should decrement both
dec r8
dec r9
; Checking bounds.
cmp r8, 0
jl outOfBoundsError
cmp r9, 0
jl outOfBoundsError
cmp r8, 3
jge outOfBoundsError
cmp r9, 3
jge outOfBoundsError
; Check if used.
mov rsi, r8
add rsi, r9
cmp byte [rsi + r8*2 + field], filler
jne usedCellError
; Set cell.
mov rax, [currentTurn]
mov byte [rsi + r8*2 + field], al
xor rax, rax
jmp makeMoveEnd
; Errors.
outOfBoundsError:
writeConsole boundsError
mov rax, 1
jmp makeMoveEnd
usedCellError:
writeConsole cellUsedError
mov rax, 1
jmp makeMoveEnd
makeMoveEnd:
ret
; ================================================Codeflow (不太详细)

发布于 2018-06-14 23:01:29
1)当寄存器为零时,执行xor eax, eax所需的代码比mov eax, 0少,而且运行速度(微观上)更快。还有一个窍门:虽然eax通常只是被认为是“较低的32位rax”,但是如果您执行xor eax, eax,它会隐式地对所有64位位进行归零,并将汇编成更少的字节。
2)查看此代码:
cmp byte [buf], OChar
je characterChosen
jmp chooseCharacter
characterChosen:考虑像这样重组它:
cmp byte [buf], OChar
jne chooseCharacter
characterChosen:如果它不使用jne,它只会继续到下一条语句。因此,这样做可以避免执行额外的跳转。
3)考虑这一守则:
dec rcx
cmp rcx, 0
jg checkCoordSeparatorcmp的工作方式是设置一些标志。在执行jg时,它会查看标志的当前值,以确定要做什么。但是cmp并不是唯一设置标志的指令。dec也是。在这种情况下,您可以尝试如下:
dec rcx
jnz checkCoordSeparator每当您发现自己正在执行cmp时,请检查您是否刚刚做了一些数学运算,以便为您设置标志,这样您就可以节省时间,不再设置它们。
例如,有时您可以重构您的循环:而不是从0上升到3(与3相比),您可以从3下降到0,并使用这个技巧。它所做的工作量是相同的,但执行的指令却少了一个。
4)我不确定在'rcx‘中有什么,但它可能不需要"rcx“可以容纳的全部64位。如果您不期望这个值大于大约2,147,483,647,那么使用32位指令"ecx“可以节省一些费用。请注意,向下移动到16位版本(cx)并没有得到更多的节省,而且可能会更糟。
5)“当时我不确定'rcx‘中有什么内容”--我不确定的原因是你没有任何评论。Asm对人类来说尤其难读。而不是使用人类可以理解的名称(即"NumberOfMoves"),而是使用注册名称(即'rcx')。更糟糕的是,由于只有少数寄存器,所以在没有阅读所有代码的情况下,在任何给定的点上使用它们都不是很清楚。
这就是为什么写得很好的汇编程序充满了注释;每行的结尾通常都是一条注释,上面写着它在做什么(比如这)。
虽然您可能清楚这段代码是做什么的(因为您刚刚编写了它),但是当其他人阅读您的代码时(或者您从现在起6个月后再读一次),这会使事情变得容易得多。
6)然后是这样的:
cmp rcx, 0
je draw在检查寄存器是否为零时,此代码(稍微)更小:
test rcx, rcx
je draw它可以汇编成一个更少的字节(如果可以使用ecx而不是rcx,则减少2个字节)。
请注意,这些建议首先(稍微)改进了代码的大小,并(不知不觉地)提高了代码的性能,但它们不会改变结果。这并不是说你正在做的事情会给出错误的答案,而是因为这不是最有效的方法。奇怪的是,你似乎在某些地方使用这些方法,而不是在其他地方?
IAC,现在,用高级语言(如C)编写代码,让编译器为您解决所有这些问题,几乎总是更好。他们已经知道了所有这些技巧(还有一千多个)。学习汇编语言是有用的,但是你学得越多,你就越意识到人类不应该再用汇编语言写东西了。
https://codereview.stackexchange.com/questions/196220
复制相似问题