Skip to content

Enable in-memory OPcache for PHP 8.1+ to reduce warm request latency #3617

@chubes4

Description

@chubes4

Summary

Playground currently enables OPcache, but generated php.ini forces it into file-cache-only mode:

opcache.enable = 1
opcache.enable_cli = 1
opcache.file_cache = /internal/shared/opcache
opcache.file_cache_only = 1

In local A/B testing, opcache.file_cache_only = 1 leaves warm WordPress/PHP requests paying a large per-request latency cost. Enabling the normal in-memory OPcache path for PHP 8.1+ while preserving file-cache-only mode for PHP 7.4 and 8.0 reduced warm request latency by roughly 10x for REST routes on current/default runtimes.

Public context

The current repo default/recommended PHP version is 8.3 (RecommendedPHPVersion), while @php-wasm/universal supports 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, and 8.5.

Proposed shape

Avoid a global flip. A global file_cache_only = 0 crashed PHP 7.4 and 8.0 in local testing. The safer shape is version-gated:

const useInMemoryOpcache =
	runtime.phpVersion.major > 8 ||
	(runtime.phpVersion.major === 8 && runtime.phpVersion.minor >= 1);

'opcache.file_cache_only = ' + (useInMemoryOpcache ? '0' : '1')

This keeps conservative file-cache-only behavior for PHP 7.4 and 8.0, while enabling in-memory OPcache for PHP 8.1+.

A/B results

Same machine, same existing WordPress site, same probe, current behavior vs gated candidate. Values are median warm samples after initial warmup.

PHP Current mode Candidate mode /wp-json/ current /wp-json/ candidate Speedup / current / candidate Speedup
7.4 file-only file-only 307.1ms 304.6ms 1.0x 367.2ms 375.9ms 1.0x
8.0 file-only file-only 299.7ms 287.3ms 1.0x 350.6ms 341.7ms 1.0x
8.1 file-only memory + file 313.5ms 26.1ms 12.0x 374.7ms 75.1ms 5.0x
8.2 file-only memory + file 334.1ms 30.0ms 11.1x 396.6ms 89.5ms 4.4x
8.3 file-only memory + file 310.5ms 28.1ms 11.0x 387.7ms 83.2ms 4.7x
8.4 file-only memory + file 314.4ms 28.4ms 11.1x 376.8ms 79.9ms 4.7x
8.5 file-only memory + file 357.4ms 23.5ms 15.2x 405.1ms 64.2ms 6.3x

For PHP 8.1+, opcache_get_status(false) reported opcache_enabled: true, cached scripts, and a hit rate around 66-67% in the probe. For PHP 7.4 and 8.0 the gated candidate kept file_cache_only = 1, matching current behavior.

Failure cutoff evidence

A global opcache.file_cache_only = 0 was tested first. It crashed on PHP 7.4 and PHP 8.0 with:

Error: PHP.run() failed with exit code -2

=== Stdout ===

=== Stderr ===

The gated PHP 8.1+ candidate avoided this by keeping file-cache-only mode for PHP versions older than 8.1.

Freshness check

For PHP 8.1 through 8.5, a direct edited-script freshness probe passed:

first request: v1
second request: v1
after immediate file edit: v2
delayed request: v2

This suggests file_cache_only = 0 did not introduce obvious stale-code behavior in the tested PHP 8.1+ range.

Targeted validation run

The gated candidate passed these targeted checks locally:

npx nx test-group-5-asyncify php-wasm-node --testFiles=php-opcache.spec.ts
# 6 tests passed

npx nx test-group-5-jspi php-wasm-node --testFiles=php-opcache.spec.ts
# 6 tests passed

npx nx test-group-1-asyncify php-wasm-node --testFiles=php-request-handler.spec.ts
# 411 tests passed

npx nx typecheck php-wasm-node
# passed

npx nx lint php-wasm-node
# passed

Why this matters

This affects the current/default Playground PHP path (8.3) and newer supported versions. For modern WordPress usage, the current file-cache-only mode appears to impose a large warm request tax on every PHP/REST request after the site is already running.

The version-gated change keeps older supported runtimes conservative while removing the unnecessary tax from PHP 8.1+.

Remaining validation before merging

  • Browser Playground validation, not only Node/CLI.
  • Longer memory soak for PHP 8.1+.
  • More edit freshness scenarios, especially mounted plugin/theme files.
  • Runtime rotation coverage.
  • A wp-admin/editor navigation workload with the gated candidate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions