Previous Post

Dev Tales - The "un-pythonic" instantiations

Next Post
Dev Tales - The "un-pythonic" instantiations
1 / 3
DESCRIPTION

Today I planned to publish a new release, but a last-minute problem forced me to delay it by a few days. If you’re looking for updates or news on Update All, hang tight as nice changes are coming very soon. Feel free to skip this post and check back later.

Since I already had a write-up ready, I’m sharing the technical part and launching a new series called “Dev Tales".

The problem

As I mentioned in the previous post, Python isn’t the fastest language, and that becomes painfully obvious on the DE10-Nano board. During the major refactor for the 2.0 release, I introduced an intermediate object to handle file routes more cleanly. That object is the one you see pictured here, named PathPackage. It’s nothing fancy: a simple Python class that even uses __slots__ to speed up attribute access and instantiation.

But here is the issue: PathPackage can be instantiated up to 20,000 times per run (once for every file checked). On a standard desktop CPU, that cost is negligible, but on our ARM board it feels like a dial-up modem. Each instantiation took roughly 200 µs. That is an eternity for such a trivial task and ended up adding about 4 to 5 seconds to a no-update run. That was unacceptable.

The un-pythonic solution

That instantiation was happening inside a for loop, and since Python for loops can be slower than list comprehensions, I rewrote that part using a list comprehension. It did make the code noticeably faster, but not by much. Clearly, instantiation was still the biggest bottleneck.

Next, I experimented with every Python data type I could think of—plain dicts, tuples, classes with and without __slots__, frozen dataclasses, and so on. Tuples were significantly faster, but their ergonomic drawbacks ruled them out. Still, they revealed the true lower bound of this optimization: I could cut those extra 4–5 seconds down to under half a second.

After researching further, I discovered that the real slowdown came from running constructors (the __init__ methods). Since I end up assigning most attributes immediately after instantiation, I had a crazy thought: what if I skip the constructors altogether? It turns out you can. By calling object.__new__ directly on any class, you get an uninitialized instance without running its constructor. Sure, accessing uninitialized attributes is dangerous, but because I centralize all instantiation and assignment in a single method (TargetPathsCalculator.create_path_packages), the risk is minimal.

So I replaced PathPackage() with object.__new__(PathPackage) within a list comprehension and then loop over it to populate the attributes manually. I was very happy with the result. All the instantiations now take under a second, making the whole process far more quick on the DE10-Nano. It worked!

This is the final result:

Additionally, I swapped out enumerate() for itertools.repeat() and hoisted frequently used functions and attributes into local variables. Those tweaks trimmed even more overhead, shaving precious microseconds off each iteration and driving the total instantiation time still lower.

This example illustrates the type of optimizations one must perform on an embedded device once the low-hanging fruits have been exhausted and diminishing returns truly set in. While I resorted to other ugly tricks in different sections during that 2.0 refactor, I believe this particular optimization undoubtedly takes the prize for the most un-pythonic code I wrote so far in this code-base.

Wrapping Up

Alright, that was the initial chapter of the "Dev Tales". Are these technical deep-dives interesting or helpful? If so, let me know! I’d love to write more of them, but I want to make sure they’re something you enjoy.

And finally, I’d like to share that I'm considering a small price adjustment (1€+) for the “Thumbs Up” tier soon. I hope this feels reasonable. It’s been 41 months since the current price was set, and with living costs rising significantly over the past 3.5 years (as I’m sure you’ve noticed), this update is necessary. Your support has always been crucial, letting me prioritize these projects over freelancing, especially now that I’m back in university full-time and no longer in a corporate job.

Thank you again for your support!

Jose BG PATREON 0 favs
VIEWS1
FILES3 files
POSTEDMay 9, 2025
ARCHIVEDMay 9, 2025