I took a close look at how Debian builds the Python interpreter,
because I noticed it ran substantially faster than the one in nixpkgs
and I was curious why.
One thing that I found made a material difference in performance was
this pair of linker flags (passed to the compiler):
-Wl,-O1 -Wl,-Bsymbolic-functions
In other words, effectively the linker gets passed the flags:
-O1 -Bsymbolic-functions
Doing the same thing in nixpkgs turns out to make the interpreter
run about 6% faster, which is quite a big win for such an easy
change. So, let's apply it.
---
I had not known there was a `-O1` flag for the *linker*!
But indeed there is.
These flags are unrelated to "link-time optimization" (LTO), despite
the latter's name. LTO means doing classic compiler optimizations
on the actual code, at the linking step when it becomes possible to
do them with cross-object-file information. These two flags, by
contrast, cause the linker to make certain optimizations within the
scope of its job as the linker.
Documentation is here, though sparse:
https://sourceware.org/binutils/docs-2.31/ld/Options.html
The meaning of -O1 was explained in more detail in this LWN article:
https://lwn.net/Articles/192624/
Apparently it makes the resulting symbol table use a bigger hash
table, so the load factor is smaller and lookups are faster. Cool.
As for -Bsymbolic-functions, the documentation indicates that it's a
way of saving lookups through the symbol table entirely. There can
apparently be situations where it changes the behavior of a program,
specifically if the program relies on linker tricks to provide
customization features:
https://bugs.launchpad.net/ubuntu/+source/xfe/+bug/644645https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=637184#35
But I'm pretty sure CPython doesn't permit that kind of trick: you
don't load a shared object that tries to redefine some symbol found
in the interpreter core.
The stronger reason I'm confident using -Bsymbolic-functions is
safe, though, is empirical. Both Debian and Ubuntu have been
shipping a Python built this way since forever -- it was introduced
for the Python 2.4 and 2.5 in Ubuntu "hardy", and Debian "lenny",
released in 2008 and 2009. In those 12 years they haven't seen a
need to drop this flag; and I've been unable to locate any reports
of trouble related to it, either on the Web in general or on the
Debian bug tracker. (There are reports of a handful of other
programs breaking with it, but not Python/CPython.) So that seems
like about as thorough testing as one could hope for.
---
As for the performance impact: I ran CPython upstream's preferred
benchmark suite, "pyperformance", in the same way as described in
the previous commit. On top of that commit's change, the results
across the 60 benchmarks in the suite are:
The median is 6% faster.
The middle half (aka interquartile range) is from 4% to 8% faster.
Out of 60 benchmarks, 3 come out slower, by 1-4%. At the other end,
5 are at least 10% faster, and one is 17% faster.
So, that's quite a material speedup! I don't know how big the
effect of these flags is for other software; but certainly CPython
tends to do plenty of dynamic linking, as that's how it loads
extension modules, which are ubiquitous in the stdlib as well as
popular third-party libraries. So perhaps that helps explain why
optimizing the dynamic linker has such an impact.
Without this flag, the configure script prints a warning at the end,
like this (reformatted):
If you want a release build with all stable optimizations active
(PGO, etc), please run ./configure --enable-optimizations
We're doing a build to distribute to people for day-to-day use,
doing things other than developing the Python interpreter. So
that's certainly a release build -- we're the target audience for
this recommendation.
---
And, trying it out, upstream isn't kidding! I ran the standard
benchmark suite that the CPython developers use for performance
work, "pyperformance". Following its usage instructions:
https://pyperformance.readthedocs.io/usage.html
I ran the whole suite, like so:
$ nix-shell -p ./result."$variant" --run '
cd $(mktemp -d); python -m venv venv; . venv/bin/activate
pip install pyperformance
pyperformance run -o ~/tmp/result.'"$variant"'.json
'
and then examined the results with commands like:
$ python -m pyperf compare_to --table -G \
~/tmp/result.{$before,$after}.json
Across all the benchmarks in the suite, the median speedup was 16%.
(Meaning 1.16x faster; 14% less time).
The middle half of them ranged from a 13% to a 22% speedup.
Each of the 60 benchmarks in the suite got faster, by speedups
ranging from 3% to 53%.
---
One reason this isn't just the default to begin with is that, until
recently, it made the build a lot slower. What it does is turn on
profile-guided optimization, which means first build for profiling,
then run some task to get a profile, then build again using the
profile. And, short of further customization, the task it would use
would be nearly the full test suite, which includes a lot of
expensive and slow tests, and can easily take half an hour to run.
Happily, in 2019 an upstream developer did the work to carefully
select a more appropriate set of tests to use for the profile:
https://github.com/python/cpython/commit/4e16a4a31https://bugs.python.org/issue36044
This suite takes just 2 minutes to run. And the resulting final
build is actually slightly faster than with the much longer suite,
at least as measured by those standard "pyperformance" benchmarks.
That work went into the 3.8 release, but the same list works great
if used on older releases too.
So, start passing that --enable-optimizations flag; and backport
that good-for-PGO set of tests, so that we use it on all releases.
Some PECLs depend on other PECLs and, like internal PHP extension
dependencies, need to be loaded in the correct order. This makes this
possible by adding the argument "peclDeps" to buildPecl, which adds
the extension to buildInputs and is treated the same way as
internalDeps when the extension config is generated.
@the-kenny did a good job in the past and is set as maintainer in many package,
however since 2017-2018 he stopped contributing. To create less confusion
in pull requests when people try to request his feedback, I removed him as
maintainer from all packages.
This should enable (manual) building of RPMs from python projects using
the `python setup.py bdist_rpm` command on systems where `rpmbuild` is
not located in `/usr/bin/`. (e.g. NixOS)
The discovery of the rpmbuild command was fixed upstream in Python 3.8,
so this commit backports the relevant patch to our currently supported
Python 3 versions.
Fixes: #85204
At the moment, using .withExtensions on a PHP derivation will
produce something which can't be used inside an
environment.systemPackages array, because outputsToInstall refers
to an output which doesn't exist on the final derivation.
Instead, override it back to just containing the single output
"out".
Also passthrough the meta of the package to have description,
homepage, license, maintainers and other metadata passed through to
the commonly used attribute.
Instead of using two different php packages in php-packages.nix, one
wrapper and one unwrapped, simply use the wrapper and use its
"unwrapped" attribute when necessary. Also, get rid of the packages
and extensions attributes from the base package, since they're no
longer needed.
Since the introduction of php.unwrapped there's no real need for the
phpXXbase attributes, so let's remove them to lessen potential
confusion and clutter. Also update the docs to make it clear how to
get hold of an unwrapped PHP if needed.
Some extensions depend on other extensions. Previously, these had to
be added manually to the list of included extensions, or we got a
cryptic error message pointing to strings-with-deps.nix, which wasn't
very helpful. This makes sure all required extensions are included in
the set from which textClosureList chooses its snippets.
Rework withExtensions / buildEnv to handle currently enabled
extensions better and make them compatible with override. They now
accept a function with the named arguments enabled and all, where
enabled is a list of currently enabled extensions and all is the set
of all extensions. This gives us several nice properties:
- You always get the right version of the list of currently enabled
extensions
- Invocations chain
- It works well with overridden PHP packages - you always get the
correct versions of extensions
As a contrived example of what's possible, you can add ImageMagick,
then override the version and disable fpm, then disable cgi, and
lastly remove the zip extension like this:
{ pkgs ? (import <nixpkgs>) {} }:
with pkgs;
let
phpWithImagick = php74.withExtensions ({ all, enabled }: enabled ++ [ all.imagick ]);
phpWithImagickWithoutFpm743 = phpWithImagick.override {
version = "7.4.3";
sha256 = "wVF7pJV4+y3MZMc6Ptx21PxQfEp6xjmYFYTMfTtMbRQ=";
fpmSupport = false;
};
phpWithImagickWithoutFpmZip743 = phpWithImagickWithoutFpm743.withExtensions (
{ enabled, all }:
lib.filter (e: e != all.zip) enabled);
phpWithImagickWithoutFpmZipCgi743 = phpWithImagickWithoutFpmZip743.override {
cgiSupport = false;
};
in
phpWithImagickWithoutFpmZipCgi743
Make buildEnv take earlier overridden values into account by
forwarding all arguments (a merge of generic's arguments, all previous
arguments and the current arguments) to the next invocation of
buildEnv.
Make all arguments to a PHP build overridable; i.e, both configuration
flags, such as valgrindSupport, and packages, such as valgrind:
php.override { valgrindSupport = false; valgrind = valgrind-light; }
This applies to packages built by generic and buildEnv/withExtensions;
i.e, it works with both phpXX and phpXXBase packages.
The following changes were also made to facilitate this:
- generic and generic' are merged into one function
- generic now takes all required arguments for a complete build and
is meant to be called by callPackage
- The main function called from all-packages.nix no longer takes all
required arguments for a complete build - all arguments passed to it
are however forwarded to the individual builds, thus default
arguments can still be overridden from all-packages.nix
This implements the override pattern for builds done with buildEnv, so
that we can, for example, write
php.override { fpmSupport = false; }
and get a PHP package with the default extensions enabled, but PHP
compiled without fpm support.
Also passthrough the meta of the package to have description,
homepage, license, maintainers and other metadata passed through to
the commonly used attribute.
This is a better name since we have multiple 64-bit things that could
be referred to.
LP64 : integer=32, long=64, pointer=64
ILP64 : integer=64, long=64, pointer=64
This makes packages use lapack and blas, which can wrap different
BLAS/LAPACK implementations.
treewide: cleanup from blas/lapack changes
A few issues in the original treewide:
- can’t assume blas64 is a bool
- unused commented code
This is to fix a regression in upstream Racket packaging, the upstream
bug tracking this is here:
https://github.com/racket/racket/issues/3046
When the bug is fixed this workaround will be unnecessary.
mkDerivation uses the dev output in buildInputs if it exits, hence the
php-with-extensions package was never built or put into the path of
packages dependent on it during build. With this fix, the php packages
built with buildEnv or withExtensions don't have any dev outputs;
packages which need the dev output can refer to the phpXXbase packages
instead.