Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This implements snapshotting of the process's state. The snapshot can be restored in a subsequent request, to save initialization time.
This adds two functions:
Example usage:
What is restored
Internal objects
Internal objects don't need special support as long as they use only the zend heap (e.g. no native allocations, kernel resources). However they need to be marked as such. The current branch marks a few internal classes as safe because these are used in the symfony-demo benchmark.
snapshot_state()
will throw if an unsupported object is found.Snapshots are private to a php instance / process, so it may be possible to support objects with native allocations and kernel resources. In some case it may be acceptable that the state of the native allocation / kernel resource changes after the snapshot (e.g. for streams or database connection), but for others it might not (e.g. DOM objects), in which case we may need to separate/clone the resource.
Design
The basic idea is that we copy the heap to a separate buffer. Later, we restore the state by copying back the buffer to the old location. We don't have to copy/relocate individual zvals and other state: We just copy heap chunks. We then update the global symbol table.
Snapshotting
We make a copy of every heap chunk (2MiB each), and prevent the heap from releasing these chunks later (to reserve the address space).
We also copy symbol tables (variables, classes, functions, constants). Symbols are allocated either in the heap or in opcache SHM, so we only copy the hashtables, not the symbols themselves.
Restoring
When restoring, we just restore chunk copies to their old location, and add the old chunks back to the heap. We then restore the symbol tables.
Thoughts
Snapshots are private to each php instance, as the location of the heap is specific to each instance (this is important because when we restore a snapshot, we want to do so at the original memory location).
Sharing snapshots is possible if a fixed memory region is reserved in the parent process, like the opcache SHM. But this wouldn't work for ZTS, as each php instance needs separate regions anyway. Also, instance-private snapshots are convenient to support snapshotting of internal objects (see above).
One issue with raw-copying heap chunks is that some slots will be reported as leaks after a restore, in debug builds, but this is fixable.
A less brute-force approach would copy/relocate every individual zval/object/etc to a new heap separately, in a similar fashion as
zend_persist.c
. CoW objects such as arrays and strings could be moved to the native heap, to reduce restoring costs further. It would result in a smaller heap (reduces restoring cost). However, relocating would increase maintenance, as every internal object needs to know how to clone itself to a new location.Performance
Benchmark
/en/blog/
on the Symfony Demo appnprocs*2
threads/processesResults
PHP-FPM with snapshotting is slower than FrankenPHP. How much slower depends on the snapshot size and of when it's taken. Best results were obtained when taking a snapshot just after the first request:
Restoring the state takes about 14% of the time in this benchmark. This could possibly be reduced by a few percent.
Taking the snapshot just after booting the kernel has smaller improvements compared to baseline:
Initializing all container services before snapshot is slower than baseline due to the cost of restoring state.
More benchmarking / analysis is needed.