Skip to content

Assembly Profiling — Measuring and Optimizing Performance

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Assembly Profiling. We cover key concepts, practical examples, and best practices to help you master this topic.

Assembly profiling uses perf, valgrind, and RDTSC to measure instruction counts, cache misses, branch mispredictions, and CPU cycles for performance optimization.

What You'll Learn

  • Using the perf profiler
  • RDTSC for cycle counting
  • Cache miss analysis
  • Branch prediction statistics
  • Microbenchmarking techniques

Why It Matters

You write assembly for performance. Profiling verifies your optimizations work. DodaZIP uses perf to identify bottlenecks in its assembly compression routines.

Real-World Use

Performance-critical code validation, compiler output comparison, hot path optimization, and understanding CPU behavior at the microarchitectural level.

flowchart LR
    A["Profiling"] --> B["perf"]
    A --> C["RDTSC"]
    A --> D["Valgrind"]
    B --> E["Hardware Counters"]
    C --> F["Cycle Count"]
    D --> G["Cache Analysis"]
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#dbeafe,stroke:#2563eb,color:#1e40af

Using perf stat

perf stat ./program
#   1,234,567      cycles
#     456,789      instructions
#      12,345      branches
#         123      branch-misses
#      50,000      cache-references
#       5,000      cache-misses

perf record and report

perf record ./program
perf report

perf record -g ./program
perf record -e cycles,instructions,cache-misses ./program

RDTSC — Read Time-Stamp Counter

section .data
    start_time dq 0

section .text
    rdtsc
    shl rdx, 32
    or rax, rdx
    mov [start_time], rax

    ; Code to measure
    mov rcx, 1000
.loop: loop .loop

    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, [start_time]
    ; rax = cycle count

Microbenchmark Template

section .text
    global benchmark

benchmark:
    ; Warm up
    mov rcx, 1000
.warmup: loop .warmup

    ; Serialize
    cpuid
    rdtsc
    shl rdx, 32
    or rax, rdx
    mov r8, rax

    ; Benchmark loop
    mov rcx, 1000000
.loop:
    ; Operation to measure
    mov r9, rcx
    imul r9, 42
    loop .loop

    ; End
    cpuid
    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8
    xor rdx, rdx
    mov rcx, 1000000
    div rcx
    ret                     ; rax = cycles per iteration

Cache Miss Analysis

perf stat -e cache-references,cache-misses,LLC-loads,LLC-load-misses ./program
perf stat -e cycles,instructions,cache-misses ./program
perf stat -e mem-loads,mem-stores ./program

Branch Prediction Analysis

perf stat -e branches,branch-misses ./program

Ideal: >95% prediction rate. Poor: <90%.

perf annotate

perf annotate --asm

Shows cycle counts per instruction for bottleneck identification.

Valgrind Callgrind

valgrind --tool=callgrind ./program
callgrind_annotate callgrind.out.*
kcachegrind callgrind.out.*

Common Mistakes

1. Not warming up the CPU

Modern CPUs throttle up/down. Run code several times before measuring.

2. Measuring with RDTSC across context switches

RDTSC counts wall-clock cycles. Run on isolated core if possible.

3. Ignoring CPU frequency scaling

Use cpufreq-set -g performance to lock frequency.

4. Measuring one iteration

Always measure many iterations and average for statistical significance.

5. Forgetting serializing instructions

Use cpuid or lfence before RDTSC for precise measurements.

Practice Questions

1. What does the RDTSC instruction do?

Reads the CPU's Time-Stamp Counter into EDX:EAX (64-bit value).

2. Why use perf instead of RDTSC for profiling?

perf measures specific hardware events (cache misses, branches) and requires no code changes.

3. What is a branch misprediction penalty?

15-20 cycles wasted when the CPU flushes the pipeline after an incorrect branch prediction.

4. How do cache misses affect performance?

L1 miss ~10 cycles, L2 ~30, L3 ~60, RAM ~200+. Memory is ~100x slower than L1 cache.

Challenge: Write a benchmark that compares rep movsb vs a loop for copying memory.

Solution
section .data
    src times 1048576 db 0xAA
    dest times 1048576 db 0

section .text
    global _start

_start:
    cpuid
    rdtsc
    mov r8, rax

    mov rsi, src
    mov rdi, dest
    mov rcx, 1048576
    cld
    rep movsb

    cpuid
    rdtsc
    sub rax, r8

    mov rax, 60
    xor rdi, rdi
    syscall

FAQ

{{< faq question="What is a good instructions-per-cycle (IPC) ratio?" >}} Modern CPUs achieve 2-4 IPC for optimized code. Theoretical maximum is 4-6 depending on microarchitecture. {{< /faq >}}

{{< faq question="How does perf work?" >}} perf uses hardware performance counters built into modern CPUs to count events without slowing down the program. {{< /faq >}}

{{< faq question="What is the overhead of profiling?" >}} perf stat adds ~2-5% overhead. perf record adds more because it samples. RDTSC itself costs ~20 cycles. {{< /faq >}}

{{< faq question="Can I profile a single function?" >}} Yes. Use perf's event-based sampling with address filters or instrument the function manually. {{< /faq >}}

{{< faq question="What is the difference between user and kernel cycles?" >}} User cycles are spent in application code. Kernel cycles are spent in OS system calls on behalf of the Process. {{< /faq >}}

Mini Project

Profile a simple sum-of-squares program and identify the bottleneck.

nasm -f elf64 -g profiling.asm -o profiling.o
ld profiling.o -o profiling
perf stat ./profiling
perf record ./profiling
perf report

What's Next

Now that you understand profiling, proceed to ARM architecture introduction.

Topic Description Link
ARM intro ARM64 architecture {{< ref "21-arm-intro" >}}
Debugging Assembly debugging {{< ref "19-debugging" >}}
Tools Development tools {{< ref "30-tools" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro