performance tuning in php
DESCRIPTION
TRANSCRIPT
Anatomy of PHP
PHP File
Zend Compiler
Execution at Zend Engine
● This cycle happens for all included files not only for main() script.
● Generally most of the time consumption take place at Compiler.
End User
Opcodes 0 EXT_STMT
1 ASSIGN !0, 'Enjoy+the+PHPCamp%2C+'
2 EXT_STMT
3 ASSIGN !1, 'Jignesh'
4 EXT_STMT
5 ZEND_ISSET_ISEMPTY_VAR 1 ~2 'name'
6 JMPZ ~2, ->11
7 EXT_STMT
8 CONCAT ~3 !0, !1
9 ASSIGN !2, ~3
10 JMP ->14
11 EXT_STMT
12 CONCAT ~5 !0, 'human'
13 ASSIGN !2, ~5
14 EXT_STMT
15 ECHO !2
16 EXT_STMT
17 RETURN 1
18* ZEND_HANDLE_EXCEPTION
<?php
$greet = 'Enjoy the PHPCamp, ';
$name = 'Jignesh';
if(isset($name)){
$say = $greet . $name;
}else{
$say = $greet . 'human';
}
echo $say;
?>
Opcodes generated using Vulcan Logic Disassembler (VLD) by Derick Rethans. http://pecl.php.net/packages/VLD/
Opcode/Compiler cache● Each PHP script (also for included files)
compiled only once and store into cache● during the next request it will not compile
again but directly pick it up from cache for execution
● reduce file I/O (stats calls) as it is being executed from the cache(memory) rather than disk
● the fastest and easiest solution for faster execution
Opcode/Compiler cache
hit/store
fetch
opcodes
Zend Compiler
PHP File
Parse/Execute Opcodes
End User
OpcodeCacher
Cache
APC● Alternate PHP Cache● A free, open source and robust framework
for caching and optimizing PHP intermediate code.
● maintained by core PHP developers● users
Yahoo!
Wikipedia and many more
Installation$pecl install APC
OR Source Compilation
#wget http://pecl.php.net/get/APC
#tar -zxvf APC-3.0.x.tar.gz
#cd APC-3.0.x
#phpize
#./configure
#make
#make install
#cp modules/apc.so /full/path/to/ext/apc.so
Windows user
Download it from : http://pecl4win.php.net/
Enable APC● edit php.in : extension=apc.so● restart apache
Web console to monitor APC Info
copy apc.php from apc source directory to document root
.ini directives● apc.shm_size = 30 (in MB)● apc.stat = 1 (by default)
- check if files has been modified on every request
- if apc.stat = 0, requires restart webserver on update or
apc_clear_cache()
- use include('/webroot/conf.php') instead of include('conf.php')
● apc.filters = <regex> a comma separated POSIX regex. default value is “-”
● apc.cache_by_default = true false - files are only cached if matched by a positive filter
more .ini directives
– apc.ttl = 0 (by default) Time to Live in cache - seconds – apc.write_lock = 1 (by default) only one process at a time instead of waiting on a lock– apc.num_files_hint = 1000 (by default) maximum number of files expect to be stored in memory– apc.max_file_size = 1M (by default) maximum file size that will be allowed in the apc cache – apc.file_update_protection=1(protect for 2 sec)– apc.include_once_override = 0 (by default)
APC user cache● bool apc_add(string $key, mixed $var [, int $ttl ])● bool apc_store(string $key, mixed $var [, int $ttl ])● mixed apc_fetch(string $key)● bool apc_delete(string $key)
.ini settings
apc.user_ttl = 0
apc.user_entries_hint = 4096
page cache with APC<?php
$key = "PAGE::".$_SERVER['REQUEST_URI'];
$pagecontents = apc_fetch($key);
if($pagecontents) {
echo $pagecontents;
exit;
}
ob_start();
// write your code
$pagecontents = ob_get_flush();
apc_store($key, $pagecontents);
?>
handling constants through APC● bool apc_define_constants(string $key, array $constants
[, bool $case_sensitive])● bool apc_load_constants(string $key [,bool $case_sensitive])<?php
$constants = array(
'NAME' => 'Jignesh',
'AGE' => 24,
'LOCATION' => 'Pune',
);
apc_define_constants('jignesh_info', $constants);
apc_load_constants('jignesh_info');
echo NAME,',',AGE,',',LOCATION;
?>
more functions● bool apc_compile_file ( string $filename )● bool apc_clear_cache ([ string $cache_type ] )● array apc_cache_info ([string $cache_type[,bool $limited ] ] )● array apc_sma_info ([ bool $limited ] )
other opcode cacher● XCache : http://xcache.lighttpd.net● IonCube: http://www.ioncube.com/sa_encoder.php ● Zend Platform: http://www.zend.com/en/products/platform/ ● Turck MMCache:
http://turck-mmcache.sourceforge.net/index_old.html ● EAccelerator: http://www.eaccelerator.net/
Benchmarking
Apache Bench
‣ ab utility bundled with Apache
http_load
‣ http://www.acme.com/software/http_load/
Siege
‣ http://www.joedog.org/JoeDog/Siege
Benchmark PEAR package
‣ http://pear.php.net/package/Benchmark
Apache Bench$ab -c 20 -n 50 <webserver_url>
Concurrency Level: 20Time taken for tests: 0.48937 secondsComplete requests: 50Non-2xx responses: 52Total transferred: 10504 bytesHTML transferred: 0 bytesRequests per second: 1021.72 [#/sec] (mean)Time per request: 19.575 [ms] (mean)Time per request: 0.979 [ms] (mean, across all concurrent requests)Transfer rate: 204.34 [Kbytes/sec] received
http_load
$http_load -rate 20 -seconds 5 <file_name>
99 fetches, 2 max parallel, 9702 bytes, in 5.00098 seconds
98 mean bytes/connection
19.7961 fetches/sec, 1940.02 bytes/sec
msecs/connect: 0.470556 mean, 0.635 max, 0.221 min
msecs/first-response: 1.40029 mean, 2.106 max, 1.175 min
HTTP response codes:
code 200 -- 99
Siege$siege -b -r 20 -t 5S <server_url>
Transactions: 677 hitsAvailability: 100.00 %Elapsed time: 5.84 secsData transferred: 5.39 MBResponse time: 0.13 secsTransaction rate: 115.92 trans/secThroughput: 0.92 MB/secConcurrency: 14.67Successful transactions: 677Failed transactions: 0Longest transaction: 0.99Shortest transaction: 0.04
serving static contents
● Apache's mod_php is designed for dynamic contents. ● Every Apache child used fair chunk of memory when it's executed with mod_php.● Serving static content like image, JS, CSS, HTML etc. with mod_php is more costly● Other option: lighttpd thttpd Boa
And nowadays S3 (Amazon Web Services)
Lets tune PHP code
include/require_once - once more
lstat64("/srv/www/htdocs/incl_test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/srv/www/htdocs/incl_test/a.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/srv/www/htdocs/incl_test/a.php", O_RDONLY) = 4
lstat64("/srv/www/htdocs/incl_test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/srv/www/htdocs/incl_test/a.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/srv/www/htdocs/incl_test/a.php", O_RDONLY) = 4
In PHP 5.2>= this will allow PHP to avoid opening the file twice.
file path problem– While it is convenient and easy to do include “foo.php” and have it work, internally.– it leads to significant overhead in system.– Whenever possible you should use full paths, that require no resolution in PHP.getcwd("/srv/www/htdocs/perf_demo", 4096) = 26
lstat64("/srv/www/htdocs/incl_test/a.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/srv/www/htdocs/incl_test/a.php", O_RDONLY) = 4
getcwd("/srv/www/htdocs/perf_demo", 4096) = 26
lstat64("/srv/www/htdocs/perf_demo/b.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/srv/www/htdocs/perf_demo/b.php", O_RDONLY) = 4
Generate system calls using $strace -e trace=file php <file_name.php>
include_path
my include_path is ".:/usr/share/php5:/usr/share/php5/PEAR:/srv/www/"
How to fix this?
sessions
● session.auto_start = 1 (by default 0)● PHP's session extension by default store
sessions in to each file within single directory.
● many files into one directory reduce access speed
● instead, store session in different directory● session.save_path = “N;/var/lib/php5”● N – no. of directory level e.g.
/tmp/4/b/1/e/3/sess_14879g
sessions
● PHP's default session handler is file system● instead, it can be stored into cache● let's say in memcache● session.save_handler = memcache
session.save_path = “tcp://127.0.0.1:11211”● using APC's add/fetch/delete
static keyword
● makes them accessible without needing an instantiation of the class
● so it's faster● public static function a( ) is faster compare to
public function a( )● quick benchmark for it.
Class Constants● define( ) is expensive● Class Constants are parsed at compile time.
So no execution overhead.● faster lookup due to smaller hash.● benchmark for it
miss use of Constants
● $foo[name] = 'Jignesh'; ● converted into strtolower ● hash lookups● E_NOTICE error message generated● temporary string being generated on the
during the execution● And of course it's slower● Let's benchmark for it.
for() loop optimization
Don't use functions in for() loop.
<?php
for ( $i = 1; $i < count($array); $i++ ) { }
?>
instead use,
<?php
$count = count($array);
for ( $i = 1; $i < $count; $i++ ) { }
?>
which is faster?
What’s the difference between:
isset($user['NAME'])
and
array_key_exists('NAME', $user)
Let's generate opcodes, more opcode less performance
Performance Tips
● Mostly people have only one solution● habitual to use DATABASE for information storage● Improper use of this resource can lead to significant
and continuously increasing performance loss.● Try to cache at different level● Avoid reg-ex, PHP's string functions are faster● Avoid magic calls - __get/__set/__autoload/__call.● There are lots of PHP's built in functions; PEAR, PECL
packages● chances are there what you need is already available
What to do, when you are stuck?
● Use Benchmarking Tools● Find bottleneck in your code● It might take time to do optimization● There is no any golden rule for it
● make your hand dirty
Finally
Premature optimization is the root of all evil.- Donald Knuth
Resources
http://pecl.php.net/apc
Thanks
Any other idea?