2. Hi
Julien PAULI
SensioLabs tech team (Blackfire - PHP)
Programming with PHP since early 2000s
Today working as Unix system programmer (C)
PHP Internals programmer/contributor
PHP 5.5 & 5.6 Release Manager
@julienpauli
Tech blog at http://jpauli.github.io
jpauli@php.net
3. What we'll cover together
PHP 7 new engine design
What has changed inside PHP from PHP 5 ?
PHP 7 new compiler
Compiler optimizations
PHP 7 new references mechanism
PHP 7 new Hashtables (PHP arrays)
PHP 7 new strings management
4. The PHP language
Born in 1995
Fully written using the C language
Today
822,000 C lines of code
dozens of contributors around the world
7. PHP 7 new compiler
PHP 7 compiler is now based on an AST
It has been fully rewritten, and can compute much
more things at compile time
Every hash of every litteral string f.e
Resolves every static/litteral expression
Optimizes some function calls when result is known
at compile time
defined(), strlen(), cufa(), is_{type}(), assert(), chr(), ord()
Don't use namespaced calls but native_calls()
10. Namespaced function calls
We have proven that the performance difference is
really tiny on real use cases.
namespace Foo;
class Bar
{
public function hello($str)
{
return "Hello" . strlen($str);
}
}
namespace Foo;
class Bar
{
public function hello($str)
{
return "Hello" . strlen($str);
}
}
VS
12. PHP 7 new compiler
PHP 7 compiler is usually slower than PHP 5's
It optimizes more things
It must walk an AST
It is globally more complex
It benefits from a better design
It is hookable through PHP extensions
Use OPCache to not suffer from compile time
13. PHP 7 compiler optim example, static arrays
Arrays containg keys/vals that are static/litteral
Such arrays are fully resolved at compile time
They involve no runtime work at all
const FOO = ['bar', 'baz', 'foo', 34, [42, 'bar'=>'baz']];
14. Static arrays in PHP 5
A lot of runtime is eaten to construct the same
array again and again
$a = ['bar', 'baz', 'foo', 34, [42, 'bar'=>'baz']];
3 0 E > INIT_ARRAY ~0 'bar'
1 ADD_ARRAY_ELEMENT ~0 'baz'
2 ADD_ARRAY_ELEMENT ~0 'foo'
3 ADD_ARRAY_ELEMENT ~0 34
4 INIT_ARRAY ~1 42
5 ADD_ARRAY_ELEMENT ~1 'baz', 'bar'
6 ADD_ARRAY_ELEMENT ~0 ~1
7 ASSIGN !0, ~0
15. Static arrays in PHP 7
No runtime impact (but compile-time)
You'd better use OPCache
$a = ['bar', 'baz', 'foo', 34, [42, 'bar'=>'baz']];
L3 #0 ASSIGN $a array(5)
16. PHP 7 new references mechanism
In PHP 5, ref mismatching a function call triggered a
full zval copy the engine
In PHP 7, the deep copy is postponed until COW
breakage
function foo($arg) { }
$a = 'foo';
$b = &$a;
foo($a); /* full copy of the argument */
17. PHP 7 new references mechanism
In PHP 7, the deep copy is postponed until COW
breakage
If no COW breakage, then no copy happens at all
function foo($arg) { $arg = 'bar'; } /* full copy of the variable */
$a = 'foo';
$b = &$a;
foo($a);
$a = ['foo', 42, ['bar' , new stdclass], 'baz'];
$b = &$a;
if (count($a) == 8) { /* no zval copy here */
}
19. Optimizing CPU time
Latency Numbers Every Programmer Should Know
http://lwn.net/Articles/250967/
http://www.eecs.berkeley.edu/~rcs/research/interactive
_latency.html
2016 numbers (may vary with chip)
---------------------------------------------------
L1 cache reference 1 ns
Branch mispredict 3 ns
L2 cache reference 4 ns 4x L1 cache
L3 cache reference 12 ns 3X L2 cache, 12x L1 cache
Main memory reference 100 ns 25x L2 cache, 100x L1 cache
SSD random read 16,000 ns
HDD random read(seek) 200,000,000 ns
20. Optimizing CPU cache efficiency
If we can reduce payload size, the CPU will use its
caches more often
CPU caches prefetch data on a "line" basis
Improve data locality to improve cache efficiency
https://software.intel.com/en-us/articles/optimize-data-
structures-and-memory-access-patterns-to-improve-
data-locality
That means in C
Reduce number of pointer indirections
Stick data together (struct hacks, struct merges)
Use smaller data sizes
21. PHP 7 cache efficiency
If we can reduce payload size, the CPU will use its
caches more often
PHP 7.1.4-dev (debug)
128,883666 task-clock (msec)
9 context-switches
0 cpu-migrations
1 768 page-faults
340 930 642 cycles
810 206 077 instructions
100 639 058 branches
187 132 branch-misses
0,131802866 seconds time elapsed
PHP 5.6.31-dev (debug)
730,824226 task-clock (msec)
92 context-switches
1 cpu-migrations
74 691 page-faults
2 030 928 993 cycles
3 766 048 098 instructions
506 047 488 branches
356 931 branch-misses
0,773863158 seconds time elapsed
22. PHP 7 optimizations
Every variable in PHP is coded on a zval struct
This struct has been reorganized in PHP 7
Narrowed / shrinked
separated
Hence, every variable usage in PHP 7 is more
optimized than in PHP 5
23. PHP 5 variables
value
refcount is_ref
type
gc_info
dval
str_val* str_len
hashtable*
object*
lval
ast*
zval
zval_value
...
...
HashTable
32 bytes
$a
8 bytes
zval *
XX bytes
40 bytes + complex value size
2 indirections
25. PHP 5 vs PHP 7 variable design
zval container no longer stores GC infos
No more need to heap allocate a zval *
Very less pressure on the heap allocator
GC infos stored into each complex types
each complex type may now be shared
In PHP 5, we had to share the zval containing them
PHP 7 variables are much more CPU cache efficient
26. New Memory Allocator
PHP 7 has a fully new heap memory allocator
Zend Memory Manager
It now uses several allocator pools
Huge
Medium
Small
... for better efficiency
Uses mmap(), no more libc's malloc() overhead
May use Kernel Huge Pages if told to
Better CPU TLB usage
30. Packed arrays
If your keys are integer only (no string key)
If your keys are constantly increasing
No matter if they don't follow each other with +1
Then you'll benefit from packed arrays optimization
Packed arrays will reduce memory size compared
to "normal" array
Reduction of (table_size - 2) * 4 bytes
~ 4Kb for a 1000 entry table
May be noticeable for BIG arrays
31. Packed arrays example
const N = 1024 * 1023;
for ($i=0; $i<N; $i++) {
$tab[] = random_bytes(3);
}
echo memory_get_usage();
const N = 1024 * 1023;
for ($i=0; $i<N; $i++) {
$tab[] = random_bytes(3);
}
$tab['foo'] = 'bar';
echo memory_get_usage();
const N = 1024 * 1023;
for ($i=0; $i<N; $i++) {
$tab[] = random_bytes(3);
}
unset($tab[1000]);
$tab[1000] = 1000;
echo memory_get_usage();
~67Mb
~71Mb
~71Mb
32. Packed arrays conditions (recalled)
Do NOT use string keys
Always use increasing integer-based keys
Contiguous or not is not important
If using the compiler, keep keys into the interval [0-
table-size] , table-size being rounded to the upper
power of two
For example, if you need lists , then you'll benefit
from this optimisation
33. HashTables in PHP 5
Each element needs
4 pointer indirections
72 bytes for a bucket + 32 bytes for a zval
zval
zval *
HashTable
$a
zval *
HashTable*
bucket *
zval
64 bytes
72 bytesbucket
34. HashTables in PHP 7
Each element needs
2 pointer indirections
32 bytes for a bucket
zval
bucket
HashTable
$a
zval
HashTable*
zval
56 bytes
32 bytes
bucket*
35. HashTables in PHP 7 : go further
http://jpauli.github.io/2016/04/08/hashtables.html
37. String management
In PHP 5, strings don't have their own structure
String management is hard
Leads to many strings duplication
And thus many memory access
In PHP 7, strings share the zend_string structure
They are refcounted, thus shareable
hashes are precomputed, often at compile time
struct hack is used to compact memory
38. Strings in PHP
char * str
...
zval
gc_infos
int len
refcount is_ref zend_string *
...
zval
...
hash
gc_infos
char str[1]size_t len
...
zend_string
PHP 5 PHP 7
43. Encapsed string optimisation
Encapsed string are double-quoted strings that get
parsed
They need to be analyzed for variables
PHP 5 used to reallocate the string at each step
$a = "foo and $b and $c";
3 0 E > ADD_STRING ~0 'foo+and+'
1 ADD_VAR ~0 ~0, !1
2 ADD_STRING ~0 ~0, '+and+'
3 ADD_VAR ~0 ~0, !2
4 ASSIGN !0, ~0
4 5 > RETURN 1
44. Encapsed string in PHP 5
$a = "foo and $b and $c";
3 0 E > ADD_STRING ~0 'foo+and+'
1 ADD_VAR ~0 ~0, !1
2 ADD_STRING ~0 ~0, '+and+'
3 ADD_VAR ~0 ~0, !2
4 ASSIGN !0, ~0
4 5 > RETURN 1
foo and
foo and b
foo and b and
foo and b and c
Lot of pressure on the allocator
Needs to find new chunk
At every new allocation
Browses through a free-chunk
linked-list
Bad for performances
$b = 'b';
$c = 'c';
45. Encapsed string optimisation in PHP 7
PHP 7 uses a "rope", and only reallocates memory
once, at the end
https://en.wikipedia.org/wiki/Rope_(data_structure)
$a = "foo and $b and $c";
L3 #0 ROPE_INIT "foo and " ~1
L3 #1 ROPE_ADD ~1 $b ~1
L3 #2 ROPE_ADD ~1 " and " ~1
L3 #3 ROPE_END ~1 $c ~0
L3 #4 ASSIGN $a ~0
L3 #5 RETURN 1
46. Encapsed strings in PHP 7
$a = "foo and $b and $c";
L3 #0 ROPE_INIT "foo and " ~1
L3 #1 ROPE_ADD ~1 $b ~1
L3 #2 ROPE_ADD ~1 " and " ~1
L3 #3 ROPE_END ~1 $c ~0
L3 #4 ASSIGN $a ~0
L3 #5 RETURN 1
foo and
foo and b
foo and b and
foo and b and c
foo and b and c
INIT
ADD
ADD
ADD
END
Keep every piece of string
as its own buffer
Stack them
At the end, merge them
as one operation
47. So ?
So you'd better use encapsed strings
Than concatenations
$a = "foo and $b and $c";
$a = 'foo and ' . $b . ' and ' . $c;
49. Future of PHP
PHP 7 branch keeps optimizing things
PHP 7 branch keep preparing the massive JIT engine
move that should happen for PHP 8
PHP 8 is not expected before 2020 at best
Try at first to migrate to PHP 7 branch
PHP 7.2 is on its way
Nov - Dec 2017
As usual, read wiki.php.net/rfc
PHP 5.6 will die end of 2017 , and PHP 5 branch as well