Storable: "freeze" versus "nfreeze"

I was doing a code review and discovered that one of our developers wrote code using Storable's freeze() function. This turned out to be a bug because we store objects in memcache with nfreeze() instead. Storable's docs have only this to say about nfreeze().

If you wish to send out the frozen scalar to another machine, use "nfreeze" instead to get a portable image.

Since people generally use freeze() instead, I decided to dig around and figure out what was going on. After all, if nfreeze() is portable, there must be a price to pay, right?

First, it turns out that nfreeze() generates a slightly smaller output:

$ perl -MStorable=freeze,nfreeze -MDateTime -E \
'$d=DateTime->now;say length(freeze($d));say length(nfreeze($d))'
165
156

I've found this to be consistent, even when scaling up to larger objects:

my @episodes = Dynamite->model('Core::Episode')->search->all;
say "Length with freeze() is " . length( freeze(\@episodes) );
say "Length with nfreeze() is " . length( nfreeze(\@episodes) );
__END__
Length with freeze() is 13056
Length with nfreeze() is 13047

Not much of a difference, but it's definitely there (everything I ran it on had 9 fewer characters). However, it must be slower, right?

Benchmarking a DateTime->now object:

Benchmark: timing 10000 iterations of freeze, nfreeze...
    freeze:  4 wallclock secs ( 4.35 usr +  0.00 sys =  4.35 CPU) @ 2298.85/s (n=10000)
   nfreeze:  5 wallclock secs ( 4.33 usr +  0.00 sys =  4.33 CPU) @ 2309.47/s (n=10000)

And benchmarking an arrayref of our Episode objects:

Benchmark: timing 10000 iterations of freeze, nfreeze...
    freeze: 10 wallclock secs (10.77 usr +  0.00 sys = 10.77 CPU) @ 928.51/s (n=10000)
   nfreeze: 10 wallclock secs (10.81 usr +  0.00 sys = 10.81 CPU) @ 925.07/s (n=10000)

So what's going on? nfreeze() is as fast as freeze(), except it's portable and a tiny bit more efficient for caching. Is there something I missed? Why use freeze() at all? Here's a small benchmark if you want to try this yourself. I was running this with Perl 5.10.1 on Ubuntu with Storable version 2.20.

#!/usr/bin/env perl

use Modern::Perl;
use Benchmark 'timethese';
use Storable qw(thaw freeze nfreeze);
use DateTime;

my $now = DateTime->now;
say "Length with freeze() is " . length( freeze($now) );
say "Length with nfreeze() is " . length( nfreeze($now) );
say thaw( freeze($now) );
say thaw( nfreeze($now) );

timethese(
  10_000,
  { freeze  => sub { thaw( freeze($now) ) },
    nfreeze => sub { thaw( nfreeze($now) ) },
  }
);

Update: Mike Cardwell informs me that nfreeze was introduced to preserve byte order. It couldn't be done in freeze() without breaking backwards-compatibility. By the way, click his link. If you like geeky/unixy stuff, his blog is a good read and Mike's a good guy.

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by http://www.circle23.com/. Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.