High Performance Python (from Training at EuroPython 2011) by Ian Ozsvald - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

CHAPTER

SEVEN

 

PURE PYTHON (CPYTHON) IMPLEMENTATION

Below we have the basic pure-python implementation. Typically you’ll be using CPython to run the code (CPython being the Python language running in a C-language interpreter). This is the most common way to run Python code (on Windows you use python.exe, on Linux and Mac it is often just python).

In each example we have a calculate_z function (here it is calculate_z_serial_purepython), this does the hard work calculating the output vector which we’ll display. This is called by a calc function (in this case it is calc_pure_python) which sets up the input and displays the output.

In calc I use a simple routine to prepare the x and y co-ordinates which is compatible between all the techniques we’re using. These co-ordinates are appended to the array q as complex numbers. We also initialise z as an array of the same length using complex(0,0). The motivation here is to setup some input data that is non-trivial which might match your own input in a real-world problem.

For my examples I used a 500 by 500 pixel plot with 1000 maximum iterations. Setting w and h to 1000 and using the default x1, x2, y1, y2 space we have a 500 by 500 pixel space that needs to be calculated. This means that z and q are 250,000 elements in length. Using a complex datatype (16 bytes) we have a total of 16 bytes * 250,000 items * 2 arrays == 8,000,000 bytes (i.e. roughly 8MB of input data).

In the pure Python implementation on a core 2 duo MacBook using CPython 2.7.2 it takes roughly 52 seconds to solve this task. We run it using:

>> python pure_python.py 1000 1000

If you have PIL and numpy installed then you’ll get the graphical plot.

NOTE that the first argument is 1000 and this results in a 500 by 500 pixel plot. This is confusing (and is based on inherited code that I should have fixed...) - I’ll fix the *2 oddness in a future version of this document. For now I’m more interested in writing this up before I’m back from EuroPython!

# \python\pure_python.py

import sys

import datetime

# area of space to investigate

x1, x2, y1, y2=-2.13,0.77,-1.3,1.3

 

# Original code, prints progress (because it is slow)

# Uses complex datatype

 

def calculate_z_serial_purepython(q, maxiter, z):

    """Pure python with complex datatype, iterating over list of q and z"""

    output=[0] * len(q)

    for i in range(len(q)):

 

      if i%1000==0:

        # print out some progress info since it is so slow...

        print "%0.2f%% complete"%(1.0/len(q)* i * 100)

      for iteration in range(maxiter):

        z[i]=z[i] * z[i]+q[i]

        if abs(z[i])>2.0:

           output[i]=iteration

           break

    return output

 

def calc_pure_python(show_output):

    # make a list of x and y values which will represent q

    # xx and yy are the co-ordinates, for the default configuration they’ll look like:

    # if we have a 500x500 plot

    # xx = [-2.13, -2.1242, -2.1184000000000003, ..., 0.7526000000000064, 0.7584000000000064, 0.7642000000000064]

    # yy = [1.3, 1.2948, 1.2895999999999999, ..., -1.2844000000000058, -1.2896000000000059, -1.294800000000006]

    x_step=(float(x2-x1)/float(w))* 2

    y_step=(float(y1-y2)/float(h)) * 2

    x=[]

    y=[]

    ycoord=y2

    while ycoord>y1:

          y.append(ycoord)

          ycoord+=y_step

    xcoord=x1

    while xcoord<x2:

          x.append(xcoord)

          xcoord+=x_step

    q=[]

    for ycoord in y:

        for xcoord in x:

           q.append(complex(xcoord,ycoord))

 

    z=[0+0j] *len(q)

    print "Total elements:",len(z)

    start_time=datetime.datetime.now()

    output=calculate_z_serial_purepython(q, maxiter, z)

    end_time=datetime.datetime.now()

    secs=end_time-start_time

    print "Main took", secs

 

    validation_sum=sum(output)

    print "Total sum of elements (for validation):", validation_sum

 

    if show_output:

       try:

          import Image

          import numpy as nm

          output=nm.array(output)

          output=(output+(256 * output)+(256 ** 2)* output)* 8

          im=Image.new("RGB", (w/2, h/2))

          im.fromstring (output.tostring(),"raw","RGBX",0,-1)

          im.show()

       except ImportError as err:

          # Bail gracefully if we’re using PyPy

          print "Couldn’t import Image or numpy:",str(err)

 

if __name__=="__main__":

    # get width, height and max iterations from cmd line

    # ’python mandelbrot_pypy.py 100 300’

    w=int(sys.argv[1]) # e.g. 100

    h=int(sys.argv[1]) # e.g. 100

    maxiter=int(sys.argv[2]) # e.g. 300

 

    # we can show_output for Python, not for PyPy

    calc_pure_python(True)

When you run it you’ll also see a validation  sum - this is the summation of all the values in the output list, if this is the same between executions then your program’s math is progressing in exactly the same way (if it is different then something different is happening!). This is very useful when you’re changing one form of the code into another - it should always produce the same validation sum.