PERF Faster random forest training, by removing redundant memoryview creation - #34586
Conversation
|
I confirm those timings on my laptop: 14.5s on main vs 9s on this branch A while ago, I did exactly that but in a hotter loop #32181. At that time I measured similar kind of changes and it had no impact, but I guess it's much more visible on this dataset which is quite different from datasets I use for benchmarking trees usually: it's sparse (very sparse). I never benchmark sparse datasets (maybe I should?). |
cakedev0
left a comment
There was a problem hiding this comment.
LGTM. I think you can include a changelog about speedup for sparse datasets.
I tried really hard to find a speedup for the dense case but I couldn't.
| cdef: | ||
| intp_t i, current_end, end_non_missing | ||
| float32_t[::1] feature_values = self.feature_values | ||
| const float32_t[:, :] X = self.X |
There was a problem hiding this comment.
You let const memviews be, so I guess those don't cost anything?
There was a problem hiding this comment.
That wasn't an explicit thing I notice. So I might have just missed one, or it really is fine and so it didn't show up in the final profile run. More likely I just missed one 😁
| # Enable when doing profiling: | ||
| # '-g', |
There was a problem hiding this comment.
For what kind of profiling does that help? py-spy --native? Something else?
There was a problem hiding this comment.
I used samply; with -g, you can see the memory view creation and destruction in profiling, and also you can see how many hits per line of code (for the generated C code, anyway). Quite nice.
There was a problem hiding this comment.
I think we should also update the performance/profiling section of the scikit-learn contributor guide but let's do that in a follow-up PR.
There was a problem hiding this comment.
I think you can also do it with the meson buildtype, but since meson-python overrides it to be release (see doc), you need to do it at the command-line IIRC. I think O2 -g is debugoptimized so you need a command-line like this:
pip install . -Csetup-args=-Dbuildtype=debugoptimized
ogrisel
left a comment
There was a problem hiding this comment.
Please add a towncrier entry of type "efficiency" for this PR.
|
I added the changelog entry. |
|
All right, let's merge this one then! Seems like there is some mystery why this only speeds up the sparse dataset case, but oh well 🤷 |
What does this implement/fix? Explain your changes.
TIL creating Cython memoryviews can involve Python reference count incrementing and therefore potential GIL acquisition, and then decrementing refcounts when it goes out of scope. So just using
self.memviewdirectly is cheaper thancdef int[::1] memview = self.memview.Usually this doesn't matter, but if it's in an inner loop it adds up I guess. As it turns out, this seems to be the case at least when running
python benchmarks/bench_20newsgroups.py -e random_forest. I tested with Python 3.14 (normal); numbers were similar for free-threaded Python.Before in upstream/main:
After this PR:
There's some noise in test-time, e.g. I just had it at 0.5109, so I don't think this slowed that down.
AI usage disclosure
None.
Any other comments?
I found this a little surprising, seems like something Cython could optimize? It's doing something more efficient for function calls I'm pretty sure.