[Tutor] Can my code be optimized any further (speed-wise)?

Tim Peters tim.peters at gmail.com
Sun Oct 8 19:55:18 CEST 2006


[Geoframer]
> The last few days i've been learning python and have been doing this by
> trying to solve problems for a programming competition.

I assume you're talking about:

    http://www.spoj.pl/problems/FCTRL/

If so, you'll note that I'm tied for the fastest Python solution there ;-)

The /guts/ of my method is just:

        # count <- number of trailing zeroes in n!
        count = 0
        base = 5
        while n >= base:
            count += n // base
            base *= 5

There are several reasons for why that's faster than what you tried,
which have been explained by others (doesn't create a list of divisors
each time; gets out of the loop as soon as there's no point to
continuing).  It's /possible/ that it would be faster if you did keep
a canned list of powers of 5.

But for some of the SPOJ problems (like this one ;-)), that's not the
real thing to worry about!  Some have very large input sets, and the
time spent doing I/O, and converting between strings and integers,
swamps the time needed to do calculations.

So, for example, in this problem I didn't read one line at a time.
Instead I did:

def main():
    import sys
    ncases = int(sys.stdin.readline())
    all = map(int, sys.stdin.read().split())
    assert ncases == len(all)
    for n in z(all):
        print n

The important part there is sucking in all of the test cases "in one
gulp", and converting to them /all/ to integers with a /single/ fast
map() call.  The z() function is basically what I wrote above,
containing a loop to march over all the inputs.  It's also important
for peak speed to use functions so that faster local-variable lookups
can be used instead of slower global-variable lookups.

But those aren't the most important parts either ;-)  When it comes to
speed, it's rarely what you think it is.

Using the input() function was almost certainly your primary problem,
because input() endures the relatively enormous expense of /compiling/
the input string as a fully general Python expression, generating a
code object for that expression, interpreting that dynamically
generated Python code, and then tearing down the code object again.
For every input.  It's not the slowest possible way to convert a
string to an integer, but you'd have to work hard to find a slower way
;-)

Just for fun, you might want to try /just/ changing:

    b = input()

in your program to

    b = int(raw_input())

I don't know whether you'll meet the time limit then, but it will run
much faster.

Finally, if you look at the 20 accepted Python solutions:

    http://www.spoj.pl/ranks/FCTRL/lang=PYTH

you'll see that the top 5 all used enormously more memory than the
other 15.  That's an almost-certain sign that they used psyco (which
the SPOJ folks have installed), like so:

# the rest of the code goes here

import psyco
psyco.full()

if __name__ == "__main__":
    main()

Note that psyco doesn't always help -- sometimes it makes a program slower.

As the 15 other accepted Python solutions show, it's not necessary to
use psyco to meet the time limit for this problem.  If I could, I'd
retract my run using psyco and settle for a non-psyco run -- I
couldn't care less about being "the fastest" on these, and just
/tried/ psyco here out of professional curiousity.  Alas, SPOJ only
remembers the fastest correct run you submit.


More information about the Tutor mailing list