feat: way to only recompile changed files#2643
Conversation
91f4a13 to
8e317b6
Compare
|
PS: this is a very minor feat, so I'd be okay with it going into 1.6.1, what do you think? |
|
Why don't we just always advise ccache? That seems like the obvious way to do this, taking all kinds of subtleties into account, rather than adding unrelated code to pybind11. (I know that's putting it rather harshly; intentionally so, since I expect you have a good answer ;-) ) Same as with the |
|
CCache is a separate dependency, and not very officially supported on Windows. This is a simpler (and off-by-default) workaround that works everywhere, and enabling it doesn't affect "normal" users, as a non-editable install is in a new build folder anyway. It's also easy for us to include here, but hard for a user to inject. It's not trivial to include dependencies at setuptime, each method of doing so (PEP 518, setup_requires, submodules, copy-and-paste) has drawbacks and doesn't work everywhere (conda disables most of them, for example). IMO, pybind11 should offer reasonable assistance with users wanting to use setuptools, because that's a key part of writing a good pybind11 module. If, for example, we added |
That's true, so I'm not really opposed to it; you managed to convince me of
But ... pybind11 is already a dependency at setuptime? One that users either require with But, as I said, I don't oppose including it. To some degree this is just an additional feature/fix to the already existing (EDIT: I just find it weird that users would need to install pybind11 to use this feature.) |
|
My point was any external solution will significantly complicate usage from pybind11, where I expect most users will be. This makes life much easier for simple to intermediate pybind11 users (advanced users probably will set everything up for themselves anyway). |
|
Hi, just to chime in, @YannickJadoul you are right, of course, that this functionality and parallel builds should ideally come from setuptools, but that's unfortunately not the case. @henryiii made a good case against ccache and it is a rather small patch with a lot of convenience value, so not really adding bloat to pybind11. I think everyone here would be happy to rip this out again once this functionality is available somewhere upstream. I missed why |
|
I have tested this locally and verified it works with Remember, it will look like this: # Use the environment variable NPY_NUM_BUILD_JOBS
ParallelCompile("NPY_NUM_BUILD_JOBS", only_changed=True).install()
# or
ParallelCompile("NPY_NUM_BUILD_JOBS", lazy=True).install()Once this is out of context (not in this thread), I could see the latter line being misconstrued as some setting on the parallel method rather than just only recompiling changed files. These were/are the suggested names:
|
True, or some independent plugin. E.g., I don't see why this couldn't be interesting for Cython project. And since our docs suggest vendoring the file or using
Agreed; I see the ccache argument, yes (though ccache still seems to be the correct way to set something up on the long term, to me?), and indeed, it's just tweaking And yes, |
ParallelCompile is already available (in a less configurable form) in NumPy, which Cython users have to have at compile time anyway, while pybind11 users do not. Agree that eventually we might also make then available in a separate package, but that will complicate usage here. We probably would want to follow pip's method of including dependencies - it has dozens of dependencies, but they vendor them in - probably with git subtree or something similar (submodules have drawbacks for something so integral as pybind11). |
Sorry, I misunderstood how this works, I thought I have to add |
|
So I think we are around 2-3 votes for |
|
I've noticed that NumPy does this by default: https://github.com/numpy/numpy/blob/cfee2df31bdc127c345894aee594cbcbb8cddd0e/numpy/distutils/ccompiler.py#L303 Should we consider changing the default from This check we have is not recursive; that is, we don't look up the headers this file depends on to see if those changed. For many pybind11 projects, this is fine - there sometimes aren't headers. Maybe it should remain optional, so that projects that aren't worried about headers changing can turn it on, but some projects may want it off? |
|
I've restructured this quite a bit. With the observation that this is not recursive (it can't tell if you changed a header file), I've pulled the check function out and instead provided a way for users to add their own, with the "simple" one as a suggestion. This a) doesn't assume anything about a user's project structure, b) keeps the code that we provide simple, and c) keeps us from falling down a rabbit hole if users start complaining about changing a header and not having it show up when they rebuild. Many projects are probably fine and mostly .cpp files, but not all. As a counter-example, boost-histogram has more pybind11 code in headers than it does in .cpp files. New usage: from pybind11.setup_helpers import ParallelCompile
class SmartCompile(ParallelCompile):
@staticmethod
def needs_recompile(obj, src):
return os.stat(obj).st_mtime < os.stat(src).st_mtime
SmartCompile("NPY_NUM_BUILD_JOBS").install()Note that in my original reasoning I mentioned that one of the key reasons was that users couldn't modify ParallelCompile to add this. Now they can. :) |
|
Hopefully this will make @YannickJadoul happier, and @HDembinski if you could "review and approve" if you like it, that would be helpful too. :) (or, of course, you could make suggestions, etc.) |
I wasn't per se unhappy about this PR ;-) Especially not about this part, because I didn't actually notice the recursive/header issue. Two suggestions:
Apart from that, sure, go ahead and merge, as far as I'm concerned :-) |
Sure, that would be useful, I think.
I thought about it when designing this - either way works, I slightly prefer the extra structure provided by subclassing (plus, that way we could provide a single import "SimpleIncrementalCompile" instead of having to get and use a second import for the function to pass it in. But no strong preference here. @HDembinski do you have an opinion? The "import" version would have looked like this: from pybind11.setup_helpers import ParallelCompile, simple_recompile
# Or, custom function:
# def simple_recompile(obj, src):
# return os.stat(obj).st_mtime < os.stat(src).st_mtime
ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile = simple_recompile).install() |
Yep, something like that is what I was thinking! No strong opinion either way, though (this is slightly more cumbersome, but also avoids subclassing, so potentially a slight increase in future combinatoriality?). |
I favor the "pass a callable" approach, because of minimal coupling and minimal design. The function is independent of the class. Requiring me to subclass something to add a staticmethod is not a minimal elegant design. Subclassing is the preferred solution only if it is conceivable that the callable may need to access privates of |
I also like |
|
Okay, you've convinced me - I've pushed a free function version. |
YannickJadoul
left a comment
There was a problem hiding this comment.
Looks pretty clean!
34dcc79 to
8c8409e
Compare
Ccache spelling noted by @YannickJadoul
| Keep in mind that Pip will not even attempt to rebuild if it thinks it has | ||
| already built a copy of your code, which it deduces from the version number. | ||
| One way to avoid this is to use [setuptools_scm]_, which will generate a | ||
| version number that includes the number of commits since your last tag and a | ||
| hash for a dirty directory. Another way to force a rebuild is purge your cache | ||
| or use Pip's ``--no-cache-dir`` option. |
There was a problem hiding this comment.
FYI, I've just added this section, as this is likely a common issue related to caching. @YannickJadoul
|
I am not an official reviewer, but for the record, it looks all good to me, thank you for the great work, @henryiii |
|
I think we have enough approvals, then. Thanks everyone! |
Description
Adding a "cache" feature that assists in development. If a source has not changed since the file has been created, you can just reuse the old compile. Opt-in, and really only affects development builds (
pip install -e .or similar), since otherwise everything is built in a clean, new directory. Requested by @HDembinski for the HEP/Astro project iMinuit. I've based it on the existing code there, and verified that it works on boost-histogram (thepython_exampleonly has one file, which makes it rather uninformative for testing this). Added notes in the docs about CCache, too.Suggested changelog entry:
* Added ``needs_recompile`` optional function to the ParallelCompiler helper, to allow a recompile to be skipped based on a user-defined function.