Pergunta

In our system's programming classes, we're being taught assembly language. In most of the sample programs our prof. has shown in classes; he's using:

XOR CX, CX

instead of

MOV CX, 0

or

OR AX, AX
JNE SOME_LABEL

instead of

CMP AX, 0
JNE SOME_LABEL

or

AND AL, 0FH        ; To convert input ASCII value to numeral
; The value in AL has already been checked to lie b/w '0' and '9'

instead of

SUB AL, '0'

My question is the following, is there some kind of better performance when using the AND/OR or XOR instead of the alternate (easy to understand/read) method?

Since these programs are generally shown to us during theory lecture hours, most of the class is unable to actually evaluate them verbally. Why spend 40 minutes of lecture explaining these trivial statements?

Foi útil?

Solução

XOR CX, CX  ;0x31 0xC9

Uses only two bytes: opcode 0x31 and ModR/M byte that stores source and destination register (in this case these two are same).

MOV CX, 0  ;0xB8 0x08 0x00 0x00

Needs more bytes: opcode 0xB8, ModR/M for destination (in this case CX) and two byte immediate filled with zeroes. There is no difference from clocking perspective (both take only one clock), but mov needs 4 bytes while xor uses only two.

OR AX, AX  ;0x0A 0xC0

again uses only opcode byte and ModRM byte, while

CMP AX, 0  ;0x3D 0x00 0x00 <-- but usually 0x3B ModRM 0x00 0x00

uses three or four bytes. In this case it uses three bytes (opcode 0x3D, word immediate representing zero) because x86 has special opcodes for some operations with Accumulator register, but normally it would use four bytes (opcode, ModR/M, word immediate). It's again the same when talking about CPU clocks.

There's no difference to processor when executing

AND AL, 0x0F  ;0x24 0x0F  <-- again special opcode for Accumulator

and

SUB AL, '0'  ;0x2D 0x30 0x00  <-- again special opcode for Accumulator

(only one byte difference), but when you substract ASCII zero, you can't be sure that there won't remain value greater than 9 in Accumulator. Also anding sets OF and CF to zero, while sub sets them according to the result ANDing can be safer, but my personal opinion is that this usage depends on context.

Outras dicas

Apart from code size savings mentioned in the other answers, I thought I'd mention a few more things which you can read more about in Intel's optimization manual and Agner Fog's x86 optimization guide:

XOR REG,REG and SUB REG,REG (with REG being the same for both operands) are recognized by modern x86 processors as dependency breakers; meaning that they also serve a purpose in breaking false dependencies on previous register/flag values. Note that this doesn't necessarily apply if you clear an 8- or 16-bit register, but it will if you clear a 32-bit register.


OR AX, AX
JNE SOME_LABEL

I believe the preferred instruction would be TEST AX,AX. TEST can be macro-fused with any conditional jump (basically combined with the jump instruction into a single instruction prior to decoding) on modern x86 processors. CMP can only be fused with unsigned conditional jumps, at least prior to the Nehalem architecture. Again, I'm not sure if this is the case for 16-bit operands.

  1. Duplicate of What is the best way to set a register to zero in x86 assembly: xor, mov or and? - xor. Although most of those advantages don't apply for a register smaller than 32-bit, at least on modern CPUs. Maybe earlier P6-family CPUs would still special-case xor cx,cx if they rename CX separately from ECX, and CL and CH separately from CX. e.g. to avoid partial-register stalls if writing CL and then reading CX.

    But the code-size advantage always applies.

  2. Duplicate of Test whether a register is zero with CMP reg,0 vs OR reg,reg? - or ax,ax is less efficient on some CPUs than test ax,ax which is designed for this purpose. The use of or seems to be a holdover from 8080. Both save a byte of code-size over cmp ax, 0, but all of those set FLAGS the same way (see my linked answer for that and the 8080 ora a idiom.)

  3. No advantage to AND here. Both are the same code-size (2 bytes). AND reminds you that the low 4 bits of an ASCII digit are the integer value.

    Generally sub al, '0' is more useful because you can do it as part of checking if a character is a digit or not. e.g. sub al, '0' / cmp al, 9 / ja non-digit, otherwise you have the integer value in a register. Using and as the first step there would always create a result in the 0..15 range, thus giving many false positives. See NASM Assembly convert input to integer? for a use-case: a loop that stops at the first non-digit character.

    See also What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa? re: range checks on ASCII.

An important difference is whether they impact the CPU operation flags. When you use logical operations xor, or, etc, then the operation flags are affected. So:

XOR  CX, CX

Will not only zero out CX, but, for example, the zero flag of the CPU will be set. The mov instruction does not affect flags. So:

MOV  CX, 0

Will not, for example, set the zero flag.

In addition to the instruction scheduling mentioned previously, which instruction is faster may also depend on the the actual instruction sequence being executed.

An example of a seeemingly innocent instruction having a great impact see page 8 in this paper by Torbjörn Granlund of GMP fame. In example three on the top right of the page a very fast division loop begins with the "nop" instruction. Acoording to footnote 4 on the same page the absence of the nop instruction causes the loop to execute 1 clock cycle slower. Granlund suggests experimenting by placing other nops inside the loop to achieve further speed-ups.

My initial, gut reaction to this was more instructions = more time. However, there is clearly much more to instruction scheduling and execution than can be gleaned from manuals.

XOR operation works faster than MOV since it is a bitwise operation,all bitwise operations are performed faster by the CPU.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top