Assembly Profiling — Measuring and Optimizing Performance
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