I have been wondering for days how PHP opcode caching works in my php-fpm setup. Several benchmark articles emerge by googling with keywords php+opcode+caching, yet the theory remains in the dark side. Finally I decided to dig into the source code of APC-3.1.9 and PHP-5.3.10, and here’s my findings.

To answer two questions:

  1. How opcode caching is hooked in
  2. How opcode cache is shared across fpm worker processes

The first question is explained well in TECHNOTES shipped with source code. Keep in mind that there are two kinds of caches: the opcode cache, a.k.a. file cache or system cache, and the user controlled cache, a.k.a. user cache. Both caches reside in the same memory segment under respective namespaces, and manipulated by different sets of cache updating functions, e.g.

apc_compile_file for opcode cache, and apc_add for user cache. Caching support in frameworks like CodeIgniter is obviously related to user cache, and probably you prefer to memcached, because APC cache can not be shared across hosts.

The opcode cache is additionally updated automatically when PHP scripts are compiled that match apc.filters and that allowed by apc.cache_by_default. When APC module’s intialization function, apc_module_init, is called, the standard PHP compiler previously registered to zend_compile_file, is replaced with the APC-provided wrapper, my_compile_file, which checks against opcode cache and, if necessary, compiles the script and updates the cache. So that’s the hook point. Opcode caching takes effect transparently when APC is installed and enabled.

1815 int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules)
1816 {

// sets zend_compile_file to standard PHP compiler compile_file
1888         zend_startup(&zuf, NULL TSRMLS_CC);

// calls apc_module_init, sets zend_compile_file to my_compile_file
2066         zend_startup_modules(TSRMLS_C);

2141 }

The second question is related to memory allocator, also explained in TECHNOTES, and fpm process manager. In short, the cache memory segment is managed by an memory allocator as an offset-based link list, on which a hash table is built holding cached objects. That memory segment is usually mmap‘ed, backed by POSIX shared memory objects, see shm_open(3), or annoymous file on your filesystem, depending on the apc.mmap_file_mask configuration option. Backing stores are created and then immediately unlinked, as a result automatically reclaimed after all referencing processes has exited. There is no identifier or something for workers to look up the memory segment, no need of, because all worker processes are fork‘ed from fpm master process, and that memory segment is mmap‘ed before fork‘ing thus inherited by all child processes. In case of your interest, PHP module initialization is at php_cgi_startup, which in turn calls php_module_startup, and fpm process manager later do forks in fpm_run.

Edit 2012-09-03 This post is for php-fpm only, Apache httpd guys may refer to FastCGI with a PHP APC Opcode Cache