PHP Conference Japan 2009

High Performance APC talk for 2009 »
Brian Shire

APCによる
ハイパフォーマンスの実現
Performance is sacrificed after a server
restart.  

The server is slammed with
requests while it attempts to cache files,
user variables, and spawn processes.

Gives users seemingly random and
unpredictible poor performance.
PHP
How "Compilation" works:
(the process of turning source code into opcodes)
Lexer
#line 5847 "Zend/zend_language_scanner.c"yy543:
    YYDEBUG(543, *YYCURSOR);        
    yych = *++YYCURSOR;        
    if (yych == 'N') goto yy553;        
    if (yych == 'n') goto yy553;        
    goto yy281;
yy544:        
    YYDEBUG(544, *YYCURSOR);        
    yych = *++YYCURSOR;      
    if (yych == 'O') goto yy545;        
    if (yych != 'o') goto yy281;
yy545:        
    YYDEBUG(545, *YYCURSOR);        
    yych = *++YYCURSOR;        
    if (yych == 'L') goto yy546;        
    if (yych != 'l') goto yy281;
yy546:        
    YYDEBUG(546, *YYCURSOR);        
    yych = *++YYCURSOR;        
    if (yych == 'E') goto yy551;        
    if (yych == 'e') goto yy551;        
    goto yy548;
yy547:        
    YYDEBUG(547, *YYCURSOR);        
    ++YYCURSOR;        
    YYFILL(1);        
    yych = *YYCURSOR;
Parser
Opcodes
Lexical Analysis of human readable
code into Lexicons that can form
syntax...
echo "Hello World!";
T_ECHO
T_STRING
Parsing of Lexicons into Opcodes
Validation of language syntax and use...
foreach_variable:
        variable            { zend_check_writable_variable(&$1); $$ = $1; } 
       |   '&' variable        { zend_check_writable_variable(&$2); $$ = $2;  $$.u.EA.type |= ZEND_PARSED_REFERENCE_VARIABLE; };

for_statement:        
     statement    |   ':' inner_statement_list T_ENDFOR ';';

foreach_statement:        
     statement    |   ':' inner_statement_list T_ENDFOREACH ';';

declare_statement:        
     statement    |   ':' inner_statement_list T_ENDDECLARE ';';
Operation Code instructs the direction
of the PHP virtual machine.  The interpreter
version of assembly code for CPUs.
<?php
$output = "Hello World!"
echo $output;
return;
<?php
$output = "Hello World"
if ($_GET['exclaim']) {
  $output .= '!';
} else {
  $output .= ".";
}
echo $output;
ASSIGN  !0  ‘Hello+World%21’ 
ECHO    !0 
RETURN  1 
ZEND_HANDLE_EXCEPTION
ASSIGN             !0, ‘HELLO+WORLD’ 
FETCH_R          GLOBAL $1, ‘_GET’ 
FETCH_DIM_R           $2, $1, ‘exclaim’ 
JMPZ                           $2, ->6 
ASSIGN_CONCAT      !0, ‘%21’ 
JMP                              ->7 
ASSIGN_CONCAT      !0, ‘.’ 
ECHO                          $0 
RETURN                     1 
ZEND_HANDLE_EXCEPTION 
Interpretation of the Opcodes by the
PHP VM.  Include or eval statements
trigger nested compilation and execution cycles.
ZEND_VM_HANDLER(73, ZEND_INCLUDE_OR_EVAL, 
CONST|TMP|VAR|CV, ANY)
{   
    zend_op *opline = EX(opline);    zend_op_array     
    *new_op_array=NULL;
    int return_value_used;
    zend_free_op free_op1;
    zval *inc_filename = GET_OP1_ZVAL_PTR(BP_VAR_R);
    zval tmp_inc_filename;
    zend_bool failure_retval=0;

    if (inc_filename->type!=IS_STRING) {
        tmp_inc_filename = *inc_filename;
        zval_copy_ctor(&tmp_inc_filename);
        convert_to_string(&tmp_inc_filename);
        inc_filename = &tmp_inc_filename;
    }

Normal Compilation
Lexer
Parser
APC Compilation
Source
Source File Not Cached or
Updated on Disk?
Copy Opcodes
for Execution
Opcodes
Execution
Store Opcodes
in APC Cache
APC
Installation   &  Configuration
http://pecl.php.net/packages/APC/
cvs -d :pserver:cvsread@cvs.php.net:/repository/ checkout pecl/apc
$ pecl install apc
Download via HTTP or CVS:
Automated PECL installation:
$ phpize && ./configure && make install
Build and Install:
- or -
; APC
apc.enable=1
apc.shm_size=400
apc.ttl=172800
apc.user_ttl=172800
apc.num_files_hint=500
apc.user_entries_hint=60000
apc.mmap_file_mask=/tmp/apc.XXXXXX
apc.file_update_protection=15
apc.write_lock=1
apc.stat=0

Begin with a simple configuration.

Increase file and user variable
hints for your application.

Verify that the shm_size setting is
enough memory for files and vars.

Install the apc.php to view cache 
statistics and contents.
Opcode Cache
Filesystem stat() calls...
$ strace -e trace=file httpd -X

stat("/www/html/test.php", {st_mode=S_IFREG|0644, st_size=710, ...}) = 0 
getcwd("/usr/local/apache/bin", 4095) = 41 
chdir("/www/html")                  = 0 
open("/www/html/includes/1.inc", O_RDONLY) = 7 
stat("/www/html/includes/1.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/2.inc", O_RDONLY) = 8 
stat("/www/html/includes/2.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/3.inc", O_RDONLY) = 9 
stat("/www/html/includes/3.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/4.inc", O_RDONLY) = 10 
stat("/www/html/includes/4.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/5.inc", O_RDONLY) = 11 
stat("/www/html/includes/5.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/6.inc", O_RDONLY) = 12 
stat("/www/html/includes/6.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/7.inc", O_RDONLY) = 13 
stat("/www/html/includes/7.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/8.inc", O_RDONLY) = 14 
stat("/www/html/includes/8.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/9.inc", O_RDONLY) = 19 
stat("/www/html/includes/9.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
open("/www/html/includes/10.inc", O_RDONLY) = 20 
stat("/www/html/includes/10.inc", {st_mode=S_IFREG|0644, st_size=16, ...}) = 0 
chdir("/usr/local/apache/bin") = 0

File paths need to be resolved to
real paths (resolve symbolic links).

Files must also be stat()'d to check
for modifications not yet cached.
PHP-5.2.x, apc.stat=1
apc.stat=0
Disable the check for any updates
to files on disk.

Big performance increase.

Requires server restart or manual
insertion of files to update code.
User Variable 
Cache
Binary Dumps
Ability to dump the file and variable user
caches to file, stream, or string.
Architecture specific, you can't transfer
files between different systems unless
they are identical.
Still "experimental".
Not intended as an obfuscation mechanism.
Cache Priming
To overcome this performance problem
we can prime the cache prior to accepting
requests...
apc_compile_file()
Ability to programatically insert a file 
into the cache.
Accepts a single filename or an array
of filenames.
Lazy Loading
Simple API to store PHP variables
apc_store($key, $value)
$value = apc_fetch($key)
Effective cache usage
Cache is server specific, so unless
users are "sticky" to a server user
specific data is not ideal due to
size constraints.
Site-wide data is the ideal data.
Think of APC as a giant array, store
individual values rather than large arrays
of values.  Currently values must be copied
from the cache to the local process, so size
matters.
Control!
Control the site behavior with APC variables.
Faster respones to enable, change, or disable 
features.
Better user experience, easier to push new
features with a large number of servers.
The Future...
APC-4.0
A major limitation of APC is that it does not
evict least-used items like memcache.
APC-4.0 is an experimental branch that adds:
Least Frequently Used (LFU) cache eviction.
Multiple caches, with individual configurations.

extension=apc.so

apc.enabled=1
apc.enable_cli=1

apc.file_caches = "file"
apc.user_caches = "primary, lfu"
apc.segments = "200M, 500M, 500M"
apc.coredump_unmap = "1, 2"
apc.mmap_file_mask=/tmp/apc.XXXXXX
apc.optimization=0
apc.include_once_override=0
apc.enable_binfiles=0
apc.binfiles_path="/Users/shire/www.bin"

apc.file.desc = "Primary File Cache"
apc.file.segment = 0
apc.file.gc_ttl = 3600
apc.file.expunge_method = "flush"
apc.file.entries_hint = 4096
apc.file.stat = 0
apc.file.ttl=172800
apc.file.cache_by_default = 1
apc.file.slam_defense = 0
apc.file.file_update_protection = 15
apc.file.max_file_size = 1Mapc.file.stat_ctime = 0
apc.file.write_lock = 1
apc.file.filters = ""
apc.file.lazy_functions=1
apc.file.lazy_classes=1

apc.primary.desc = "Primary Variable Cache"
apc.primary.segment = 1
apc.primary.gc_ttl = 3600
apc.primary.expunge_method = "lfu"
apc.primary.entries_hint = 1048576
apc.primary.ttl=0

apc.lfu.desc = "LFU Variable Cache"
apc.lfu.segment = 2
apc.lfu.gc_ttl = 3600
apc.lfu.expunge_method = "lfu"
apc.lfu.entries_hint = 1048576
apc.lfu.ttl=0

Abstract Extension API and Dependency Interface
GSoC Project
by Varuna Jayasiri
No-copy shared cache.
*under research*
Currently APC and other opcode caches must copy
opcode and other data from shared memory to a local
process when a file (opcodes) or a user variable is requested.
It may be possible to remove this cost, creating a huge cost 
savings for APC and likely other opcode caches.
Renders lazy loading unecessary!
Should have results in the next month or two.
George Schlossnagle
Daniel Cowgill 
Rasmus Lerdorf 
Gopal Vijayaraghavan 
Edin Kadribasic 
Ilia Alshanetsky 
Marcus Börger 
Sara Golemon 
Brian Shire
Performance
Locking
If you use SVN, CVS, rsync or
other similar tools to push code
*and* apc.stat=1, then you may
need to enable apc.stat_ctime=1.

This ensures that the creation
time rather than modification time
signifies file updates, as these
tools often backdate 
modification times...
EOF
questions?
Primary 
APC Developers:
Live in San Francisco.
Work for Facebook, Palo Alto.
and I Focus on APC & PHP stack performance.

email: shire@tekrat.com or shire@facebook.com
blog: http://tekrat.com/

Code
<?php

echo 'Hello World!';
Execution
$ strace -e trace=file httpd -X
stat("/www/html/test.php", {st_mode=S_IFREG|0644, st_size=710, ...}) = 0 
getcwd("/usr/local/apache/bin", 4095) = 41 
chdir("/www/html")                  = 0 
open("/www/html/includes/1.inc", O_RDONLY) = 7 
open("/www/html/includes/2.inc", O_RDONLY) = 8 
open("/www/html/includes/3.inc", O_RDONLY) = 9 
open("/www/html/includes/4.inc", O_RDONLY) = 10 
open("/www/html/includes/5.inc", O_RDONLY) = 11 
open("/www/html/includes/6.inc", O_RDONLY) = 12 
open("/www/html/includes/7.inc", O_RDONLY) = 13 
open("/www/html/includes/8.inc", O_RDONLY) = 14 
open("/www/html/includes/9.inc", O_RDONLY) = 19 
open("/www/html/includes/10.inc", O_RDONLY) = 20 
chdir("/usr/local/apache/bin") = 0 

PHP-5.2.x, apc.stat=0
apc_bindump_fifile("gzip://...")
apc_binload_fifile("gzip://...") 
API is simple, and supports streams:
PHP,
カンファレンス
日本、2009
Brian Shire
APC.php...
Copying opcodes out of shared memory causes
delays attributed to include() or require() calls.
Lazy Loading is an experimental patch to delay 
copy until a function or class is actually used.
This can significantly reduce include time for 
large codebases.
apc_compile_file($filename);
apc_compile_file($file_array);
Any PHP value can be stored, except for resources.
Objects have to be serialized, this is a significant
performance loss, so it's not recommended.
Data center A
Data center B
Web Server 1
Web Server 2
Web Server ...
Web Server 1
Web Server 2
Web Server ...
Central update script
spawns multiple HTTP 
requests to individual
servers.
Updates are made in the time
it takes for a HTTP Request.

Can be scaled by distributing to
per data center update scripts.
Internal Infrastructure
Internet/Users
User experiences
near immediate
changes in
functionality.
Normal enable/disable.
List of file and user caches
by name descriptions.
Segment sizes.
"file" cache configuration.
Which segment to use.
"primary" cache configuration.
"lfu" cache configuration.
LFU expunge method.
Develop functionality for PHP that will allow extensions to register a 
set of functions as a versioned API.


This could potentially allow for APC to export it's shared memory storage
and mechanisms to other extensions.
More information and updates:
http://wiki.php.net/gsoc/2009/api
Contact info...

Brian Shire: 

shire@tekrat.com, 
http://tekrat.com

Iterator, Iterator, Iterator...
$iterator = new APCIterator(APC_LFU, 
                                             '/some_key/',
                                             APC_ITER_KEY |
                                             APC_ITER_HITS |
                                             APC_ITER_MTIME);

foreach($iterator as $key=>$info) {    ...
}
Needed the ability to search for and browse cache
entries quickly and efficiently, up to many millions 
of entries.

The only way to do this was move searching facilities 
to the C extension, and browse by smaller chunks...

Loading comments...

Please log in to add your comment.

Report abuse

More presentations by Brian Shire