Hubro
Hubro

Reputation: 59333

Can I speed optimize the use of GridCellAttr in wxPython's wx.grid.Grid?

I'm making a Windows application using wx.grid.Grid that will handle very large Microsoft Excel documents. Currently it opens a file with 17 columns and 12 000+ rows quite quickly, and I can scroll around smoothly. (This is a virtual table that operates on data from a custom table class.)

Anyway, the problem starts when I use custom grid cell attribute objects, e.g:

grid.SetAttr(row, col, SomeGridCellAttr('#FF0000'))

Once every cell in a row has a custom grid cell attribute, the performance of the grid just goes 99.9% into the toilet. Instead of smooth scrolling around I'm lucky if I get 1 redraw per 3 seconds. I fixed most of these instances by using grid.SetColAttr instead, which restored performance to it's previous smoothness, but there is one case where that won't work. The application iterates over every cell in a column (12 000 cells), performs some processing on the data and applies a custom grid cell attribute based on the results. Once that is done, the grid becomes a sluggish nightmare to work with.

Is there any way of removing this gruesome performance hit and retain the custom cell attributes? I suspect the answer is quite simple to somebody who knows the inner workings of the grid and it's cell attributes.

Thanks.

Upvotes: 0

Views: 1972

Answers (1)

Anonymous Coward
Anonymous Coward

Reputation: 6226

Setting a cell attribute will add a new GridCellAttr to the list of the GridCellAttrProvider. As the list grows, looking up a specific attribute for a cell (by iterating through the list and comparing the coordinates) gets slower and slower.

You can try to speed it up by implementing your own PyGridTableBase.SetAttr and GetAttr (using a dict for example):

EDIT: Updated code to allow overwriting attributes and emulate the default implementations attribute ownership.

class MyTable(wx.grid.PyGridTableBase):
    atts = {}

    def Hash(self,row,col):
        #FIXME: assumes a constant number of rows and rows > cols
        return col + row * self.GetNumberRows()

    def SetAttr(self,attr,row,col):
        HASH = self.Hash(row, col)
        if HASH in self.atts:
            # decrement usage count of existing attr
            self.atts[HASH].DecRef()
        #assign new attribute
        self.atts[HASH] = attr

    def GetAttr(self,row,col,kind):
        HASH = self.Hash(row, col)
        if HASH in self.atts:
            attr = self.atts[HASH]
            attr.IncRef() # increment reference count
            return attr
        return None

To allow setting entire rows and columns, you'll also have to implement:

    def SetRowAttr(self,attr,row):
        for col in range(self.GetNumberCols()):
            attr.IncRef() # increment reference count for SetAttr
            self.SetAttr(attr,row,col)
        attr.DecRef() # attr passed to SetRowAttr no longer needed

    def SetColAttr(self,attr,col):
        for row in range(self.GetNumberRows()):
            attr.IncRef() 
            self.SetAttr(attr,row,col)
        attr.DecRef()

NOTE: when passing a GridCellAttr to Set*Attr(), the default implementation will take ownership of the attribute. To re-use the same attribute (stored in a class variable for example), you have to either Clone() it or increment its usage count (IncRef()) before passing it to a Set*Attr() method (cloning may increase memory consumption).

The above example lacks proper removal of attributes: SetAttr() could check for a None attr and decrement the ref count at the coordinates specified, then delete the entry from the dict. SetCol/RowAttr() can be optimized by adding dicts for row and col, similar to SetAttr(). GetAttr() could then check for an existing entry in the row and col dict and merge/override the attribute(s) with the one from the cell dict (that's the principle used by the default implementation). For proper cleanup of the dict(s), call DecRef on every entry before .clear().

Alternatively, you could derive from wx.grid.GridCellAttrProvider and attach it with PyGridTableBase.SetAttrProvider(). This would prevent direct access to the table, however.

Upvotes: 3

Related Questions