20

I've been working on one of the coding challenges on InterviewStreet.com and I've run into a bit of an efficiency problem. Can anyone suggest where I might change the code to make it faster and more efficient?

Here's the code

Here's the problem statement if you're interested

James Brewer
  • 255
  • 1
  • 3
  • 6
  • 4
    This might be better posted to http://codereview.stackexchange.com/ – Santa Aug 23 '11 at 18:07
  • Or you could change this into a more general question about optimising python code, with short examples of the sort of things you want to optimise. Afaik there is no such question? – James Aug 23 '11 at 18:12
  • I might be interested in reopening this if it wasn't a link only question. – Jonathan Mee Aug 24 '16 at 15:38

4 Answers4

108

If your question is about optimising python code generally (which I think it should be ;) then there are all sorts of intesting things you can do, but first:

You probably shouldn't be obsessively optimising python code! If you're using the fastest algorithm for the problem you're trying to solve and python doesn't do it fast enough you should probably be using a different language.

That said, there are several approaches you can take (because sometimes, you really do want to make python code faster):

Profile (do this first!)

There are lots of ways of profiling python code, but there are two that I'll mention: cProfile (or profile) module, and PyCallGraph.

cProfile

This is what you should actually use, though interpreting the results can be a bit daunting. It works by recording when each function is entered or exited, and what the calling function was (and tracking exceptions).

You can run a function in cProfile like this:

import cProfile
cProfile.run('myFunction()', 'myFunction.profile')

Then to view the results:

import pstats
stats = pstats.Stats('myFunction.profile')
stats.strip_dirs().sort_stats('time').print_stats()

This will show you in which functions most of the time is spent.

PyCallGraph

PyCallGraph provides a prettiest and maybe the easiest way of profiling python programs -- and it's a good introduction to understanding where the time in your program is spent, however it adds significant execution overhead

To run pycallgraph:

pycallgraph graphviz ./myprogram.py

Simple! You get a png graph image as output (perhaps after a while...)

Use Libraries

If you're trying to do something in python that a module already exists for (maybe even in the standard library), then use that module instead!

Most of the standard library modules are written in C, and they will execute hundreds of times faster than equivilent python implementations of, say, bisection search.

Make the Interpreter do as Much of Your Work as You Can

The interpreter will do some things for you, like looping. Really? Yes! You can use the map, reduce, and filter keywords to significantly speed up tight loops:

consider:

for x in xrange(0, 100):
    doSomethingWithX(x)

vs:

map(doSomethingWithX, xrange(0,100))

Well obviously this could be faster because the interpreter only has to deal with a single statement, rather than two, but that's a bit vague... in fact, this is faster for two reasons:

  • all flow control (have we finished looping yet...) is done in the interpreter
  • the doSomethingWithX function name is only resolved once

In the for loop, each time around the loop python has to check exactly where the doSomethingWithX function is! even with cacheing this is a bit of an overhead.

Remember that Python is an Interpreted Language

(Note that this section really is about tiny tiny optimisations that you shouldn't let affect your normal, readable coding style!) If you come from a background of a programming in a compiled language, like c or Fortran, then some things about the performance of different python statements might be surprising:

try:ing is cheap, ifing is expensive

If you have code like this:

if somethingcrazy_happened:
     uhOhBetterDoSomething()
else:
     doWhatWeNormallyDo()

And doWhatWeNormallyDo() would throw an exception if something crazy had happened, then it would be faster to arrange your code like this:

try:
    doWhatWeNormallyDo()
except SomethingCrazy:
    uhOhBetterDoSomething()

Why? well the interpreter can dive straight in and start doing what you normally do; in the first case the interpreter has to do a symbol look up each time the if statement is executed, because the name could refer to something different since the last time the statement was executed! (And a name lookup, especially if somethingcrazy_happened is global can be nontrivial).

You mean Who??

Because of cost of name lookups it can also be better to cache global values within functions, and bake-in simple boolean tests into functions like this:

Unoptimised function:

def foo():
    if condition_that_rarely_changes:
         doSomething()
    else:
         doSomethingElse()

Optimised approach, instead of using a variable, exploit the fact that the interpreter is doing a name lookup on the function anyway!

When the condition becomes true:

foo = doSomething # now foo() calls doSomething()

When the condition becomes false:

foo = doSomethingElse # now foo() calls doSomethingElse()

PyPy

PyPy is a python implementation written in python. Surely that means it will run code infinitely slower? Well, no. PyPy actually uses a Just-In-Time compiler (JIT) to run python programs.

If you don't use any external libraries (or the ones you do use are compatible with PyPy), then this is an extremely easy way to (almost certainly) speed up repetitive tasks in your program.

Basically the JIT can generate code that will do what the python interpreter would, but much faster, since it is generated for a single case, rather than having to deal with every possible legal python expression.

Where to look Next

Of course, the first place you should have looked was to improve your algorithms and data structures, and to consider things like caching, or even whether you need to be doing so much in the first place, but anyway:

  • This page of the python.org wiki provides lots of information about how to speed up python code, though some of it is a bit out of date.

  • Here's the BDFL himself on the subject of optimising loops.

There are quite a few things, even from my own limited experience that I've missed out, but this answer was long enough already!

This is all based on my own recent experiences with some python code that just wasn't fast enough, and I'd like to stress again that I don't really think any of what I've suggested is actually a good idea, sometimes though, you have to....

brett
  • 198
  • 3
  • 9
James
  • 24,676
  • 13
  • 84
  • 130
  • 1
    +1 to your first point: if it needs to be really fast, write it in Fortran, C, C++ or assembly, then call it from Python. – Zan Lynx Aug 23 '11 at 19:39
  • -1 to your first point: if that were true, everything else you wrote would be pointless. What IS true is that optimizing python code can only get you up to a certain speed and beyond that you need other tools. So I would start your otherwise perfect answer with: 1) check for optimal algorithm 2) check if you can use e.g. pypy 3) if 1 or 2 gets you close to your needed speed optimization is ok, otherwise choose a different language... – xubuntix Aug 23 '11 at 20:07
  • @xubuntix, I've modified my first point a little. – James Aug 23 '11 at 20:16
  • 4
    Somehow this post is really missing numpy. I mean first of all large percentage of all coding challenges are really math stuff in disguise and second that's basically THE library to get python up to speed. – Voo Aug 23 '11 at 22:19
  • 2
    @Voo, I would just include numpy as part of using the right libraries -- sure, it's a pretty big library, but it doesn't do everything; it's unlikely to help you speed up a parser, for example. – James Aug 24 '11 at 07:18
3

First off, profile your code so you know where the problems lie. There are many examples of how to do this, here's one: https://codereview.stackexchange.com/questions/3393/im-trying-to-understand-how-to-make-my-application-more-efficient

You do a lot of indexed access as in:

for pair in range(i-1, j):
    if coordinates[pair][0] >= 0 and coordinates[pair][1] >= 0:

Which could be written more plainly as:

for coord in coordinates[i-1:j]:
    if coord[0] >= 0 and cood[1] >= 0:

List comprehensions are cool and "pythonic", but this code would probably run faster if you didn't create 4 lists:

N = int(raw_input())
coordinates = []
coordinates = [raw_input() for i in xrange(N)]
coordinates = [pair.split(" ") for pair in coordinates]
coordinates = [[int(pair[0]), int(pair[1])] for pair in coordinates]

I would instead roll all those together into one simple loop or if you're really dead set on list comprehensions, encapsulate the multiple transformations into a function which operates on the raw_input().

Community
  • 1
  • 1
John Gaines Jr.
  • 11,174
  • 1
  • 25
  • 25
2

This answer shows how I locate code to optimize. Suppose there is some line of code you could replace, and it is costing, say, 40% of the time. Then it resides on the call stack 40% of the time. If you take 10 samples of the call stack, it will appear on 4 of them, give or take. It really doesn't matter how many samples show it. If it appears on two or more, and if you can replace it, you will save whatever time it costs.

Community
  • 1
  • 1
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135
  • I need the test cases to be able to locate the problem though, which InterviewStreet doesn't release. Is there another way to locate whats taking so much time? – James Brewer Aug 23 '11 at 18:26
  • @James Brewer Yeah if there were only a way to write ones own test cases and profile these ;) I mean you have to write test cases anyhow to make sure that the code is correct (well you should at least) - that's a good starting point. – Voo Aug 23 '11 at 22:21
  • @James: Can't you run the code? If you don't have a dataset to run it on, simulate one. – Mike Dunlavey Aug 24 '11 at 13:40
1

Most of the interview street problems seem to be tested in a way that will verify that you have found an algorithm with the right big O complexity rather than that you have coded the solution in the most optimal way possible.

In other words if you are failing some of the test cases due to running out of time the problem is likely that you need to figure out a solution with lower algorithmic complexity rather than micro-optimize the algorithm you have. This is why they generally state that N can be quite large.

mattnewport
  • 13,728
  • 2
  • 35
  • 39