Line Benchmarks - Tkinter vs. wxPython

Gordon Williams g_will at cyberus.ca
Fri Dec 22 05:33:51 CET 2000


Hello All,

In November of 1999 I did some benchmark tests to gauge the performance
difference between Tkinter and wxPython when large numbers of lines are
plotted.  At that time wxPython outperformed Tkinter by a large margin as
can be seen in the results that are repeated below.  As a full year has
passed and there are new versions of Python, Tkinter and wxPython, I felt
that it was time to rerun the tests to see if there were any significant
changes for better or worst.
While Tkinter has improved significantly, it is still far behind wxPython in
this aspect by a margin of 20 to 30 times.

The same computer was used to generate the new set of results, so the
results are directly comparable.  As described below, a repeating sine wave
was created and put into a horizontal scrollable window.  These are the
results
for the line plotting time only.  Time in Seconds. (W95, Python 2.0,
wxPython 2.2.2)

Points      Tkinter      Tkinter   Tkinter    wxPython     wxPython
                (standard) (mod 1)  (mod 2)  (direct draw) (bitmap)
1000        0.163        0.162      0.157         0.0078            0.028
2000        0.324          --            --             0.0130
0.041
5000        0.817          --            --             0.0390
0.092
10000      1.62          1.63        1.64           0.0590            0.157

See the notes below for more information on the configuration for each run.

Conclusions:
1) The biggest change is in the performance of Tkinter (standard).
Previously, the time to plot lines using the standard Tkinter was quadratic
with the
number of points (ugh!). Now it is linear and the results are very similar
to both of the modified Tkinter runs.  An improvement was made to speed up
the_flatten method where most of the time was spent previously.

2) Tkinter mod 1 and 2 give almost the same results.  There is a slight
improvement over the last years results by 15 to 20%.  Some other changes
must have been made to the code to get these efficiencies.

3) wxPython results have changed very little.  Maybe they have gotten 5 to
10% slower, but it is difficult to say without doing a more though study.
Direct drawing onto to screen is still roughly 3 times faster than using a
bitmap.

4) wxPython still beats Tkinter by a wide margin.  Although the number are
not as huge as they were a year ago, wx still goes 20 - 30 times faster.

If you need raw speed to get large numbers of lines out to the screen,
wxPython is the way to go.

It would be interesting to have this repeated on a Unix box to get
comparable results.

Regards,

Gordon Williams
Dec. 2000



########################################################
November 1999 (Windows 95, Python 1.5.2, wxPython ?)

I have done some benchmark testing to see the difference in speed between
plotting multi point lines using Tkinter and wxPython.  I have looked at 3
different methods for doing it in Tk and 2 different methods in wx.  The
differences are astounding for the 1000 to 10000 point lines that I
examined.  Generally, wx was found to be in the order of 200 to 1800 times
faster than the standard Tkinter.  Tk can be sped up by ten to 50 times by
changing Tkinter so it bypasses the _flatten method or by making the Tk
calls directly.

A repeating sine wave was created and put into a horizontal scrollable
window of about 500 points wide and 300 points high.  These are the results
for the line plotting time only.Time in Seconds on a P166 with 96MB RAM.

Points      Tkinter      Tkinter   Tkinter    wxPython     wxPython
                (standard) (mod 1)  (mod 2)  (direct draw) (bitmap)
1000        1.190        0.192      .202         0.007            0.026
2000        4.406        0.385        --           0.014            0.042
5000      25.170        0.967        --           0.027            0.080
10000  100.260        1.941     2.064        0.054            0.148

Notes:
1) Tk standard is the standard usage with no modifications.
2) Tk mod 1 has Tkinter modified so a flat tuple of points is passed to
create_line and _flatten is by-passed.
3) Tk mod 2 used the apply method  e.g.
apply(self.can.tk.call,(self.can._w,'create','line') + points +
('-width',1,'-fill',colour,'-tags',"tag"))
4) wx direct draw draws to the window directly.
5) wx bitmap draws to a bitmap and then blit

Conclusions:
1) The time to plot lines using the standard Tkinter is quadratic with the
number of points (ugh!).  This is due to the time spent in _flatten.
_flatten is very versatile but agonizingly slow for large numbers of lines.
2) Tkinter mod 1 and 2 give the same results.  Times are linear with the
number of points plotted as both cases dont use the _flatten method.  This
means that a flat tuple of points has to be used (a small constraint).  The
speed increase in doing this is from 10 to 50 times over the standard.
3) wxPython beats Tk by a very! large margin even when Tk is called
directly.  Direct drawing onto to screen is roughly 3 times faster than
using a bitmap.

Thanks to:
Greg Landrum for providing some information on direct calls to tk.
Ionel Simionescu for providing the wxPython code that I was able to modify.

############################################################
I know I am going to get questions on where is the code, so here it is.  I
have not supplied my version of Tkinter (Tkinter_gw.py) because it is large.

############################################################
#Ionel2_test_spd.py (for draw to bitmap)
__author__ = "Ionel Simionescu"  # messed up by Gordon Williams
#---------------------------------------------------------------------------
from wxPython.wx import *
import time
from math import sin

#---------------------------------------------------------------------------
t0 = time.clock()

N = 1000                # the number of line points


points = []         # here we store the points
for k in range(N):
    points.append( (k, 50+ 50*sin(k/10.)) )
#print points
#print points

t1 = time.clock()
print 'Data is ready! Total data preparation time:  %f ' %  (t1 - t0)

#---------------------------------------------------------------------------

bmpW = N #changed
bmpH = 300

#---------------------------------------------------------------------------
class myCanvas(wxScrolledWindow):
    def __init__(self, parent, ID, position, size):
        wxScrolledWindow.__init__(self, parent, ID, position, size,
wxSUNKEN_BORDER)
        self.SetBackgroundColour(wxNamedColor("WHITE"))

        self.maxWidth  = 500
        self.maxHeight = 300
        self.SetScrollbars(20, 0, N/20, 0) #self.maxWidth/20,
self.maxHeight/20)

        self.bmp = wxEmptyBitmap(bmpW, bmpH, -1)
        #self.bmp.Create(bmpW, bmpH, -1)
        #
        self.bmp_w = self.bmp.GetWidth()
        self.bmp_h = self.bmp.GetHeight()

        # flag used to redraw only when necessary
        self.new_data = 1
        EVT_PAINT(self, self.OnPaint)

    def OnPaint(self, event):
        dc = wxPaintDC(self)
        self.PrepareDC(dc)
        self.DoDrawing(dc)

    def DoDrawing(self, dc):
        t0 = time.clock()
        pen1 = wxPen(wxNamedColour('RED'))

        tmp_dc = wxMemoryDC()
        tmp_dc.SelectObject(self.bmp)

        # if new data, update the bitmap drawing
        if self.new_data:
            tmp_dc.BeginDrawing()
            tmp_dc.Clear()
            tmp_dc.SetPen(pen1)
            # draw the lines
            tmp_dc.DrawLines( points ) #changed
            tmp_dc.EndDrawing()
            # reset the new_data flag
            self.new_data = 0

        # copy the bitmap to the canvas
        dc.Blit( 0, 0, self.bmp_w, self.bmp_h, tmp_dc, 0, 0 )

        t1 = time.clock()
        print 'Plotting is ready! Total drawing time:  %f ' %  (t1 - t0)



#---------------------------------------------------------------------------
class myFrame(wxFrame):
    def __init__(self):
        wxFrame.__init__(
            self, NULL, -1, "wx_plot_lines",
            wxDefaultPosition, size= (500,300)#wxSize(500, 300)
        )
        myCanvas(self, -1, wxPoint(0,0), self.GetClientSize())

#---------------------------------------------------------------------------
class myApp(wxApp):
    def OnInit(self):
        myFrame().Show(TRUE)
        return TRUE

#---------------------------------------------------------------------------
if __name__ == '__main__':
    app = myApp(0)
    app.MainLoop()

#######################################################
#Ionel1_test_spd.py (for direct draw method)
__author__ = "Ionel Simionescu" # messed up by Gordon Williams
#---------------------------------------------------------------------------
from wxPython.wx import *
from math import sin
import time

#---------------------------------------------------------------------------
t0 = time.clock()

N = 10000                # the number of line points


points = []         # here we store the points
for k in range(N):
    points.append( (k, 50+ 50*sin(k/10.)) )
#print points

t1 = time.clock()
print 'Data is ready! Total data preparation time:  %f ' %  (t1 - t0)


#---------------------------------------------------------------------------
class myCanvas(wxScrolledWindow):
    def __init__(self, parent, ID, position, size):
        wxScrolledWindow.__init__(self, parent, ID, position, size,
wxSUNKEN_BORDER)
        self.SetBackgroundColour(wxNamedColor("WHITE"))

        self.maxWidth  = 500
        self.maxHeight = 300
        self.SetScrollbars(20, 0, N/20, 0) #self.maxWidth/20,
self.maxHeight/20)
        EVT_PAINT(self, self.OnPaint)

    def OnPaint(self, event):
      dc = wxPaintDC(self)
      self.PrepareDC(dc)
      self.DoDrawing1(dc)

    def DoDrawing1(self, dc):
        t0 = time.clock()

        dc.BeginDrawing()
        pen1 = wxPen(wxNamedColour('RED'))
        dc.SetPen(pen1)
        #
        # draw line_points
        dc.DrawLines(points, 0)
        #
        dc.EndDrawing()

        t1 = time.clock()
        print 'Plotting is ready! Total drawing time:  %f ' %  (t1 - t0)



class myFrame(wxFrame):
    def __init__(self):
        wxFrame.__init__(
            self, NULL, -1, "wx_plot_lines",
            wxDefaultPosition,wxSize(500,300)
        )

        myCanvas(self, -1, wxPoint(0,0), self.GetClientSize())

#---------------------------------------------------------------------------
class myApp(wxApp):
    def OnInit(self):
        myFrame().Show(TRUE)
        return TRUE

#---------------------------------------------------------------------------
def test():
    app = myApp(0)
    app.MainLoop()

if __name__ == '__main__':
    test()

##########################################################
# File for benchmarking multi-point lines
# in wxPython and Tkinter.
#
# This file is for Tkinter
# Change the commented sections as required.
#
# Author: Gordon Williams 99/11/15
#



import time
from math import sin
#from Tkinter_gw import *
from Tkinter import *

class LayoutFrames:
    def __init__(self,master):
        # ********** Frame Layouts ***************
        # Main Frame Layout

        self.data= Frame(master)
        self.data.pack(side=TOP,fill=X)

        self.scroll= Frame(self.data)
        self.scroll.pack(side=BOTTOM,fill=X)


class Plot:

    def __init__(self, master, master_scroll=None):

# dataframe with canvas and scollbar
        self.scrollbar = Scrollbar(master_scroll, orient=HORIZONTAL)
        self.can = Canvas(master,cursor="crosshair", width=500,
height=300,relief=RIDGE, bd=2,bg="lightyellow",
xscrollcommand=self.scrollbar.set)
        self.scrollbar.config(command=self.can.xview)
        self.scrollbar.pack(side=BOTTOM, fill=X)
        self.can.pack(side=LEFT)


    def makelines(self,points, tag="misc",colour="black"):
        t0 = time.clock()
#>>>> comment out as required (use for standard and mod1 runs)
        #self.can.create_line(points,width= 1, tags=tag, fill=colour)

#>>>> comment out as required (use for mod2 runs)
        apply(self.can.tk.call,(self.can._w,'create','line') + points +
            ('-width',1,'-fill',colour,'-tags',"tag"));


        self.can.config(scrollregion=self.can.bbox(ALL))

        t1 = time.clock()
        print 'Plotting is ready! Total drawing time:  %f ' %  (t1 - t0)



class PlotResults:
    def __init__(self, dataFrame, scrollFrame):

    # ********** PLOTTING DATA ***************
        self.analog = Plot(dataFrame,scrollFrame)


    def analogPlot(self):
        self.analog.makelines(linePoints, "dataA","black" )


#************ start of main **************************
def main():

    root= Tk()
    frame=LayoutFrames(root)

    analog=PlotResults( frame.data, frame.scroll)
    analog.analogPlot()

    root.mainloop()
    try:
        root.destroy() # removes root
    except:
        pass


# make a bit of data
N= 1000   # number of points
points = []         # here we store the points
for k in range(N):
    points.extend( [k, 50+ 50*sin(k/10.)] )
#print points


linePoints= tuple(points)  # must be in tupple form for Tkinter_gw
main()
#######################################################
Enjoy!!






More information about the Python-list mailing list