Sök…


Introduktion

Familjen x86 har funnits länge, och som sådan finns det många trick och tekniker som har upptäckts och utvecklats som är allmän kunskap - eller kanske inte så offentlig. De flesta av dessa trick utnyttjar det faktum att många instruktioner effektivt gör samma sak - men olika versioner är snabbare eller sparar minne eller påverkar inte flaggorna. Här är ett antal knep som har upptäckts. Var och en har sina för- och nackdelar, så bör listas.

Anmärkningar

Om du är osäker kan du alltid hänvisa till den ganska omfattande Intel 64 och IA-32 Architectures Optimization Reference Manual , som är en stor resurs från företaget bakom själva x86-arkitekturen.

Nollställ ett register

Det uppenbara sättet att nollställa ett register är att MOV i ett 0 - till exempel:

B8 00 00 00 00    MOV eax, 0

Lägg märke till att detta är en 5-byte instruktion.

Om du är villig att klöva flaggorna ( MOV påverkar aldrig flaggorna) kan du använda XOR instruktionen för att bitvis-XOR registret med sig själv:

33 C0             XOR eax, eax

Den här instruktionen kräver endast 2 byte och körs snabbare på alla processorer .

Flytta Carry flaggan till ett register

Bakgrund

Om Carry ( C ) -flaggan har ett värde som du vill sätta i ett register, är det naiva sättet att göra något liknande:

    mov  al, 1
    jc   NotZero
    mov  al, 0
NotZero:

Använd 'sbb'

Ett mer direkt sätt att undvika hoppet är att använda "Subtrahera med lån":

    sbb  al,al    ; Move Carry to al

Om C är noll, blir al noll. Annars blir det 0xFF ( -1 ). 0x01 till: om du behöver vara 0x01

    and  al, 0x01 ; Mask down to 1 or 0

Fördelar

  • Ungefär samma storlek
  • Två eller en färre instruktioner
  • Inget dyrt hopp

Nackdelar

  • Det är ogenomskinligt för en läsare som inte känner till tekniken
  • Det förändrar andra flaggor

Testa ett register för 0

Bakgrund

För att ta reda på om ett register har noll är den naiva tekniken att göra detta:

    cmp   eax, 0

Men om du tittar på opoden för detta får du detta:

83 F8 00      cmp   eax, 0

Använd test

    test   eax, eax      ; Equal to zero?

Undersök den opcode du får:

85 c0         test   eax, eax

Fördelar

  • Endast två byte!

Nackdelar

  • Opaque för en läsare som inte känner till tekniken

Du kan också titta på Q & A-frågan om denna teknik .

Linux-system samtal med mindre uppblåsning

I 32-bitars Linux utförs systemsamtal vanligtvis genom att använda sysenter-instruktionen (jag säger vanligtvis eftersom äldre program använder det nu avskrivna int 0x80 ), men det kan ta mycket plats i ett program och så det finns sätt att kan klippa hörn för att förkorta och påskynda saker.
Detta är vanligtvis layouten för ett systemsamtal på 32-bitars Linux:

mov eax, <System call number>
mov ebx, <Argument 1> ;If applicable
mov ecx, <Argument 2> ;If applicable
mov edx, <Argument 3> ;If applicable
push <label to jump to after the syscall>
push ecx
push edx
push ebp
mov ebp, esp
sysenter

Det är massivt rätt! Men det finns några knep som vi kan dra för att undvika det här röran.
Den första är att ställa in ebp på värdet på esp minskat med storleken på 3 32-bitars register, det vill säga 12 byte. Det här är bra så länge du är ok med att skriva över ebp, edx och ecx med skräp (till exempel när du kommer att flytta ett värde in i dessa register direkt efter hur som helst), kan vi göra detta med LEA-instruktionen så att vi inte behöver att påverka värdet på ESP själv.

mov eax, <System call number>
mov ebx, <Argument 1>
mov ecx, <Argument 2>
mov edx, <Argument 3>
push <label to jump to after the syscall>
lea ebp, [esp-12]
sysenter

Men vi är inte klara, om systemsamtalet är sys_exit kan vi komma undan med att inte skjuta något till stacken!

mov eax, 1
xor ebx, ebx ;Set the exit status to 0
mov ebp, esp
sysenter

Multiplicera med 3 eller 5

Bakgrund

För att få produkten från ett register och ett konstant och lagra det i ett annat register är det naiva sättet att göra detta:

    imul ecx, 3      ; Set ecx to 5 times its previous value
    imul edx, eax, 5 ; Store 5 times the contend of eax in edx

Använd lea

Multiplikationer är dyra operationer. Det är snabbare att använda en kombination av skift och tillägg. För det specifika fallet med att multiplicera striden i ett 32 eller 64 bitars register som inte är esp eller rsp med 3 eller 5, kan du använda lea instruktionen. Detta använder adressberäkningskretsen för att snabbt beräkna produkten.

    lea ecx, [2*ecx+ecx] ; Load 2*ecx+ecx = 3*ecx into ecx
    lea edx, [4*edx+edx] ; Load 4*edx+edx = 5*edx into edx

Många montörer kommer också att förstå

    lea ecx, [3*ecx]
    lea edx, [5*edx]

För alla möjliga multiplikatorer andra dem ebp eller rbp , är den resulterande instruktionslängden densamma som med användning av imul .

Fördelar

  • Kör mycket snabbare

Nackdelar

  • Om din multiplicand är ebp eller rbp tar det en byte mer av dem med imul
  • Mer att skriva om din monterare inte stöder genvägarna
  • Opaque för en läsare som inte känner till tekniken


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow