Begin main content

Optimising Perl with Inline::C

I was discussing with someone today about a time I used Inline::C to massively speed up an inner loop in a Perl program. Thing is, in that case the real speedup wasn't any super smart C programming on my behalf, it was just making use of a very optimised vendor library that you could only access from C.

So I got to thinking - in normal every day code, is there any real speed benefit to be had by writing your inner loops in C.

I found an old web page by Mitchell Charity discussing Inline and, interpreting his (slightly pathological) example a little, I got a surprising result:

use strict;
use warnings;

use Inline 'C';
use Benchmark qw(cmpthese);

cmpthese( 50,
         {
             perl_method => sub {
                 my $object = new Foo;

                 for(my $i=0;$i<1_000_000;$i++) {
                     $object->set_element($i,67);
                 }
             },

             all_in_one_c => sub {
                 my $object = new Foo;
                 
                 set_all_with_c($object);
             }
           
         });

package Foo;

sub new { my $self = " " x 1_000_000; return bless \$self, 'Foo' }

sub set_element {
  my($self,$n,$value) = @_;
  substr($$self,$n,1) = pack("C",$value);
}

__END__
__C__

#define USING(object) unsigned char *ptr = SvPVX(SvRV(object))
#define SET_ELEMENT(IDX,VAL) ptr[IDX] = VAL

void set_all_with_c (SV* object) {
    USING(object);
    int i;
    for(i=0;i<1000000;i++) { SET_ELEMENT(i, 67); }
}
               s/iter  perl_method all_in_one_c
perl_method      4.16           --        -100%
all_in_one_c 8.60e-03       48321%           --
ie. the C code was 48321% times as fast.

But it's not really comparing apples with apples - the Perl code is doing a method call on each iteration, the C code is operating on the value directly. In addition, the C code (by way of Inline::C's magic) is basically copying the string into a temporary variable, operating directly on that, and copying back - so the dereferencing is not happening on each loop. We can make those changes in Perl too, and see how that compares.

...

sub set_all_with_perl {
    my $tmp_str = ${ $_[0] };
    substr($tmp_str, $_, 1) = pack("C",67) for 0..1_000_000;
    ${ $_[0] } = $tmp_str;
}

...
                  s/iter     perl_method all_in_one_perl    all_in_one_c
perl_method         4.21              --            -62%           -100%
all_in_one_perl     1.61            161%              --            -99%
all_in_one_c    8.60e-03          48821%          18626%              --
So eliminating the method dispatch and dereference in the loop made our Perl code much faster, but the C code is still way faster. Obviously it's a contrived example, and 1 million iterations is one heck of an inner loop, but I am still surprised by how much difference it made.

01:38 AM, 11 Jan 2008 by Mark Aufflick Permalink | Short Link

Add comment