C is often called "portable assembly," but sometimes you need the real thing.
If you need to read the CPU Time Stamp Counter (RDTSC) for nanosecond-precision benchmarking, access specific processor flags, or utilize SIMD instructions not exposed by your C compiler, you can write raw assembly and call it directly from Perl.
The Recipe
This time, we'll implement a function to read the CPU cycle count (RDTSC) on x86-64 Linux/Unix/Windows systems.
use v5.40;
use Affix qw[:all];
use Affix::Build;
# 1. Write Assembly
# We use AT&T syntax (standard for GCC/Clang on Unix).
# This file (.s) will be passed to the assembler.
my $c = Affix::Build->new( name => 'asm_lib' );
$c->add( \<<~'END', lang => 's' );
.text
.global get_ticks
get_ticks:
# rdtsc puts 32 bits in EDX (high) and 32 bits in EAX (low)
rdtsc
# We need to pack them into a single 64-bit register (RAX)
# Shift RDX left by 32 bits
shl $32, %rdx
# Combine RDX and RAX
or %rdx, %rax
# Return value is in RAX
ret
END
# 2. Link
# Note: On macOS, symbols need a leading underscore ('_get_ticks').
my $lib = $c->link;
# 3. Bind
# The function takes no args and returns a 64-bit unsigned integer.
affix $lib, 'get_ticks', [] => UInt64;
# 4. Benchmark
say 'Measuring CPU cycles for a Perl operation...';
my $start = get_ticks();
# Do some work
my $x = 0;
$x += $_ for 1 .. 1000;
# Check the clock again
my $end = get_ticks();
say 'Start: ' . $start;
say 'End: ' . $end;
say 'Diff: ' . ( $end - $start ) . ' cycles';
On my system, the output looks like this:
Measuring CPU cycles for a Perl operation...
Start: 4098233110110909
End: 4098233110188467
Diff: 77558 cycles
How It Works
-
1. The Assembler When you pass
lang => 's',Affix::Buildinvokes the system assembler (usuallyasorgcc). It compiles the raw instructions into machine code. -
2. The ABI You must respect the Calling Convention of your platform (System V AMD64 in this example).
- Arguments:
RDI, RSI, RDX... - Return Values:
RAX - Safety: You must preserve "callee-saved" registers (
RBX, RBP, R12-R15) if you use them. Sincerdtsconly clobbersRAXandRDX(which are caller-saved/volatile), we don't need to push/pop anything.
- Arguments:
Kitchen Reminders
-
Architecture Specific Assembly is not portable. This code will crash on ARM64 (Apple Silicon, Raspberry Pi) or 32-bit systems. You must detect
$Config{archname}and provide different assembly implementations for different CPUs. -
Syntax Flavors
- Unix/Linux: Uses AT&T syntax (
%reg,src, dest). - Windows: Uses Intel syntax (
reg,dest, src) via NASM. If you are targeting Windows and Intel, you should uselang => 'asm'(NASM) instead of's'.
- Unix/Linux: Uses AT&T syntax (