Lesson 10-Advanced Instructions and Optimization

Advanced Data Manipulation Instruction Set

String Operation Instructions

String operation instructions (String Instructions) in the x86 architecture are specifically designed for bulk data transfer and comparison, supporting operations at the byte, word, double-word, and even quad-word levels.

Core Instructions and Functions:

InstructionFunction DescriptionExample
MOVSCopy string (MOVSB copies bytes, MOVSW copies words, MOVSD copies double-words)MOVSB (copies [DS:ESI] to [ES:EDI])
CMPSCompare strings (CMPSB compares bytes, CMPSW compares words)CMPSB (compares [DS:ESI] and [ES:EDI])
SCASScan string (compares with AL/AX/EAX; SCASB scans bytes)SCASB (searches for a byte equal to AL in [ES:EDI])
STOSStore string (stores AL/AX/EAX into [ES:EDI]; STOSB stores bytes)STOSB (stores AL into [ES:EDI])
LODSLoad string (loads [DS:ESI] into AL/AX/EAX; LODSB loads bytes)LODSB (loads [DS:ESI] into AL)

Optimization Tips:

  • Direction Flag (DF): Controlled by CLD (DF=0, address increments) or STD (DF=1, address decrements) to manage string operation direction.
  • Repeat Prefix (REP): REP MOVSB repeats MOVSB until ECX=0 (efficient for copying large strings).

Example: Copy 1KB of data using REP MOVSB

section .data
    src db 1024 dup(0xAA)   ; Source data (1KB)
    dst db 1024 dup(0)      ; Destination buffer

section .text
    mov esi, src            ; Source address
    mov edi, dst            ; Destination address
    mov ecx, 1024           ; Copy length (1024 bytes)
    cld                     ; DF=0 (increment addresses)
    rep movsb               ; Repeat copy until ECX=0

Core String Instructions:

; Byte/Word/Double-word operations
movsb   ; [ES:EDI] = [DS:ESI], EDI/ESI auto-increment/decrement
movsw   ; 16-bit version
movsd   ; 32-bit version
movsq   ; 64-bit version

; Compare strings
cmpsb   ; Compare [DS:ESI] and [ES:EDI]
scasb   ; Compare AL and [ES:EDI]

; Store string
stosb   ; [ES:EDI] = AL
stosd   ; 32-bit version

Direction Flag Control:

cld     ; Clear direction flag (EDI/ESI increment)
std     ; Set direction flag (EDI/ESI decrement)

Repeat Prefix Applications

Repeat prefixes (REP, REPE, REPNE) allow a single instruction to be executed multiple times, reducing loop control overhead (e.g., avoiding LOOP) and enabling efficient bulk data processing.

PrefixFunction DescriptionApplicable Instructions
REPRepeat until ECX=0MOVSB, STOSB, etc.
REPERepeat until ECX=0 or ZF=0 (stop when not equal)CMPSB, SCASB, etc.
REPNERepeat until ECX=0 or ZF=1 (stop when equal)CMPSB, SCASB, etc.

Optimization Example: Use REPNE SCASB to find a character

; Function: Find first 'X' (ASCII 0x58) in a string
; Input: ESI = start address, ECX = string length
find_x:
    mov al, 'X'         ; Target character
    mov edi, esi        ; EDI points to current character
    repne scasb         ; Repeat compare until 'X' found or ECX=0
    jne not_found       ; Not found (ZF=0)
    dec edi             ; Adjust EDI to point to 'X' (SCASB already incremented)
    mov eax, edi
    ret
not_found:
    xor eax, eax        ; Return 0 (not found)
    ret

Repeat Prefix Combinations:

rep movsb       ; Repeat MOVSB until ECX=0
repe cmpsb      ; Repeat while equal (stop when ECX=0 or ZF=0)
repne scasb     ; Repeat while not equal (stop when ECX=0 or ZF=1)

; In 64-bit mode
rep stosq       ; Fast memory fill

Performance Optimization Tips:

  • Pre-set ECX to maximum iteration count
  • Combine with MOVNTI for non-temporal stores
  • Use PREFETCH instructions to preload data

Bulk Data Transfer Patterns

Bulk data transfer patterns reduce memory access frequency through block operations, commonly used in file I/O and network data transmission.

Key Techniques:

  • Memory-to-Memory Copy: Achieved via MOVSB/MOVSD with REP prefix (e.g., memcpy).
  • Bulk Load to Registers: Use MOVDQA (aligned 128-bit load) or MOVDQU (unaligned) to load data into XMM registers (SSE instructions).

Example: Bulk load data using SSE

section .data
    data times 16 db 0xAA   ; 16-byte aligned data

section .text
    movdqa xmm0, [data]     ; Load 16 bytes into XMM0 (requires 16-byte alignment)
    ; Subsequent SSE instructions (e.g., `PADDB`) can operate on all bytes in parallel

MOVS Instruction Variants:

movsb   ; Byte transfer
movsw   ; Word transfer (16-bit)
movsd   ; Double-word transfer (32-bit)
movsq   ; Quad-word transfer (64-bit)

; Explicit operand size declaration
movs dword [edi], dword [esi]   ; Explicitly specify operand size

LODS/STOS Instruction Family:

lodsb   ; Load byte into AL
lodsd   ; Load double-word into EAX
stosb   ; Store AL to memory
stosq   ; Store quad-word to memory

Floating-Point Arithmetic Instruction Set

Floating-point instructions handle single-precision (float) and double-precision (double) numbers. x86 supports both the legacy x87 FPU and modern SSE/AVX instruction sets.

Instruction Set Comparison:

Instruction SetData WidthExample InstructionsCharacteristics
x87 FPU80-bit extendedFLD (load), FADD (add)High precision; variable-length instructions; lower performance
SSE128-bitADDSS (scalar single), MULSD (scalar double)Parallel processing: 4 floats or 2 doubles per instruction
AVX256/512-bitVADDPS (256-bit packed single)Wider vectors; higher parallelism

Optimization Example: Parallel floating-point addition with SSE

section .data
    a      dd 1.0, 2.0, 3.0, 4.0    ; 4 single-precision floats (128 bits)
    b      dd 5.0, 6.0, 7.0, 8.0
    result dd 0.0, 0.0, 0.0, 0.0

section .text
    movaps xmm0, [a]        ; Load a into XMM0 (128-bit aligned)
    movaps xmm1, [b]        ; Load b into XMM1
    addps  xmm0, xmm1       ; Parallel add: xmm0 = a + b
    movaps [result], xmm0   ; Store result

Basic FPU Instructions:

fld dword [mem32]       ; Load 32-bit float
fadd st0, st1           ; Floating-point addition
fmul st0, st2           ; Floating-point multiplication
fstp qword [mem64]      ; Store and pop 64-bit float

; Transcendental functions
fsin                    ; Sine
fcos                    ; Cosine
fptan                   ; Tangent

Floating-Point Control Instructions:

fldcw [control_word]    ; Load control word
fstsw ax                ; Store status word
sahf                    ; Transfer to flags register

SIMD Instruction Fundamentals

SIMD (Single Instruction, Multiple Data) processes multiple data elements (e.g., 4 ints, 8 floats) with one instruction, greatly accelerating batch computations. x86 SIMD includes SSE, AVX, and AVX-512.

Core Concepts:

  • Data-Level Parallelism: Pack data into wide vectors (e.g., 128-bit); each instruction operates on all elements simultaneously.
  • Register Width: SSE (128-bit), AVX (256-bit), AVX-512 (512-bit)—enabling more parallel elements.

Example: Compute sum of 8 integers using AVX

section .data
    nums dd 1, 2, 3, 4, 5, 6, 7, 8   ; 8 32-bit integers (256 bits)
    sum  dd 0

section .text
    vmovdqu ymm0, [nums]        ; Load 8 integers into YMM0 (256-bit)
    vphaddd ymm0, ymm0, ymm0    ; Horizontal add: sum groups of 4 (result in low 128 bits)
    vphaddd ymm0, ymm0, ymm0    ; Horizontal add again → total sum in lowest 32 bits
    vmovd eax, ymm0             ; Extract low 32 bits to EAX
    mov [sum], eax              ; Store result (sum = 36)

MMX Instruction Example:

movq mm0, [mem64]       ; Load 64-bit data
paddb mm0, mm1          ; Byte-wise addition
pmullw mm0, mm2         ; Word-wise multiplication
emms                    ; Clear MMX state

Advanced SSE Instructions:

movaps xmm0, [mem128]   ; Aligned load
addps xmm0, xmm1        ; Packed single-precision add
cvtps2dq xmm2, xmm0     ; Convert float to integer

AVX Basic Instructions:

vmovaps ymm0, [mem256]          ; 256-bit load
vaddps ymm0, ymm1, ymm2         ; Three-operand add
vpermq ymm3, ymm0, 0x1B         ; Bitmask permutation

Program Optimization Technique System

Program optimization maximizes performance by reducing instruction execution time, improving memory access efficiency, and minimizing branch mispredictions. This section focuses on instruction scheduling, register allocation, and loop unrolling.

Instruction Scheduling Optimization

Instruction scheduling rearranges instructions to avoid pipeline hazards (data, control, structural), improving CPU pipeline utilization.

Hazard TypeDescriptionSolution
Data HazardInstruction depends on result of previous (e.g., ADD EAX, EBX then use EAX)Insert NOP or reorder instructions
Control HazardBranch (JZ) prevents prefetchingBranch prediction or delay slots
Structural HazardMultiple instructions compete for same unit (e.g., multiplier)Split instructions or use multi-cycle ops

Optimization Example: Eliminate data hazard

; Original (data hazard)
mov eax, [a]        ; EAX = a
add eax, [b]        ; EAX = a + b (depends on EAX)
mul eax, [c]        ; EAX = (a + b) * c (depends on EAX)

; Optimized (reorder, use temporary registers)
mov eax, [a]        ; EAX = a
mov ebx, [b]        ; EBX = b (temp store)
add eax, ebx        ; EAX = a + b
mov ebx, [c]        ; EBX = c (temp store)
mul eax, ebx        ; EAX = (a + b) * c (no hazard)

Pipeline Stall Solutions:

; Original (RAW dependency)
mov eax, [ebx]
add eax, ecx
mov [edx], eax

; Optimized (insert independent instruction)
mov eax, [ebx]
mov esi, [edi]      ; Independent instruction
add eax, ecx
mov [edx], eax

Typical Optimization Patterns:

  • Identify critical path instructions
  • Insert independent instructions to fill bubbles
  • Reorder to eliminate false dependencies

Register Allocation Strategy

Registers are high-speed CPU storage; register allocation minimizes slow memory accesses (10–100× slower than registers).

Key Strategies:

  • Local variables in registers: Keep frequently accessed locals (e.g., loop counters) in registers.
  • Load globals on demand: Only load global variables when needed.
  • Avoid register conflicts: Use compiler techniques (e.g., graph coloring) to assign non-conflicting registers.

Optimization Example: Register reuse in loops

; Original (frequent memory access)
mov ecx, 100
loop_start:
    mov eax, [arr + ecx*4 - 4]      ; Load arr[ecx-1]
    add eax, [arr + ecx*4 - 8]      ; Load arr[ecx-2]
    mov [arr + ecx*4], eax          ; Store to arr[ecx]
    loop loop_start

; Optimized (reuse registers, avoid multiplication)
mov ecx, 100
mov esi, arr                        ; ESI points to array start
loop_start:
    mov eax, [esi + 4]              ; Load arr[i] (i = ecx-1)
    add eax, [esi]                  ; Load arr[i-1] (i = ecx-2)
    mov [esi + 4], eax              ; Store result to arr[i]
    add esi, 4                      ; Increment ESI (avoid multiply)
    dec ecx
    jnz loop_start

Register Pressure Analysis:

; Register allocation example
func:
    push ebp
    mov ebp, esp
    sub esp, 16                     ; Local variable space
    mov eax, [ebp+8]                ; Arg1
    mov ebx, [ebp+12]               ; Arg2
    ; ... ensure eax/ebx not overwritten
    leave
    ret

Optimization Principles:

  • Prefer caller-saved registers
  • Minimize register-to-memory spills
  • Leverage register windows (e.g., x87 FPU stack)

Loop Unrolling Technique

Loop unrolling reduces branch overhead by decreasing loop iterations, enhancing instruction-level parallelism.

Optimization Principle:

  • Reduce branches: From N iterations to N/K (K = unroll factor).
  • Improve ILP: Denser instruction stream enables more parallel execution.

Optimization Example: Unroll by factor of 4

; Original loop (K=1)
mov ecx, 100
loop_start:
    ; Loop body (assume 3 instructions)
    add eax, ebx
    sub ecx, 1
    jnz loop_start

; Unrolled (K=4)
mov ecx, 25             ; 100 / 4 = 25
loop_start:
    ; Repeat loop body 4 times
    add eax, ebx
    add eax, ebx
    add eax, ebx
    add eax, ebx
    sub ecx, 1
    jnz loop_start

Basic Loop Unrolling:

; Original
mov ecx, 100
loop_start:
    ; Loop body
    dec ecx
    jnz loop_start

; Unroll 4x
mov ecx, 25             ; 100/4
loop_start:
    ; Loop body (4 copies)
    ; Loop body
    ; Loop body
    ; Loop body
    dec ecx
    jnz loop_start

Advanced Unrolling Techniques:

  • Loop fusion (merge adjacent loops)
  • Software pipelining (interleave iterations)
  • Full unrolling (for small, fixed loops)

Branch Prediction Optimization

Branch prediction guesses the outcome of conditional branches (JZ, JNZ) to avoid pipeline flushes.

Optimization Strategies:

  • Static prediction: Compiler uses profile data (e.g., GCC -fprofile-use).
  • Dynamic prediction: CPU predictor (e.g., Intel TAGE) learns from recent behavior.
  • Hint instructions: Use __builtin_expect (GCC) or PREDICT to guide prediction.

Optimization Example: Use __builtin_expect

// C code: hint that "condition" is likely true
if (__builtin_expect(condition, 1)) {
    // Hot path
} else {
    // Cold path
}

Compiler generates optimized branch layout (e.g., place hot path inline).

CMOV Instruction Application:

; Original (branching)
cmp eax, ebx
je equal_label
mov ecx, 1
jmp next
equal_label:
mov ecx, 0
next:

; Optimized (branchless)
cmp eax, ebx
cmovz ecx, 0    ; If ZF=1, ecx=0
cmovnz ecx, 1   ; If ZF=0, ecx=1

SETCC Instruction Combination:

cmp eax, ebx
setg al         ; AL=1 if EAX > EBX
setl bl         ; BL=1 if EAX < EBX
; Combine results
movzx ecx, al
shl ecx, 1
or cl, bl

Mixed Programming Techniques

Mixed programming combines assembly’s efficiency with high-level language productivity, ideal for performance-critical code (e.g., math libraries, drivers).

Implementation Methods:

  • Inline Assembly: Embed assembly in C/C++ (e.g., GCC __asm__).
  • External Assembly Modules: Compile .s to .o, link with high-level code.

Optimization Example: Inline assembly for matrix multiply

#include <stdio.h>
void matrix_multiply(int *A, int *B, int *C, int N) {
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            int sum = 0;
            for (int k = 0; k < N; k++) {
                // Inline assembly optimizes inner loop
                __asm__ volatile (
                    "mov %[A_i_k], %%eax \n\t"     // eax = A[i][k]
                    "imul %[B_k_j], %%eax \n\t"    // eax *= B[k][j]
                    "add %%eax, %[sum] \n\t"       // sum += eax
                    : [sum] "+r"(sum)
                    : [A_i_k] "r"(A[i*N + k]), [B_k_j] "r"(B[k*N + j])
                    : "%eax"
                );
            }
            C[i*N + j] = sum;
        }
    }
}

GCC Inline Assembly Template:

asm volatile (
    "movl %1, %%eax\t"
    "addl %2, %%eax\t"
    "movl %%eax, %0"
    : "=r" (result)         // Output
    : "r" (a), "r" (b)      // Inputs
    : "%eax"                // Clobbered register
);

MSVC Inline Assembly:

__asm {
    mov eax, a
    add eax, b
    mov result, eax
}

Best Practices:

  • Minimize assembly code size
  • Clearly specify input/output constraints
  • Save/restore modified registers

System Calls and API Interaction

System calls let user programs request kernel services (e.g., file I/O); APIs wrap these for high-level languages.

Linux System Call Mechanism

Linux uses syscall (x86-64) or int 0x80 (x86):

  • Parameter Passing: rdi (1st), rsi (2nd), rdx (3rd), etc.
  • System Call Number: In rax (e.g., SYS_write=1).
  • Privilege Switch: User (Ring 3) → Kernel (Ring 0).
  • Kernel Execution: Jumps to handler (e.g., sys_write).

Example: Linux write system call (64-bit)

section .data
    msg db 'Hello, Linux!', 0xA
    len equ $ - msg

section .text
global _start
_start:
    ; write(1, msg, len)
    mov rax, 1          ; sys_write
    mov rdi, 1          ; stdout
    mov rsi, msg        ; buffer
    mov rdx, len        ; length
    syscall

    ; exit(0)
    mov rax, 60         ; sys_exit
    xor rdi, rdi        ; status 0
    syscall

Traditional int 0x80:

mov eax, 4      ; sys_write
mov ebx, 1      ; stdout
mov ecx, msg
mov edx, len
int 0x80

section .data
msg db "Hello", 0x0A
len equ $ - msg

Modern syscall:

mov rax, 1      ; sys_write
mov rdi, 1      ; stdout
mov rsi, msg
mov rdx, len
syscall

Windows API Calls

Windows wraps system calls in ntdll.dll (Nt* functions), ultimately using syscall. Parameters often passed via stack.

Example: Windows file write (C)

#include <windows.h>
int main() {
    HANDLE hFile = CreateFile(
        L"test.txt", GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
    );
    char buf[] = "Hello, Windows!";
    DWORD bytes_written;
    WriteFile(hFile, buf, sizeof(buf)-1, &bytes_written, NULL); // Calls NtWriteFile
    CloseHandle(hFile);
    return 0;
}

Parameter Passing Conventions

Linux x86_64:

  • RDI: 1st arg
  • RSI: 2nd
  • RDX: 3rd
  • RCX: 4th
  • R8: 5th
  • R9: 6th
  • Stack: extra args

Windows x64:

  • RCX: 1st
  • RDX: 2nd
  • R8: 3rd
  • R9: 4th
  • Stack: extra args

Cross-Platform Handling

Use conditional compilation or abstraction layers.

Example: Cross-platform write

#ifdef __linux__
#include <unistd.h>
#define WRITE(fd, buf, len) write(fd, buf, len)
#elif _WIN32
#include <windows.h>
#define WRITE(fd, buf, len) WriteFile((HANDLE)(fd), (buf), (len), NULL, NULL)
#endif

int main() {
    WRITE(1, "Hello, Cross-Platform!", 20);
    return 0;
}

System Call Number Mapping:

// Linux
#define SYS_write 1
#define SYS_read 0

// Windows
#define SYS_CreateFile 0x75
#define SYS_ReadFile 0x72

ABI Compatibility Layer:

; Platform dispatch
cpuid
test eax, 0x40000000    ; Detect long mode
jz legacy_mode
; 64-bit
mov rax, syscall_number
syscall
legacy_mode:
; 32-bit
mov eax, syscall_number
int 0x80

Performance Analysis and Tuning

Performance analysis identifies bottlenecks; tuning applies optimizations.

Performance Analysis Toolchain

Tool TypeTool NameFunction
Macro Profilingperf (Linux)Sample CPU usage, cache misses, branch misses
Micro DebuggingGDBStep debugging, register/memory inspection
Kernel TracingftraceTrace kernel functions, interrupts
VisualizationVTune (Intel)GUI for hotspots, memory, vectorization

Example: Analyze with perf

# Sample CPU usage (10 sec)
perf record -g -o perf.data ./my_program

# Generate report
perf report -i perf.data

perf Usage Examples:

# Sample hot functions
perf record -F 99 -a -g ./program

# Cache hit rate
perf stat -e cache-misses,cache-references ./program

# Trace syscalls
perf trace -e syscalls:sys_enter_* ./program

VTune Analysis Focus:

  • Hotspot function identification
  • Instruction-level parallelism
  • Memory access patterns
  • Thread contention

Performance Bottleneck Identification: From Symptoms to Root Causes

Common bottlenecks:

  • CPU-bound: Too many loops, low ILP (e.g., branch misprediction).
  • Memory-bound: Cache misses, high latency (e.g., unaligned access).
  • I/O-bound: Frequent disk/network I/O (e.g., no buffering).

Diagnosis Methods:

  • perf stats: cycles, cache-misses, branch-misses
  • GDB disassembly: inspect hot function assembly
  • Memory tools: valgrind (leaks), cachegrind (cache behavior)

Performance Tuning Strategies: Targeted Optimization

  • CPU-bound: Loop unrolling, SIMD, branch prediction
  • Memory-bound: Data alignment, cache blocking, reduce accesses
  • I/O-bound: Buffering, async I/O, request batching

Example: Optimize cache misses (Cache Blocking)

; Original (high miss rate)
mov ecx, 1024
loop_start:
    mov eax, [arr + ecx*4]  ; Access arr[ecx] (4-byte stride)
    add eax, 1
    mov [arr + ecx*4], eax
    dec ecx
    jnz loop_start

; Optimized (cache-friendly blocks)
mov ecx, 64                 ; Block size (64 bytes = 16 ints)
loop_start:
    mov esi, ecx
    mov edi, 0
block_loop:
    mov eax, [arr + esi*4]  ; Access 16 consecutive ints (64B = 1 cache line)
    add eax, 1
    mov [arr + esi*4], eax
    inc esi
    cmp esi, 16
    jl block_loop
    dec ecx
    jnz loop_start

Summary and Outlook

Advanced x86 instructions and optimization techniques are core to low-level programming, enabling precise hardware control and maximum performance. From string operations to SIMD vectorization, from instruction scheduling to system calls, mastering these skills empowers developers in high-performance computing, embedded systems, and reverse engineering.

Looking ahead, as AI and heterogeneous computing grow, assembly optimization will evolve toward automatic vectorization (e.g., LLVM auto-SIMD) and hardware-specific tuning (e.g., ARM NEON, Intel AMX). Developers must continuously deepen their understanding of underlying principles, integrate toolchains, and apply knowledge to real-world scenarios to push the boundaries of performance optimization.

Share your love