Perl uses reference counted scalars (SV) to represent variables like strings, numbers, and references. Strings are stored as C-style null-terminated strings which do not shrink when characters are removed. Arrays are lists of SVs, and hashes are arrays of arrays. While removing elements may change counts, it does not necessarily free memory. Perl favors speed of allocation over memory efficiency, so the programmer must manage memory usage.
3. Why you care
●
Long lived, highvolume, or highspeed app's
need to avoid swapping, heap fragmentation, or
blowing ulimits.
●
Examples are ETL, Bioinformatics, or longlived
servers.
●
Nonexamples are JAPH or WWW::Mechanize
jobs that snag one page.
4. PerlGuts & Perl Data Intro
●
The standard introduction to Perl's guts is
“perlguts” (use perldoc or view it as HTML on
CPAN).
●
Perldoc also includes “perldebuguts” which
shows how to use the debugger to look around
for yourself.
●
Simeon Cozens' Intro to Perl5 Internals is
avaialable online at:
http://www.faqs.org/docs/perl5int/
5. Space vs. Speed
●
Speed is fun, but is comes at a price.
●
The usual tradeoff in computing is space: faster
is bigger.
●
Perl is simple: it always favors speed.
●
As the programmer you have to manage the
space.
7. Perly Memory
●
Perl's model is similar to C:
●
subroutine calls have a “scratchpad” (“PAD”) that
works like C's stack and a heap for permenant
allocations.
●
lexical variables live in the pad but are allocated on
the heap.
●
Perl's heap is expanded to meet new allocation
requests, it does not shrink.
8. Perl5 Peep Show
●
Devel::Peek displays internals.
●
Quite handy when used with the Perl debugger: you
can set things and eyeball the consequences easily.
●
Devel::Size shows the size of a structure alone
or with its contents.
●
Both can be added to #! code or modules.
9. A View from the Perly Side
●
Most of us use scalars, arrays, and hashes.
●
Scalars do all the work of simple data types in C
and are managed in the Perl code via “SV”
structures (leaving out Magic).
●
Arrays are lists of scalars.
●
Hashes are arrays of arrays.
10. “length” vs “structure”
●
Most people think of “size” as data:
● length $string;
● scalar @array;
●
This leaves out the structure of the variable
itself.
●
Devel::Size uses “size” for the structure,
“total_size” for the size + contained data.
11. Scalars: “SV”
●
Because SV's can handle text, numeric, and
pointer values their perlguts are fairly detailed.
●
Strings are interesting for memory issues:
●
They can grow depending on what they store.
●
peek and size give a way to look at the space they
take up.
12. Text
●
Strings are allocated as Cstyle strings, with a
list of characters followed by a NUL.
●
They can grow by being extended or re
allocated.
●
Removing a characters from a string moves a
'start of string' pointer in the SV or reduces the
length counter (or both).
●
Note that the structure does not shrink.
13. Aside: NULL vs NUL
●
NUL is an ACSII character with all zero bits used
to terminate strings in C.
●
NULL is a pointer value that will never compare
true to a valid pointer.
●
It is used to check whether memory is allocated.
●
It may not be zero.
●
It is not used to terminate strings.
14. Examples
●
Much of what follows are examples of using
Devel::* modules to examine structures.
●
I've done them in the Perl debugger, showing
the debug commands.
●
Hopefully this will make it easier to replicate
these tests on your code.
22. Takeaway
●
Strings don't shrink.
●
Use lexical variables to return memory.
●
Clean variables up before assigning them:
my $buffer = <$fh>;
$buffer =~ s{ $rx }{}gx;
$data{ $key } = $buffer;
23. Arrays are similar to text
●
They are reduced by adding a skip count to the
array and reducing the length.
●
This can lead to all sorts of confusion if you try
to 'free up space' by shifting data off of an array.
29. What about about queues?
●
A classic arrangement is to shift work off the
front, pushing new tasks onto the array.
●
Which works fine, but doesn't do anything to
recover any space.
●
New items are pushed onto the end, regardless
of unused initial space.
32. Big Arrays: Big Leaps
●
This has a big effect arrays that result from
“slurp” reads.
●
If you cannot control the initial size, be careful
about what you do afterward: the results can
grow more than you expect!
DB<30> @d = ( 1 .. 1_000_000 )
DB<31> p size @d
4000100
DB<34> push @d, 1
DB<35> p size @d
8388692
33. Order Matters
●
There are four ways to keep a constant queue –
say for a moving average:
●
shift + push
●
push + shift
●
pop + unshift
●
unshift + pop
●
Q:Which of these results in the least size?
35. Random trial
●
Randomly push or pop from a list, tracking the
maximum array length (scalar @array),
maximum and final size.
●
This is similar to what happens in a normal
work queue: jobs arrive asynchronously and are
serviced in FIFO order.
36. Results: Random Push or Pop
Length Final
Max Final Size
20 6 212 After 1_000 iterations of random push or pop:
25 10 212
25 9 212 ( 0.50 < rand ) ? pop @a : push @a, 1;
26 14 212
26 21 212
27 23 212 Final length does not determine final size: the maximum
28 17 212 length does: the shortest final length (4) has a larger size
29 21 340
31 18 340
than all of the arrays with the smallest final size.
33 31 340
35 27 340
35 4 340
35 7 340
40 13 340
43 34 340
43 43 340
45 39 340
51 40 340
78 48 596
83 61 596
37. Takeaway
●
Like strings:
●
Arrays don't shrink.
●
Lexicals return their space.
●
You can recover space from values stored in the
array, but the SA structure itself only grows.
●
For cyclic use, clean up a buffer first and then
assign it to a new variable (just like strings).
38. Hashes
●
Hashes are composed of a bucket list and
collision chains of arrays (array of arrays.
●
You get 8 collision chains (“buckets”) with
the hash structure, even if it is empty.
●
Within a chain, searches are linear.
42. Takeaway
●
Hashes are bigger than arrays.
●
Deleting keys from the hash does not reduce the
structure.
●
Even assigning @a = () or %a = () does not
reduce the structure.
●
Collision chains are arrays: they don't shrink!
43. What can you do about it?
●
Buffer inputs, clean them up then assign by
value: recycling the variable saves time re
allocating it, copying the cleaned up copy saves
space:
my $buffer = <$read>;
# chomp, clean out witespace, then.
push @linz, $buffer;
●
Same with hashes and arrays: recycle static variables
for input and assign them to lexicals for use.
●
Generate structures before you fork: at least the
readonly portion will be shared.
44. Presize Buffers
●
Say you regularly read a millionline file and
don't want it to use twomillion array entries:
my @buffer = ();
$#buffer = 1_001_000;
$#buffer = -1;
●
This will give you 1001 offsets of wiggle room
before you end up doubling the array size.
●
Avoiding reallocation as the array grows also
helps speed.
45. Use arrays instead of hashes
●
Store a tree as $tree{ parent } => @children
instead of $tree{ $parent }{ $child } = 1 or
$tree{ $parent1 }{ $parent2 }{ $child } = ().
●
Avoid $tree{ $child }{ $parent} with only one
entry per child.
●
Store smaller lists as arrays and just search them
with List::Util::first or smartmatch (~~):
my $found = first{ $_ eq $value } @array;
$value ~~ @array or die “Unknown: '$value'”;
46. Manage Scope
●
Lexicals flag their space as reusable when the
go out of scope:
●
Put buffer variables inside the subroutines:
sub read_log
{
my $buffer = ' ' x 120;
...
●
Or inside the loops that use them:
while( my $line = <$input> )
●
Use “undef” to control the scope of data.
47. undef: Speed vs. Space
●
“undef” releases memory.
●
This empties a scalar, array, or hash variable: the
contents are not shortened, they are discarded.
●
This requires reallocating SV space if the variable is
used again: gives you control space/speed tradeoff.
●
Use with iflogic to manage presized buffers:
if( $buff_presize < @buffer )
{
undef @buffer;
$#buffer = $buff_presize;
$#buffer = 1;
}
48. Summary
●
Perly structures trade space for speed.
●
Structures can grow in nonobvious ways.
●
Strings, arrays, and hashes are not reduced by
substr, splice, delete.
●
undef discards contents, it does not resize them.
●
Devel::Peek and Devel::Size give you a way to
benchmark and validate your results.
●
And, yes, size() really matters.