beeware / rubicon-objc

A bridge interface between Python and Objective-C.
https://rubicon-objc.readthedocs.io
Other
265 stars 51 forks source link

Speed up instantiating new class instances #183

Open samschott opened 4 years ago

samschott commented 4 years ago

Is your feature request related to a problem? Please describe.

Instantiating an ObjC class with rubicon-objc takes about 600 - 700 μs which is relatively slow compared to PyObjc with 10 - 15 μs. This can become problematic for instance when showing a table with 30 rows and 5 columns which are visible (see https://github.com/beeware/toga/issues/1030): each of the 150 cells is a NSTableCellView which has a NSTextField and a NSImageView and 6 layout constraints. Creating these 1350 class instances then takes almost one second which is a very noticeable lag for a "simple" table. A similar task in PyObjc takes about 16 ms.

Describe the solution you'd like

Speed up the creation of ObjCClass instances. I am not sure about how rubicon-objc handles creating class instances but can try to look through the code to see if there are obvious slowdowns.

dgelessus commented 4 years ago

Hi, sorry for not replying earlier. Thanks for the report - in the past we haven't focused that much on performance in rubicon-objc, so it's quite likely that some parts of the code just aren't very well optimized. PyObjC will always have a slight performance advantage because it uses native code for many parts of the Objective-C interface (whereas rubicon-objc uses only ctypes), but the overhead of ctypes shouldn't be that big, and certainly doesn't explain why rubicon-objc is 60 times as slow as PyObjC.

For testing, I ran for _ in range(1000): NSObject.new().autorelease() with cProfile, to see if there were any obvious slow calls that happen often. Here are the most significant things I found:

These are just the most obvious things I found - there might be other possible optimizations that I haven't seen. (If you find any other slow parts in the method calling code that could be optimized, let us know!) When I have the time I'll try implementing these optimizations properly - or if you like you can submit a PR yourself.

samschott commented 4 years ago

Thanks for looking into this!

The speedup from from 600 µs to 300 µs when removing py_from_ns conversions seems worth the tradeoff of not automatically converting numbers.

Of course, there is still more than an order of magnitude difference in performance to PyObjc which may not entirely be due to using ctypes. I think PyObjc may have methods and return types of most Frameworks hard-coded but I am not sure about that.

For now, it would be great if you could implement the optimisations. I still not familiar with most parts of rubicon.objc.

dgelessus commented 4 years ago

184 is merged now, which speeds up calls like NSObject.alloc().init() from about 600 µs to about 200 µs. This is much faster than before, but can probably still be improved further (see #185 for example), so I'll keep this issue open to track that.

samschott commented 4 years ago

Wow, I have just tested this and it makes a large difference for UIs with many widgets. The user interface feels a lot smoother, especially when widgets are initialised on-demand, and startup times are significantly shorter.

Thank you!