Документация
HTML CSS PHP PERL другое

Section 9.1.  Simple Inline::C

 
Previous
Table of Contents
Next

9.1. Simple Inline::C

The idea behind Inline::C is pretty straightforward: you write a C function as part of your Perl program, and the Inline library goes away and does the work required to make that function available from Perl. So, here's the simplest C function we could possibly wrap:

    use Inline C => q[

    void print_hi(  ) {
         puts("Hi, world!");
    }

    ];

    print_hi(  );

The first time this program is run, it takes a little time; Inline::C has to parse the C code, determine what wrappings are needed to bridge the gap between C and Perl (almost nothing in this case), write the wrapping, fire up a C compiler, create a shared library that can be loaded by Perl, and load it up. Only then is the print_hi subroutine available to Perl.

If we had to go through this rigmarole every time we executed the program, Inline wouldn't actually buy us very much. But if we run our program again, we should find that it's considerably faster. All Inline needs to do in this case is make sure that the C code we're compiling hasn't changed, and then load up the shared library it created last time.

9.1.1. Taking and Giving

Of course, real functions are a little more complicated than that; they take arguments, they return a value. With Inline, interfacing with these real functions isn't that much more complicated at all.

Let's take a relatively noncontrived example. You want to display some information to the user, such as:

    You have 2 lives left; score 1500, with 15 gold pieces.

However, you also want the user to be able to customize this information, if they prefer seeing:

    [Lives: 2 XP: 1500 GP:15]

So what you do is set up the output format as a pattern for printf, and have something like this:

    my $pattern = $user_pattern ||
                  "You have %i lives left; score %04i, with %2i gold pieces";
    printf $pattern, $lives, $xp, $gp;

But then you hear that allowing users to supply their own format string caused Korea to be knocked off the Net last week, which didn't seem too much of a big deal to you, but then someone starts telling you scary stories about the %p format, and you start wondering how you can sanitize the format string you were passed.

And then you remember that you're developing on a BSD Unix, which has the very handy fmtcheck function in the C library just for this purpose.[*] So you write a quick C function that selects the right format, like so:

[*] OK, we said it was relatively noncontrived. So we lied.

    char* score_format(char* pref_format, char* user_format) {
          return fmtcheck(user_format, pref_format);
    }

And once you put that in your Inline section, you can call it just like a normal Perl subroutine. This time, use a slightly different formulation of Inline; instead of passing in a string, put your C code into the DATA section at the end of the program. To tell Inline what to look at, add the _ _C marker after the DATA_ _ marker.

    use Inline C;

    my $pref_format = "You have %i lives left; score %04i, with %2i gold pieces";
    my ($lives, $score, $gp) = (3, 2500, 50);

    my $user_format = "[Lives: %i, XP: %i, GP: %i]";

    printf(score_format($pref_format, $user_format), $lives, $score, $gp);

    _ _END_ _
    _ _C_ _
    #include <stdio.h>

    char* score_format(char* pref_format, char* user_format) {
          return fmtcheck(user_format, pref_format);
    }

This determines that the user's format it safe and uses that instead of your format. Inline::C automatically knows how to deal with int, long, double, char*, and many other types as specified in the default Perl typemap (found in the ExtUtils subdirectory of your @INC path). Later on in the chapter, we'll see how to use more complex structures with Inline.

9.1.2. C is not Always a Win

But first a cautionary tale: there are any number of people who will complain that Perl is too slow, and if you're doing anything serious, you should rewrite it in C for speed. OK, then. This time, we're going to write a function to find the number of alphabetic characters in a string. We could use Perl's tr operator, but perhaps calling out to C will be faster. Here is the C function we're going to use:

    int count_alpha(char* foo) {
        int i = 0;
        do {
         if (isalpha(*foo)) i++;
        } while (*foo++);
        return i;
    }

    use Inline C;

    use Benchmark;
    $test = "a b cd e fg" x 10000;

    timethese(10000,
        {
          Perl => sub { $test =~ tr/[a-zA-Z]//; },
          C    => sub { count_alpha ($test) }
        }
    )

    _ _DATA_ _
    _ _C_ _

    int count_alpha(char* foo) {
        int i = 0;
        do {
         if (isalpha(*foo)) i++;
        } while (*foo++);
        return i;
    }

This produces output similar to:

    Benchmark: timing 10000 iterations of C, Perl...
             C: 24 wallclock secs (20.80 usr +  0.15 sys = 20.95 CPU) @ 477.33/s
    (n=10000)
          Perl: 10 wallclock secs ( 8.05 usr +  0.04 sys =  8.09 CPU) @ 1236.09/s
    (n=10000)

Unfortunately, we find that when we run this, the Perl built-in version is around twice as fast; this is a good reminder that it's not always beneficial to recode things in C for speed.[*] However, we've found that it is easy enough to wrap simple C functions in Perl, receiving and passing values between the two languages without worrying about the usual XS glue.

[*] With considerable hand-optimization and tuning of the Inline options, we can produce a C function that competes reasonably well with the Perl built-in. However, the time spent shunting around between Perl and C means that the built-in will win every time.

    Previous
    Table of Contents
    Next
    © 2000- NIV