Ertug Karamatli / pages / lang /

Pools in Python




Object pooling is pre-allocating a number of objects to use later. It is done by creating a group of objects (pool) initially and when you need an object, you take one from the pool rather than creating it. When you are done with the object you send it back to the pool. It effectively means reusing of objects. This allows bypassing the object creation overhead and reducing the work of garbage collector. Thus, managing the memory yourself. This is, of course, should be used with care because if you really want to manage the memory, why use a high-level language like Python?

Despite the fact that many programs don't need object pooling, this technique can speed things up, especially in situations explained below.

When to use Object Pooling?

There are two main reasons to use object pooling:

It is an optimization so it shouldn't be applied until there is a proved bottleneck. It will increase the overall complexity of your program and probably present additional bugs. Also, when you create many objects at start, your program may allocate too much memory throughout its lifetime. As a result, you should carefully analyze the trade-off here.

Sample Implementation

The harbor example is from Paul Graham's book ANSI Common Lisp. Here are two classes, one is using object pooling while the other is not.

class Ship():
    def __init__(self):
        self.name = None
        self.flag = None
        self.tons = None

class HarborWithPooling():
    def __init__(self, count):
        self.pool = [Ship() for i in range(count)]
        self.harbor = {}

    def enter(self, name, flag, tons):
        ship = self.pool.pop()
        ship.name = name
        ship.flag = flag
        ship.tons = tons
        self.harbor[name] = ship

    def find_ship(self, name):
        return self.harbor[name]

    def leave(self, name):
        ship = self.harbor[name]
        self.pool.append(ship)
        del self.harbor[name]

class HarborWithoutPooling():
    def __init__(self, count):
        self.harbor = {}

    def enter(self, name, flag, tons):
        ship = Ship()
        ship.name = name
        ship.flag = flag
        ship.tons = tons
        self.harbor[name] = ship

    def find_ship(self, name):
        return self.harbor[name]

    def leave(self, name):
        ship = self.harbor[name]
        del self.harbor[name]

Benchmark

Its execution time is measured while enter and leave methods are called 100,000 times in both classes. The test machine has an Intel Core 2 Duo 1.80GHz CPU and it is running Linux. Tested using Python 2.5.4. If you have time to test it on another version or OS, please let me know. You can download the complete source code here.

EnterLeaveTotal
With Object Pooling459 ms246 ms705 ms
Without Object Pooling952 ms224 ms1176 ms
Speed Up%107-%9%66

There is about %66 overall speed increase when using object pooling.

Conclusion

Using object pooling in Python increases total object creation and destruction rate more than two-fold. When used appropriately, it will help developing fast Python programs. Particularly, the programs that needs to be fast such as games.

Further Reading

Pool image from: Stuck In Customs


blog comments powered by Disqus


If you liked the article you can subscribe to the RSS feed or share:
Creative Commons License