P vs P speed (was RE: Newbie question: files..)

Tim Peters tim_one at email.msn.com
Mon Oct 11 18:03:53 EDT 1999


[Tim]
>> try-timing-a-program-with-bigints<wink>-ly y'rs  - tim

[Hrvoje Niksic]
> Huh?

Python's long ints; Perl's Math::BigInt.  I first saw Python when working
for a company trying to design a 64-bit supercomputer, using software on
32-bit workstations.  I had been using Perl, very painfully and slowly, and
the very first Python alpha already ran circles around Perl (I'm talking
factors of 10 - 100 here) for these tasks.  I kept Perl for text-crunching,
but dropped it for everything else.

[Tim again]
> It [Python] isn't slower than Perl in my experience (the contrary) --
> but then I use Perl for the little bit of serious text-crunching and
> regexp-slinging that comes up in my work.  Perl put extraordinary effort
> into implementing those efficiently; outside of them, I find that Python
> is typically faster.

[Hrvoje again]
> How do you measure that?

Speed excites me, so I just estimate my heartrate <wink -- but what do you
*think*?!>.

> So far I haven't seen an example of Python code that would be faster
> than its Perl equivalent.

I'll attach the first one I tried today -- and the last one I intend to try
ever again <0.1 wink>.  No bigints, just objects, recursion and simple
integer and floating-point math.  I did not work at optimizing either
version, but put considerable effort into fiddling the code so that:

a) Python and Perl print the same stuff.

b) Perl doesn't yield any warnings when run with -w (although, at the end,
it prints a bunch of "deep recursion" warnings when run as described next).

To be as fair as possible, I used the stock Python 1.5.2 without -O, and
downloaded the latest Win32 Perl (ActivePerl build 520).

OTOH, to be as unfair as possible (to compensate for the usual unfairness we
see in the other direction <0.5 wink>), I wrote this first in idiomatic
Python and then translated it straightforwardly into Perl.

Run it with command-line argument 5000.  It should print:

0 appeared 284 times
1 appeared 315 times
2 appeared 333 times
3 appeared 278 times
4 appeared 326 times
5 appeared 316 times
6 appeared 298 times
7 appeared 313 times
8 appeared 307 times
9 appeared 321 times
10 appeared 330 times
11 appeared 334 times
12 appeared 298 times
13 appeared 338 times
14 appeared 284 times
15 appeared 325 times
saw 5000 in all

On my home platform (P5-166, 32Mb RAM, Win95), it takes about 20 seconds in
Perl and about 14 in Python.  Which, applying the usual benchmarking
chicanery, means "Python is 43% faster than Perl".

More, this is *typical* in my experience.  All the convolution in Perl's
internals costs time to execute, and Python's much cleaner implementation
translates to faster runtime.  Python gets a bad rap here because of one
thing:  Guido doesn't put "extraordinary effort" into optimizing *anything*
(it's a personality flaw <wink>), while Larry et alia did put extraordinary
effort into optimizing some common things (mostly related to
text-crunching).  Outside of its areas of special competence, Perl is not
fast.

followups-ignored-ly y'rs  - tim

Python version:

class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class BinaryTree:
    def __init__(self):
        self.root = None

    def insert(self, val):
        root = self.root
        lastdir = None
        while root:
            lastroot = root
            if val <= root.val:
                lastdir = 'left'
                root = root.left
            else:
                lastdir = 'right'
                root = root.right
        new = Node(val)
        if lastdir is None:
            self.root = new
        elif lastdir == 'left':
            lastroot.left = new
        else:
            lastroot.right = new

    def inorder(self):
        result = []
        self.__helper(self.root, result)
        return result

    def __helper(self, root, result):
        if root is not None:
            self.__helper(root.left, result)
            result.append(root.val)
            self.__helper(root.right, result)

class Random:
    def __init__(self, seed=(1,2,3)):
        self.x, self.y, self.z = seed

    def get(self):
        x, y, z = self.x, self.y, self.z
        x = (171 * x) % 30269
        y = (172 * y) % 30307
        z = (170 * z) % 30323
        self.x, self.y, self.z = x, y, z
        w = (x/30269.0 + y/30307.0 + z/30323.0) % 1.0
        return int(w * 16)

def _test(n):
    generator = Random().get
    tree = BinaryTree()
    for i in xrange(n):
        tree.insert(generator())
    all = tree.inorder()
    last = -1
    totalcount = count = 0
    for x in all:
        if x == last:
            count = count + 1
        else:
            if count:
                print last, "appeared", count, "times"
                totalcount = totalcount + count
            last = x
            count = 1
    if count:
        print last, "appeared", count, "times"
        totalcount = totalcount + count
    print "saw", totalcount, "in all"

if __name__ == "__main__":
    import sys
    _test(int(sys.argv[1]))

Perl version:

package Node;
sub new {
    my ($class, $val) = @_;
    return bless {"val" => $val, "left" => undef, "right" => undef}, $class;
}

package BinaryTree;
sub new {
    return bless {"root" => undef}, shift;
}

sub insert {
    my ($self, $val) = @_;
    my $root = $self->{root};
    my $lastdir = undef;
    while (defined $root) {
        $lastroot = $root;
        if ($val <= $root->{val}) {
            $lastdir = 'left';
            $root = $root->{left};
        }
        else {
            $lastdir = 'right';
            $root = $root->{right};
        }
    }
    my $newguy = new Node($val);
    if (!defined($lastdir)) {
        $self->{root} = $newguy;
    }
    elsif ($lastdir eq 'left') {
        $lastroot->{left} = $newguy;
    }
    else {
        $lastroot->{right} = $newguy;
    }
}

sub helper {
    my ($self, $root, $result) = @_;
    if (defined $root) {
        $self->helper($root->{left}, $result);
        push(@$result, $root->{val});
        $self->helper($root->{right}, $result);
    }
}

sub inorder {
    my $self = shift;
    my $result = [];
    $self->helper($self->{root}, $result);
    return $result;
}

package Random;
sub new {
    my $class = shift;
    return bless {"x" => 1, "y" => 2, "z" => 3}, $class;
}

sub get {
    my $self = shift;
    my ($x, $y, $z) = ($self->{x}, $self->{y}, $self->{z});
    $x = (171 * $x) % 30269;
    $y = (172 * $y) % 30307;
    $z = (170 * $z) % 30323;
    ($self->{x}, $self->{y}, $self->{z}) = ($x, $y, $z);
    # Perl doesn't do something sensible with float % float, so fake it by
    # hand.
    my $w = $x/30269.0 + $y/30307.0 + $z/30323.0;
    $w -= int $w;
    return int($w * 16);
}

package main;
sub _test {
    my $n = shift;
    my $generator = new Random;
    my $tree = new BinaryTree;
    foreach (1 .. $n) {
        $tree->insert($generator->get());
    }
    my $all = $tree->inorder();
    my $last = -1;
    my ($totalcount, $count) = (0, 0);
    foreach (@$all) {
        if ($_ == $last) {
            ++$count;
        }
        else {
            if ($count) {
                print "$last appeared $count times\n";
                $totalcount += $count;
            }
            $last = $_;
            $count = 1;
        }
    }
    if ($count) {
        print "$last appeared $count times\n";
        $totalcount += $count;
    }
    print "saw $totalcount in all\n";
}

_test($ARGV[0]);






More information about the Python-list mailing list