Faster Readonly variables with Const::XS
So, what exactly is a Readonly variable in Perl? A readonly variable is one that, once assigned a value, cannot be changed. Any attempt to modify it will trigger a runtime error. This mechanism enforces immutability, ensuring that critical values remain untouched and are protected from accidental or unauthorised alterations.
While Perl doesn’t natively support readonly variables in its syntax, this behaviour can be mimicked using various CPAN modules. Two of the most commonly used ones are Readonly, which utilises tie, and Const::Fast, which sets an internal read-only flag on the variable (or SV in XS terms). Both approaches are effective, but I understood after inspecting the code of Const::Fast an XS implementation would likely be faster.
Enter Const::XS — it follows the same strategy as Const::Fast, marking variables with the SvREADONLY flag, but the key difference is that it’s implemented in XS, which does indeed speed up the process.
Here is a basic benchmark of Readonly, Const::Fast, Const::XS and a bonus module Const::PP which I wrote so I could benchmark fully Const::XS with something backwards compatible in perl.
use Const::Fast; use Const::XS; use Const::PP; use Readonly; my $r = timethese(5000000, { 'Readonly' => sub { Readonly::Scalar my $string => "Hello World" ; Readonly::Hash my %hash => ( a => $string , b => $string , c => $string ); Readonly::Array my @array => ( qw/1 2 3/ , $string , \ %hash ); die unless $string eq "Hello World" ; die unless $hash {a} eq "Hello World" ; die unless $array [3] eq "Hello World" ; }, 'Const::Fast' => sub { Const::Fast::const my $string => "Hello World" ; Const::Fast::const my %hash => ( a => $string , b => $string , c => $string ); Const::Fast::const my @array => ( qw/1 2 3/ , $string , \ %hash ); die unless $string eq "Hello World" ; die unless $hash {a} eq "Hello World" ; die unless $array [3] eq "Hello World" ; }, 'Const::PP' => sub { Const::XS::PP::const my $string => "Hello World" ; Const::XS::PP::const my %hash => ( a => $string , b => $string , c => $string ); Const::XS::PP::const my @array => ( qw/1 2 3/ , $string , \ %hash ); die unless $string eq "Hello World" ; die unless $hash {a} eq "Hello World" ; die unless $array [3] eq "Hello World" ; }, 'XS' => sub { Const::XS::const my $string => "Hello World" ; Const::XS::const my %hash => ( a => $string , b => $string , c => $string ); Const::XS::const my @array => ( qw/1 2 3/ , $string , \ %hash ); die unless $string eq "Hello World" ; die unless $hash {a} eq "Hello World" ; die unless $array [3] eq "Hello World" ; } }); cmpthese $r ; |
...
As demonstrated, Const::XS is over 4x more performant than its pure Perl equivalent and nearly 8x faster than Readonly. Therefore, if you're currently using either Readonly or Const::Fast, it might be worth considering a switch to Const::XS for improved performance.
In addition to the const keyword, Const::XS and Const::PP also offers several other functions:
make_readonly
- Makes a variable read-only.
- Helps enforce immutability at runtime.
make_readonly_ref
- Makes a reference read-only.
- Prevents modification of the referenced data.
unmake_readonly
- Removes the read-only restriction from a variable.
- Allows the variable to be modified after this operation.
is_readonly
- Checks if a variable is read-only.
- Returns true if the variable cannot be modified.
The link is incorrect: https://metacpan.org/pos/Const::XS
One of the issues with `use const` is that the implementation of using a subroutine makes those consts difficult to use in quoted situations. Does Const::XS have any such limitations? I can't see any from reading the POD.
Thanks updated the link and no such limitations with Const::XS they are just 'variables' so concatenating like "$STRING $HASH{ONE} $ARRAY[0]" is fine.
You should focus the benchmarks only on usage, not on creation. No user who cares about performance is defining constants in a tight loop.
And don't forget about neilb's excellent roundup of constant modules: https://neilb.org/reviews/constants.html
My benchmark measures both instantiation and access. If I were to benchmark only access, Const::Fast and Const::XS would show very similar performance. I include both in the benchmark because it's relevant in scripting scenarios, where the process isn't long-running, and variables/constants are instantiated each run. And yes, I know we're talking about fractions of a second but those add up.
Note that Readonly appears to make use of Readonly::XS if that is available.
Readonly::XS
does not declare a Perl version dependency that I can see, but it is installed on my Perl 5.8.9.