Forwarded from Daniil Gentili
Psalm v6 Deep Dive: Copy-on-Write + dynamic task dispatching
Psalm is one of the biggest and most powerful PHP Static analysis tools, featuring exclusive features like security analysis.
In Psalm 6.1, I implemented a major refactoring of multithreaded mode (automatically enabled on Linux/Mac OS) based on amphp/parallel, which greatly reduced analysis speeds!
But why was it so effective? To understand, one must first understand that in the vast majority of PHP multithreaded analysis tools, jobs are distributed statically between threads on startup, which means that towards the end of the analysis, a lot of workers just sit there doing nothing, just waiting for the other workers processing bigger and heavier files to finish.
However, the new multithreaded mode now allows Psalm to dynamically distribute jobs to workers immediately, as soon as they finish processing their current task, reducing idle worker time and maximizing CPU usage, thus reducing the overall runtime!
Implementation wasn't as easy as just plugging in amphp/parallel, because Psalm relies heavily on the copy-on-write semantics of fork(): indeed, Psalm's multithreaded mode was quite fast even before the refactoring because it doesn't have to copy all type information to all workers when spawning them, as when workers are spawned using the fork() syscall, the entire memory is not copied to the forked process.
Instead, it is copied only when a memory page is modified by the forked process, which means that unless workers start modifying large amounts of type information (which usually happens pretty rarely, as most of that data is immutable after Psalm's scan phase), most of the memory is not copied, leading to large performance improvements.
amphp/parallel does not support using fork() to spawn workers out of the box, however I managed to add support using a custom context class (taking care to avoid some edge cases around reused file descriptors, which can cause issues with the event loop).
The maintainer of amphp was kind enough to begin integration of Psalm's fork context inside of parallel itself after I pinged him, which means amphp users will soon be able to make use of Psalm's fork context to improve worker spawning performance with copy-on-write fork() semantics.
This release also adds an additional check to ensure VM overcommitting (the feature which allows copy-on-write optimizations) is enabled in the OS when running Psalm, by ensuring that the
~~~
This post is the first of a series of technical deep dives into Psalm v6's performance improvements, which will be released over the next weeks, subscribe to @danog_blog to always stay up to date on the latest Psalm news and developments!
Psalm is one of the biggest and most powerful PHP Static analysis tools, featuring exclusive features like security analysis.
In Psalm 6.1, I implemented a major refactoring of multithreaded mode (automatically enabled on Linux/Mac OS) based on amphp/parallel, which greatly reduced analysis speeds!
But why was it so effective? To understand, one must first understand that in the vast majority of PHP multithreaded analysis tools, jobs are distributed statically between threads on startup, which means that towards the end of the analysis, a lot of workers just sit there doing nothing, just waiting for the other workers processing bigger and heavier files to finish.
However, the new multithreaded mode now allows Psalm to dynamically distribute jobs to workers immediately, as soon as they finish processing their current task, reducing idle worker time and maximizing CPU usage, thus reducing the overall runtime!
Implementation wasn't as easy as just plugging in amphp/parallel, because Psalm relies heavily on the copy-on-write semantics of fork(): indeed, Psalm's multithreaded mode was quite fast even before the refactoring because it doesn't have to copy all type information to all workers when spawning them, as when workers are spawned using the fork() syscall, the entire memory is not copied to the forked process.
Instead, it is copied only when a memory page is modified by the forked process, which means that unless workers start modifying large amounts of type information (which usually happens pretty rarely, as most of that data is immutable after Psalm's scan phase), most of the memory is not copied, leading to large performance improvements.
amphp/parallel does not support using fork() to spawn workers out of the box, however I managed to add support using a custom context class (taking care to avoid some edge cases around reused file descriptors, which can cause issues with the event loop).
The maintainer of amphp was kind enough to begin integration of Psalm's fork context inside of parallel itself after I pinged him, which means amphp users will soon be able to make use of Psalm's fork context to improve worker spawning performance with copy-on-write fork() semantics.
This release also adds an additional check to ensure VM overcommitting (the feature which allows copy-on-write optimizations) is enabled in the OS when running Psalm, by ensuring that the
vm.overcommit_memory kernel setting is always set to 1. ~~~
This post is the first of a series of technical deep dives into Psalm v6's performance improvements, which will be released over the next weeks, subscribe to @danog_blog to always stay up to date on the latest Psalm news and developments!
Daniil Gentili's blog
Psalm v6 Deep Dive: Copy-on-Write + dynamic task dispatching - Daniil Gentili's blog
A deep dive into Psalm 6.1's performance improvements, powered by CoW optimizations, and dynamic task dispatching with amphp/parallel!
🔥7
Small overview of the new psalm-review tool I added in Psalm 6.3: https://blog.daniil.it/2025/05/18/psalm-review-tool-in-psalm-6-3/
Daniil Gentili's blog
Psalm review tool in Psalm 6.3 - Daniil Gentili's blog
Introducing the new psalm-review tool!
👍4❤1
Forwarded from Daniil Gentili
Psalm is one of the biggest and most powerful PHP Static analysis tools, featuring exclusive features like security analysis, and in Psalm 6.9, an official, hyperoptimized Docker image was introduced.
Psalm’s docker image uses a custom build of PHP built from scratch with a custom deepbind patch and the jemalloc allocator, running Psalm +30% faster on average than normal PHP (+50% faster if comparing to PHP without opcache installed).
Recently, my deepbind patch was also merged into PHP and will be available to all users (even those not using the Docker image) in PHP 8.5!
To use it right now, on PHP 8.4, simply run:
Issues due to missing extensions can be fixed by enabling them in psalm.xml and/or requiring them in composer.json, see here for more info.
Extensions not stubbed by Psalm itself (and thus not available as a psalm config option) may be stubbed using traditional PHP stubs.
Also posted on the blog: https://blog.daniil.it/2025/07/10/official-psalm-docker-image/
Psalm’s docker image uses a custom build of PHP built from scratch with a custom deepbind patch and the jemalloc allocator, running Psalm +30% faster on average than normal PHP (+50% faster if comparing to PHP without opcache installed).
Recently, my deepbind patch was also merged into PHP and will be available to all users (even those not using the Docker image) in PHP 8.5!
To use it right now, on PHP 8.4, simply run:
docker run -v $PWD:/app --rm -it ghcr.io/danog/psalm:latest /composer/vendor/bin/psalm --no-cache
Issues due to missing extensions can be fixed by enabling them in psalm.xml and/or requiring them in composer.json, see here for more info.
Extensions not stubbed by Psalm itself (and thus not available as a psalm config option) may be stubbed using traditional PHP stubs.
Also posted on the blog: https://blog.daniil.it/2025/07/10/official-psalm-docker-image/
GitHub
Do not use RTLD_DEEPBIND if dlmopen is available by danog · Pull Request #18612 · php/php-src
This pull request disables usage of RTLD_DEEPBIND if dlmopen is available.
Context:
Php8.1 with alternative malloc allocators #10670 - The issue this PR fixes, using RTLD_DEEPBIND with custom allo...
Context:
Php8.1 with alternative malloc allocators #10670 - The issue this PR fixes, using RTLD_DEEPBIND with custom allo...
🔥3
Forwarded from Daniil Gentili
Also announcing the public beta of Psalm v7!
Psalm v7 brings huge performance improvements to security analysis, up to 10x thanks to a full refactoring of both the internal representation of taints, and optimization of the graph resolution logic.
A major new feature was also added: combined analysis!
Combined analysis, enabled by default in Psalm v7, allows running normal analysis, security analysis and dead code analysis all at the same time, within a single run, greatly reducing overall runtimes!
Future beta releases will also enable taint analysis by default, given that now it can be run alongside normal analysis.
Psalm v7 also brings performance improvements to dead code analysis, and fixes for list types.
Even more performance improvements and new features will be released soon!
Psalm v7 brings huge performance improvements to security analysis, up to 10x thanks to a full refactoring of both the internal representation of taints, and optimization of the graph resolution logic.
A major new feature was also added: combined analysis!
Combined analysis, enabled by default in Psalm v7, allows running normal analysis, security analysis and dead code analysis all at the same time, within a single run, greatly reducing overall runtimes!
Future beta releases will also enable taint analysis by default, given that now it can be run alongside normal analysis.
Psalm v7 also brings performance improvements to dead code analysis, and fixes for list types.
Even more performance improvements and new features will be released soon!
❤5🔥2
Psalm 7.0.0-rc12 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc12
GitHub
Release 7.0.0-rc12 · vimeo/psalm
What's Changed
Fixes
Analyse traits declared inside a namespace by @ybai08 in #12498
[6.x] Fix: mixed variable stays narrowed after an if with an && instanceof condition by @alies-dev ...
Fixes
Analyse traits declared inside a namespace by @ybai08 in #12498
[6.x] Fix: mixed variable stays narrowed after an if with an && instanceof condition by @alies-dev ...
👍1
Psalm 6.20.3 has been released: https://github.com/vimeo/psalm/releases/tag/6.20.3
GitHub
Release 6.20.3 · vimeo/psalm
What's Changed
Fixes
[6.x] Fix a ~600 MiB memory regression from 6.19.2 (nullsafe chain state on every node) by @jarstelfox in #12493
Internal changes
[6.x] Install PECL extensions with PIE ...
Fixes
[6.x] Fix a ~600 MiB memory regression from 6.19.2 (nullsafe chain state on every node) by @jarstelfox in #12493
Internal changes
[6.x] Install PECL extensions with PIE ...
🔥3👍1
Psalm 7.0.0-rc13 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc13
GitHub
Release 7.0.0-rc13 · vimeo/psalm
What's Changed
Features
Let plugins give an expression the taint nodes of a list, without their internal ids by @danog in #12535
Fixes
Keep what is set under a name not known in the class of...
Features
Let plugins give an expression the taint nodes of a list, without their internal ids by @danog in #12535
Fixes
Keep what is set under a name not known in the class of...
Psalm 7.0.0-rc14 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc14
GitHub
Release 7.0.0-rc14 · vimeo/psalm
What's Changed
Features
Add a stub for the zstd extension by @danog in #12539
Fixes
Count tmpfile() as io, like tempnam() by @danog in #12537
Declare the capabilities of psr/log's Logger...
Features
Add a stub for the zstd extension by @danog in #12539
Fixes
Count tmpfile() as io, like tempnam() by @danog in #12537
Declare the capabilities of psr/log's Logger...
Psalm 7.0.0-rc15 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc15
GitHub
Release 7.0.0-rc15 · vimeo/psalm
What's Changed
Fixes
Resolve ArrayAccess offset types in isset()/empty(), don't report risky truthiness of mixed unions by @danog in #12540
Full Changelog: 7.0.0-rc14...7.0.0-rc15
Fixes
Resolve ArrayAccess offset types in isset()/empty(), don't report risky truthiness of mixed unions by @danog in #12540
Full Changelog: 7.0.0-rc14...7.0.0-rc15
👍1
Psalm 7.0.0-rc16 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc16
GitHub
Release 7.0.0-rc16 · vimeo/psalm
What's Changed
Fixes
Return the list or array the webmozart/assert all* assertions are given by @danog in #12541
Infer non-empty-string for implode() of array shapes with a non-empty piece by ...
Fixes
Return the list or array the webmozart/assert all* assertions are given by @danog in #12541
Infer non-empty-string for implode() of array shapes with a non-empty piece by ...
Psalm 7.0.0-rc17 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc17
GitHub
Release 7.0.0-rc17 · vimeo/psalm
What's Changed
Internal changes
[7.x] Perf: don't deep-scan Composer's autoloader machinery by @alies-dev in #12556
[7.x] Perf: append array literal item types in place (O(N²) -> O(...
Internal changes
[7.x] Perf: don't deep-scan Composer's autoloader machinery by @alies-dev in #12556
[7.x] Perf: append array literal item types in place (O(N²) -> O(...
Psalm 7.0.0-rc19 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc19
GitHub
Release 7.0.0-rc19 · vimeo/psalm
What's Changed
Features
Require the psalm/* plugins and enable them, and the webmozart/assert plugin, for the libraries a project uses by @danog in #12568
Full Changelog: 7.0.0-rc18...7.0.0-rc19
Features
Require the psalm/* plugins and enable them, and the webmozart/assert plugin, for the libraries a project uses by @danog in #12568
Full Changelog: 7.0.0-rc18...7.0.0-rc19
Psalm 7.0.0-rc20 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc20
GitHub
Release 7.0.0-rc20 · vimeo/psalm
What's Changed
Internal changes
Require psalm/plugin-laravel 4.x, the line built for Psalm 7.x by @alies-dev in #12570
Enable the Laravel plugin when composer.json requires orchestra/testbench...
Internal changes
Require psalm/plugin-laravel 4.x, the line built for Psalm 7.x by @alies-dev in #12570
Enable the Laravel plugin when composer.json requires orchestra/testbench...
For anyone thinking about the release frequency: Psalm will be switching to rolling releases starting from v7, with automated releases every hour or so if CI passes; this already applies now to rcs and betas
Psalm 6.20.4 has been released: https://github.com/vimeo/psalm/releases/tag/6.20.4 #psalm
GitHub
Release 6.20.4 · vimeo/psalm
What's Changed
Fixes
[6.x] Fix: class_alias() of an enum, interface, attribute, trait or type holder crashes with Could not get class storage by @alies-dev in #12518
[6.x] Fix: type imported t...
Fixes
[6.x] Fix: class_alias() of an enum, interface, attribute, trait or type holder crashes with Could not get class storage by @alies-dev in #12518
[6.x] Fix: type imported t...
❤1
Psalm 7.0.0-rc23 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc23 #psalm
GitHub
Release 7.0.0-rc23 · vimeo/psalm
What's Changed
Fixes
[6.x] Fix: class_alias() of an enum, interface, attribute, trait or type holder crashes with Could not get class storage by @alies-dev in #12518
[6.x] Fix: type imported t...
Fixes
[6.x] Fix: class_alias() of an enum, interface, attribute, trait or type holder crashes with Could not get class storage by @alies-dev in #12518
[6.x] Fix: type imported t...
Psalm 7.0.0-rc25 has been released: https://github.com/vimeo/psalm/releases/tag/7.0.0-rc25 #psalm
GitHub
Release 7.0.0-rc25 · vimeo/psalm
What's Changed
Features
Add an imagick extension stub by @alies-dev in #12720
Fixes
Give the closure of a Class::method string callable the signature of the method by @jirkace in #12708
Don&...
Features
Add an imagick extension stub by @alies-dev in #12720
Fixes
Give the closure of a Class::method string callable the signature of the method by @jirkace in #12708
Don&...