Random Number Code Size
In an ideal world, a good random number generator ought not to require a lot of code code to implement or execute.
A Simple Example
Let's consider this example of code that prints out eight random numbers.
#include "pcg_random.hpp" #include <cstdio> int main() { pcg32 rng; for (int i = 0; i < 8; ++i) printf("%08x\n", rng()); }
And let's see how much code we'd produce with PCG and if we swapped pcg32 for a few other generators. (The code was compiled with GCC 4.8 using -O2 -fno-asynchronous-unwind-tables -fno-exceptions -fno-align-loops.)
Generator | Init Instrs | Init Bytes | Loop Instrs | Loop Bytes |
---|---|---|---|---|
PCG 64/32 (XSH-RR) | 4 | 35 | 15 | 52 |
Minstd (LCG) | 3 | 16 | 17 | 61 |
XorShift* 64/32 | 3 | 25 | 17 | 60 |
Ran | 6 | 55 | 33 | 115 |
MT 19937 | 51 | 197 | 49 | 174 |
We can see here that the PCG family is comparable in code size to other generators known for their compact code, and actually has fewer instructions inside the loop. The Mersenne Twister and Ran (from Numerical Recipes) are much worse for code size—their implementations are much more complex, and it shows in the generated code (the same also applies arc4random).
The Actual Code
FWIW, here's the code that was generated by GCC for the above code:
_main: ## Function prologue: pushq %r13 pushq %r12 pushq %rbp pushq %rbx subq $8, %rsp ## Initialize RNG and loop constants movabsq $5573589319906701683, %rcx movl $8, %ebp movabsq $6364136223846793005, %r13 movabsq $1442695040888963407, %r12 .align 4 ## Loop L2: movq %rcx, %rbx movq %rcx, %rsi xorl %eax, %eax imulq %r13, %rbx shrq $18, %rsi leaq LC0(%rip), %rdi ## string "%08x\n" xorq %rcx, %rsi shrq $59, %rcx shrq $27, %rsi rorl %cl, %esi call _printf addq %r12, %rbx movq %rbx, %rcx subl $1, %ebp jne L2 ## Function Epilogue addq $8, %rsp xorl %eax, %eax popq %rbx popq %rbp popq %r12 popq %r13 ret
Interestingly, Clang produces quite different code:
_main: ## Function prologue: pushq %rbp movq %rsp, %rbp pushq %rbx pushq %rax ## Load string constant: leaq L_.str(%rip), %rbx ## string "%08x\n" ## Directly print out eight numbers movl $676697322, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $420258633, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $-876335118, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $-699367085, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $-1029176017, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $257272927, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $-687915470, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf movl $1330014364, %esi xorl %eax, %eax movq %rbx, %rdi callq _printf xorl %eax, %eax ## Function Epilogue addq $8, %rsp popq %rbx popq %rbp retq
Because the generator is initialized with a fixed seed, and because the steps PCG uses to generate random numbers are simple, Clang's optimizer runs the generator at compile time and produces the output constants directly.