Thanks to visit codestin.com
Credit goes to github.com

Skip to content

PERF Faster random forest training, by removing redundant memoryview creation - #34586

Merged
lesteve merged 5 commits into
scikit-learn:mainfrom
itamarst:faster-partitioner
Aug 4, 2026
Merged

PERF Faster random forest training, by removing redundant memoryview creation#34586
lesteve merged 5 commits into
scikit-learn:mainfrom
itamarst:faster-partitioner

Conversation

@itamarst

@itamarst itamarst commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

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.memview directly is cheaper than cdef 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:

Classifier   train-time test-time Accuracy
--------------------------------------------
random_forest     20.5980s   0.5086s     0.7645

After this PR:

Classifier   train-time test-time Accuracy
--------------------------------------------
random_forest     13.6600s   0.5280s     0.7645  

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.

@itamarst itamarst changed the title Faster random forest training, by removing memoryview creation (presumably in an inner loop) PERF Faster random forest training, by removing redundant memoryview creation Jul 27, 2026
@itamarst
itamarst marked this pull request as ready for review July 27, 2026 21:12
@cakedev0

Copy link
Copy Markdown
Contributor

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 cakedev0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You let const memviews be, so I guess those don't cost anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 😁

Comment thread meson.build
Comment on lines +30 to +31
# Enable when doing profiling:
# '-g',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what kind of profiling does that help? py-spy --native? Something else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ogrisel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a towncrier entry of type "efficiency" for this PR.

@itamarst

itamarst commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

I added the changelog entry.

@lesteve
lesteve merged commit 6b64adc into scikit-learn:main Aug 4, 2026
38 checks passed
@lesteve

lesteve commented Aug 4, 2026

Copy link
Copy Markdown
Member

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 🤷

prady0t pushed a commit to prady0t/scikit-learn that referenced this pull request Sep 2, 2026
@jeremiedbb jeremiedbb mentioned this pull request Sep 8, 2026
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants