#!/usr/bin/env perl use strict; use warnings; use v5.16; use Benchmark qw(cmpthese); use Random::Simple; my $args = join(" ", @ARGV); ############################################################################### ############################################################################### bench_me(); ############################################################################### ############################################################################### sub micros { require Time::HiRes; my @p = Time::HiRes::gettimeofday(); my $ret = ($p[0] * 1000000) + $p[1]; return $ret; } sub bench_me { my $count = 10000000; my $seeds = [ Random::Simple::_rand64(), Random::Simple::_rand64(), Random::Simple::_rand64(), Random::Simple::_rand64(), ]; print "Running PRNG benchmark...\n"; cmpthese($count, { 'PCG32' => sub { return pcg32_perl($seeds); }, 'PCG64' => sub { return pcg64_perl($seeds); }, 'Splitmix64' => sub { return splitmix64_perl($seeds); }, 'xosh256**' => sub { return xoshiro256starstar($seeds); }, 'biski64' => sub { return biski64($seeds); }, 'sfc64' => sub { return sfc64($seeds); }, }); } #my $seeds = [12, 34, 56]; #my $rand = sfc64($seeds); sub sfc64 { my $seeds = $_[0]; use integer; my $tmp = $seeds->[0] + $seeds->[1] + $seeds->[3]++; no integer; # Make sure tmp is a UV $tmp |= 0; # Bitwise shifts need to be 'no integer' so the shift is unsigned $seeds->[0] = $seeds->[1] ^ ($seeds->[1] >> 11); my $x = ($seeds->[2] << 3); use integer; $seeds->[1] = $seeds->[2] + $x; no integer; $seeds->[2] = (($seeds->[2] << 24) | ($seeds->[2] >> 40)); use integer; $seeds->[2] += $tmp; return $tmp; } #my $seeds = [12, 34, 56]; #my $rand = biski64($seeds); sub biski64 { my $seeds = $_[0]; use integer; my $output = $seeds->[1] + $seeds->[2]; my $old_lm = $seeds->[2]; $seeds->[2] = $seeds->[0] ^ $seeds->[1]; $seeds->[1] = rotl($seeds->[1], 16) + rotl($old_lm, 40); #$seeds->[1] |= 0; # Convert IV to UV (not needed here?) $seeds->[0] += 11068046444225730969; return $output; } #my $seeds = [12, 34]; #my $rand = pcg32_perl($seeds); sub pcg32_perl { # state/inc are passed in by reference my ($seeds) = @_; my $oldstate = $seeds->[0]; # Save original state # We use interger math because Perl converts to floats any scalar # larger than 2^64 - 1. PCG *requires* 64bit uint64_t math, with overflow, # to calculate correctly. We have to unconvert the overflowed signed integer (IV) # to an unsigned integer (UV) using bitwise or against zero. (weird hack) use integer; $seeds->[0] = ($oldstate * 6364136223846793005 + ($seeds->[1] | 1)) | 0; no integer; my $xorshifted = ((($oldstate >> 18) ^ $oldstate) >> 27) & 0xFFFFFFFF; # -$rot on a uint32_t is the same as (2^32 - $rot) my $rot = ($oldstate >> 59); my $invrot = 4294967296 - $rot; my $ret = (($xorshifted >> $rot) | ($xorshifted << ($invrot & 31))) & 0xFFFFFFFF; return $ret; } # PCG64 RXS-M-XS simple variant #my $seeds = [12, 34]; #my $rand = pcg64_perl($seeds); sub pcg64_perl { my $seeds = $_[0]; my $ret = (($seeds->[0] >> (($seeds->[0] >> 59) + 5)) ^ $seeds->[0]); use integer; $ret *= 12605985483714917081; $seeds->[0] = $seeds->[0] * 6364136223846793005 + $seeds->[1]; no integer; $ret = ($ret >> 43) ^ $ret; return $ret; } #my $seeds = [1216172134540287360, 607988272756665600, 16172922978634559625, 8476171486693032832]; #my $num = xoshiro256starstar($seeds); # xoshiro256** sub xoshiro256starstar { # Seeds are passed in by reference so we can update them my ($s) = @_; # We use integer math here because we need the large multiplication to # overflow. Without this Perl will try and convert this big number to a # float and we don't want that. use integer; my $result = rotl($s->[1] * 5, 7) * 9; no integer; $result |= 0; my $t = $s->[1] << 17; $s->[2] ^= $s->[0]; $s->[3] ^= $s->[1]; $s->[1] ^= $s->[2]; $s->[0] ^= $s->[3]; $s->[2] ^= $t; $s->[3] = rotl($s->[3], 45); return $result; } # Rotate the bits in a 64bit integer to the left and wrap back # around to the right side. sub rotl { my ($num, $shift) = @_; my $ret = ($num << $shift) | ($num >> (64 - $shift)); return $ret; } #my $seed = [10293820198]; #my $num = splitmix_64_perl_single($seed); sub splitmix64_perl { # Seed must be passed as a array reference so we can update it my $seed = $_[0]; use integer; # We bitwise or with zero to convert a signed int (IV) to an unsigned int (UV) # This is a weird hack that mauke taught me. It works so *shrug* my $z = ($seed->[0] += 11400714819323198485) | 0; $seed->[0] |= 0; no integer; $z = ($z ^ ($z >> 30)); use integer; $z = ($z * 13787848793156543929) | 0; no integer; $z = ($z ^ ($z >> 27)); use integer; $z = ($z * 10723151780598845931) | 0; no integer; $z = ($z ^ ($z >> 31)); return $z; } # String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' sub color { my ($str, $txt) = @_; # If we're NOT connected to a an interactive terminal don't do color if (-t STDOUT == 0) { return $txt // ""; } # No string sent in, so we just reset if (!length($str) || $str eq 'reset') { return "\e[0m"; } # Some predefined colors my %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0); $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; # Get foreground/background and any commands my ($fc,$cmd) = $str =~ /^(\d{1,3})?_?(\w+)?$/g; my ($bc) = $str =~ /on_(\d{1,3})$/g; if (defined($fc) && int($fc) > 255) { $fc = undef; } # above 255 is invalid # Some predefined commands my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); my $cmd_num = $cmd_map{$cmd // 0}; my $ret = ''; if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } if (defined($txt)) { $ret .= $txt . "\e[0m"; } return $ret; } sub file_get_contents { open(my $fh, "<", $_[0]) or return undef; binmode($fh, ":encoding(UTF-8)"); my $array_mode = ($_[1]) || (!defined($_[1]) && wantarray); if ($array_mode) { # Line mode my @lines = readline($fh); # Right trim all lines foreach my $line (@lines) { $line =~ s/[\r\n]+$//; } return @lines; } else { # String mode local $/ = undef; # Input rec separator (slurp) return my $ret = readline($fh); } } sub file_put_contents { my ($file, $data) = @_; open(my $fh, ">", $file) or return undef; binmode($fh, ":encoding(UTF-8)"); print $fh $data; close($fh); return length($data); } # Creates methods k() and kd() to print, and print & die respectively BEGIN { if (!defined(&trim)) { *trim = sub { my ($s) = (@_, $_); # Passed in var, or default to $_ if (length($s) == 0) { return ""; } $s =~ s/^\s*//; $s =~ s/\s*$//; return $s; } } if (eval { require Dump::Krumo }) { *k = sub { Dump::Krumo::kx(@_) }; *kd = sub { Dump::Krumo::kxd(@_) }; } else { require Data::Dumper; *k = sub { print Data::Dumper::Dumper(\@_) }; *kd = sub { print Data::Dumper::Dumper(\@_); die; }; } } __END__ 2025-12-24 on AMD Ryzen 7 5700G Running PRNG benchmark... Rate xosh256** biski64 sfc64 PCG32 Splitmix64 PCG64 xosh256** 1562500/s -- -27% -48% -53% -58% -66% biski64 2127660/s 36% -- -29% -36% -43% -54% sfc64 2985075/s 91% 40% -- -10% -20% -35% PCG32 3322259/s 113% 56% 11% -- -11% -28% Splitmix64 3731343/s 139% 75% 25% 12% -- -19% PCG64 4608295/s 195% 117% 54% 39% 24% -- // vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4