Hello,
I've run into an issue when drawing a line with a large number of points using
DrawLines. I've attached code below that simply draws a Numeric array of 30000
points into a window. When the window is resized, it regenerates the point list
and draws the points again. When the 30000 points are initially drawn to the
window, they are very fast (.07 seconds), but subsequent draws after a resize
are much slower (sometimes 2 orders of magnitude. Here is the output from my
little test script when using DrawLines. The first timing is when the window
initially appears. The others are after a resize. This is using wxPython2.3.2
with Python 2.1.3 on windows 2000.
C:\home\ej\wrk\chaco>python faster.py
30001
wxPython DrawLines: 0.0752268793939
30000
wxPython DrawLines: 0.327031508194
30000
wxPython DrawLines: 6.30562264198
30000
wxPython DrawLines: 6.32888677192
30000
wxPython DrawLines: 4.22979680378
I thought maybe the wxPython overhead was the problem, so I used weave.inline to
bypass the wxPython stuff and draw the win32 PolyLine API routine directly. I
got the same lousy result.
C:\home\ej\wrk\chaco>python faster.py
30001
str: _1878900_wxPaintDC_p <weave debug output>
Call to Polyline: 1.30914739164 <slower due to loading a dll>
30001
str: _1878900_wxPaintDC_p
Call to Polyline: 0.285114728269
30000
str: _1878900_wxPaintDC_p
Chunked calls to Polyline: 5.18981490664
30000
str: _1878900_wxPaintDC_p
Call to Polyline: 0.327584651122
30001
str: _1878900_wxPaintDC_p
Call to Polyline: 6.85253796223
30000
str: _1878900_wxPaintDC_p
Call to Polyline: 4.44026182099
Further poking around showed that the slow down was much worse than linear with
the number of points being drawn. This seemed wierd, but I went ahead and set
things up to draw the line in small chunks of 100 points to see if that helped.
It did in a major way.
C:\home\ej\wrk\chaco>python faster.py
30001
str: _1878900_wxPaintDC_p
Chunked calls to Polyline: 1.49216897678 <slow because it is loading a DLL>
30000
str: _1878900_wxPaintDC_p
Chunked calls to Polyline: 0.199176990372
30000
str: _1878900_wxPaintDC_p
Chunked calls to Polyline: 0.318512827748
30000
str: _1878900_wxPaintDC_p
Chunked calls to Polyline: 0.316748078317
30000
str: _1878900_wxPaintDC_p
Chunked calls to Polyline: 0.266672160847
All this background to ask some non-wxPython questions (though related...):
1. Can anyone verify that they get the same behavior with
DrawLines? <I wondering if something is strange about
my machine setup>
Verifying the weave behavior requires a CVS version of
weave -- I'm less interested in this, and it'll require
effort.
2. Does anyone have any insight into what is going on here?
a. Why is PolyLine fast for the initial call and slow for
subsequent calls with a large number of points?
b. Why is PolyLine slow for a large number of points?
Heck, 30000 isn't even all that large of a number.
3. Does Linux have the same problem?
4. Am I missing an extra call somewhere that would fix all my
problems?
The code below is the demo. It does require Numeric, but doesn't require
weave unless you restore the commented out code in the OnPaint routine.
As a side benefit, it does show a nice example of how to use weave with
wxPython.
thanks for any hints,
eric
···
--
Eric Jones <eric at enthought.com>
Enthought, Inc. [www.enthought.com and www.scipy.org]
(512) 536-1057
""" Implements a fast replacement for calling DrawLines with an array as an
argument. It uses weave, so you'll need that installed.
I wrote this because I was seeing very bad performance for DrawLines when
called with a large number of points -- 5000-30000. Now, I have found the
performance is sometimes OK, and sometimes very poor. Drawing to a
MemoryDC seems to be worse than drawing to the screen. My first cut of the
routine just called PolyLine directly, but I got lousy performance for this
also. After noticing the slowdown as the array length grew was much worse
than linear, I tried the following "chunking" algorithm. It is much more
efficient (sometimes by 2 orders of magnitude, but usually only a factor
of 3). There is a slight drawback in that it will draw end caps for each
chunk of the array which is not strictly correct. I don't imagine this is
a major issue, but remains an open issue.
"""
from Numeric import *
polyline_code = \
"""
// uses a grouping approach because drawing seems to be veeery slow for
// a large number of lines.
// Hmmm. Further inspection shows that this isn't always the case, but
// don't have the time to track down the causes. It seems to always be
// fast using this algorithm.
int *p_data = line_data;
HDC hdc = (HDC) dc->GetHDC();
const int n_pts = _Nline[0];
const int bunch_size = 100;
const int bunches = n_pts / bunch_size;
const int left_over = n_pts % bunch_size;
for (int i = 0; i < bunches; i++)
{
Polyline(hdc,(POINT*)p_data,bunch_size);
p_data += bunch_size*2; //*2 for two longs per point
}
Polyline(hdc,(POINT*)p_data,left_over);
"""
def polyline(dc,line):
"""
"""
import weave
shp = line.shape
assert(len(shp)==2 and shp[1] == 2)
if (line.typecode() != Int32 or
not line.iscontiguous()):
line = line.astype(Int32)
weave.inline(polyline_code,['dc','line'])
if __name__ == '__main__':
from wxPython.wx import *
import time
class Canvas(wxWindow):
def __init__(self, parent, id = -1, size = wxDefaultSize):
wxWindow.__init__(self, parent, id, wxPoint(0, 0), size,
wxSUNKEN_BORDER | wxWANTS_CHARS)
self.point_count = 30000
self.calc_points()
EVT_PAINT(self, self.OnPaint)
EVT_SIZE(self, self.OnSize)
def calc_points(self):
w,h = self.GetSizeTuple()
from RandomArray import *
x = arange(0., w, float(w)/self.point_count)
y = randint(0., h, len(x))
self.points = concatenate((x[:,NewAxis],y[:,NewAxis]),-1)
def OnSize(self,event):
self.calc_points()
self.Refresh()
def OnPaint(self,event):
print len(self.points)
dc = wxPaintDC(self)
dc.SetPen(wxRED_PEN)
dc.BeginDrawing()
t1 = time.clock()
dc.DrawLines(self.points)
t2 = time.clock()
print 'wxPython DrawLines:', t2-t1
"""
# This first call is slow because your either compiling (very slow)
# or loading a DLL (kinda slow)
# Resize the window to get a more realistic timing.
t1 = time.clock()
dc.SetPen(wxGREEN_PEN)
polyline(dc,self.points)
t2 = time.clock()
print 'Chunked calls to Polyline:', t2-t1
"""
dc.EndDrawing()
class CanvasWindow(wxFrame):
def __init__(self, id=-1, title='Canvas',size=(500,500)):
parent = NULL
wxFrame.__init__(self, parent,id,title, size=size)
self.canvas = Canvas(self)
self.Show(1)
class MyApp(wxApp):
def OnInit(self):
frame = CanvasWindow(title="Speed Examples",size=(500,500))
frame.Show(true)
return true
app = MyApp(0)
app.MainLoop()