KEMBAR78
PHP Performance Trivia | PDF
PHP Performance Trivia
Nikita Popov
Opcache
Opcache
Method
Opcodes
Class
Method
Shared Memory
Opcache
Method
Opcodes
Class
Method Method
Class
Method
Shared Memory Per-Request Arena
copy
Opcache
Method
Opcodes
Class
Method Method
Class
Method
Shared Memory Per-Request Arena
copy
Modified during
inheritance
Opcache
Method
Opcodes
Class
Method Method
Class
Method
Shared Memory Per-Request Arena
copy
Modified during
inheritance
Registered in
class table
Preloading
Method
Opcodes
Class
Method
Shared Memory
Preloading
Method
Opcodes
Class
Method
Shared Memory MAP area
Static properties
Runtime cache
Preloading
Method
Opcodes
Class
Method
Shared Memory MAP area
Static properties
Runtime cache
Cleared on each request
Preloading
Method
Opcodes
Class
Method
Shared Memory ●
Classes must be fully inherited!
Preloading
Method
Opcodes
Class
Method
Shared Memory ●
Classes must be fully inherited!
●
Parents/interfaces known
●
All constant expressions known
●
All-ish types known
Preloading
Method
Opcodes
Class
Method
Shared Memory ●
Classes must be fully inherited!
●
Parents/interfaces known
●
All constant expressions known
●
All-ish types known
●
Windows: lost cause (ASLR),
internal classes not "known"
Preloading
Method
Opcodes
Class
Method
Shared Memory ●
No way to clear preload state
●
Opcache reset not enough
●
FPM reload not enough
Value Caching
●
Only about completely static data here…
– Say a composer class map
Value Caching
PHP
PHP
PHP
Opcache
SHM
APCU
SHM
Value Caching
PHP
PHP
PHP
Opcache
SHM
APCU
SHM
Direct accessRequires copying!
Value Caching
PHP
PHP
PHP
Opcache
SHM
APCU
SHM
Direct accessRequires copying!
Data never removed (*)Supports deletion
Value Caching
●
Only about completely static data here…
– Say a composer class map
●
APCU very inefficient for large data
– Requires unserialization and copying
●
(Ab)use opcache as a data cache
Value Caching
<?php
return [
"foo" => "bar",
"bar" => "baz",
];
Array stored as "immutable array"
in shared memory
Opcache Reset
●
Invalidated files remain in opcache
●
Only cleared on full reset
Opcache Reset
PHP
PHP
PHP
Opcache
SHM
Wait for requests to finish
Opcache Reset
PHP
PHP
PHP
Opcache
SHM
Wait for requests to finish
Opcache Reset
PHP
PHP
PHP
Opcache
SHM
Wait for requests to finish
Opcache Reset
PHP
PHP
PHP
Opcache
SHM
Wait for requests to finish
SIGKILL
Opcache Reset
PHP
PHP
Opcache
SHM
Wait for requests to finish
Opcache Reset
PHP
PHP
Opcache
SHM
Clear
Opcache Reset
PHP
PHP
Opcache
SHM
Opcache Reset
●
Cache not used during reset
●
Needs to be repopulated from scratch
●
File cache can help mitigate
Arrays vs Objects
● Array: ["first" => $a, "second" => $b]
●
Packed array: [$a, $b]
●
Object with declared properties
– class Test { public $first, $second; }
●
Object with dynamic properties
– (object)["first"=>$a, "second"=>$b]
Memory Usage
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
0
100
200
300
400
500
600
700
800
Array Array (packed) Object
Number of properties/keys
Memoryusage(bytes)
Memory Usage
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
0
100
200
300
400
500
600
700
800
Array Array (packed) Object
Number of properties/keys
Memoryusage(bytes)
["first" => $a,
"second" => $b]
[$a, $b]
class Pair { public $first, $second; }
Memory Usage (Ratio)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
0
1
2
3
4
5
6
7
Ratio Ratio (packed)
Number of properties/keys
Arraysize/objectsize
Caveat: Properties table
●
Some operations materialize properties table
– foreach ($object as $propName => $value)
– (array) $object
– var_dump($object)
– …
Caveat: Properties table
●
Some operations materialize properties table
– foreach ($object as $propName => $value)
– (array) $object
– var_dump($object)
– …
●
You pay the price for both object and array
●
No way to remove once created
Memory Usage
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
0
100
200
300
400
500
600
700
800
Array Array (packed) Object Object (dyn)
Number of properties/keys
Memoryusage(bytes)
Garbage Collection
Garbage Collection
●
Reference Counting
– Count how many times a value is used
– Destroy when count is zero
Garbage Collection
●
Reference Counting
– Count how many times a value is used
– Destroy when count is zero
$x = "foobar"; // refcount=1
$y = $x; // refcount=2
unset($x); // refcount=1
unset($y); // refcount=0 ==> Destroy!
Garbage Collection
●
Reference Counting
– Count how many times a value is used
– Destroy when count is zero
$x = []; // refcount=1
$x[0] =& $x; // refcount=2
unset($x); // refcount=1
// Will never reach 0 due to cycle!
Garbage Collection
●
Cycle Collector
– Mark & sweep algorithm
Garbage Collection
●
Cycle Collector
– Mark & sweep algorithm
●
Start from "roots"
●
Simulate what would happen if they were released
●
If simulation results in refcount=0, actually destroy
Garbage Collection
●
Cycle Collector
– Mark & sweep algorithm
– PHP <= 7.2: Fixed root buffer with 10000 entries
●
10000 objects should be enough for everyone!
Garbage Collection
●
Cycle Collector
– Mark & sweep algorithm
– PHP <= 7.2: Fixed root buffer with 10000 entries
●
10000 objects should be enough for everyone!
●
Cycle collector runs every time root buffer full
●
May walk graph with millions of objects each time
Composer + GC
Too many memes
Composer + GC
commit ac676f47f7bbc619678a29deae097b6b0710b799
Author: Jordi Boggiano <j.boggiano@seld.be>
Date: Tue Dec 2 10:23:21 2014 +0000
Disable GC when computing deps, refs #3482
diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php
index b76155a5..1b2a6772 100644
--- a/src/Composer/Installer.php
+++ b/src/Composer/Installer.php
@@ -160,6 +160,8 @@ public function __construct(...
*/
public function run()
{
+ gc_disable();
+
if ($this->dryRun) {
$this->verbose = true;
$this->runScripts = false;
Composer + GC
commit ac676f47f7bbc619678a29deae097b6b0710b799
Author: Jordi Boggiano <j.boggiano@seld.be>
Date: Tue Dec 2 10:23:21 2014 +0000
Disable GC when computing deps, refs #3482
diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php
index b76155a5..1b2a6772 100644
--- a/src/Composer/Installer.php
+++ b/src/Composer/Installer.php
@@ -160,6 +160,8 @@ public function __construct(...
*/
public function run()
{
+ gc_disable();
+
if ($this->dryRun) {
$this->verbose = true;
$this->runScripts = false;
2x speedup
Garbage Collection
●
Cycle Collector
– Mark & sweep algorithm
– PHP <= 7.2: Fixed root buffer with 10000 entries
– PHP >= 7.3: Dynamic root buffer
●
Root buffer automatically grows
●
Dynamic GC threshold
●
If GC collects little garbage, GC threshold grows
Type Declarations
●
Do they make PHP slower or faster?
Type Declarations
●
Do they make PHP slower or faster?
●
Type declarations need to be checked
●
Type declarations allow more optimizations
Type Optimization
<?php
function powi(float $base, int $power): float {
if ($power == 0) return 1.0;
$result = $base;
for ($i = 1; $i < $power; $i++) {
$result *= $base;
}
return $result;
}
Type Optimization
<?php
function powi(float $base, int $power): float {
if ($power == 0) return 1.0;
$result = $base;
for ($i = 1; $i < $power; $i++) {
$result *= $base;
}
return $result;
}
Type inference: int
Type inference: float
Type Optimization
<?php
function powi(float $base, int $power): float {
if ($power == 0) return 1.0;
$result = $base;
for ($i = 1; $i < $power; $i++) {
$result *= $base;
}
return $result;
}
Type inference: int
Type inference: float
→ Eliminated return type check
Type Optimization
<?php
function powi(float $base, int $power): float {
if ($power == 0) return 1.0;
$result = $base;
for ($i = 1; $i < $power; $i++) {
$result *= $base;
}
return $result;
}
Use specialized
ZEND_PRE_INC_LONG
Use specialized ZEND_MUL_DOUBLE
+ eliminate compound operation
Use specialized
ZEND_IS_SMALLER_LONG
Type Optimization
<?php
function powi(float $base, int $power): float {
if ($power == 0) return 1.0;
$result = $base;
for ($i = 1; $i < $power; $i++) {
$result *= $base;
}
return $result;
}
Type inference: int
Type inference: float
Type Optimization
<?php
function powi( $base, $power) {
if ($power == 0) return 1.0;
$result = $base;
for ($i = 1; $i < $power; $i++) {
$result *= $base;
}
return $result;
}
Type inference: int|float
Type inference: mixed
Type Optimization
●
For this example:
– Without opcache: Performance ~same with and
without types
– With opcache: With types 2.5x faster
– Type check cost: Once
– Type optimization benefit: Multiple loop iterations
Type Optimization
●
For this example:
– Without opcache: Performance ~same with and
without types
– With opcache: With types 2.5x faster
– Type check cost: Once
– Type optimization benefit: Multiple loop iterations
●
But: Does not happen often in practice.
Global Namespace Fallback
<?php
namespace Foo;
var_dump(strlen("foobar"));
// Might be strlen()
// Might be Foostrlen()
Global Namespace Fallback
<?php
namespace Foo;
var_dump(strlen("foobar"));
// Might be strlen()
// Might be Foostrlen()
Actual function cached on first call
→ Not particularly expensive
Specialized Functions
●
Some functions have optimized VM instruction
– strlen() and count()
– is_null() etc
– intval() etc
– defined()
– call_user_func() and call_user_func_array()
– in_array() and array_key_exists()
– get_class(), get_called_class() and gettype()
– func_num_args() and func_get_args()
Specialized Functions
●
Some functions have optimized VM instruction
●
Can only be used if function known
●
Requires fully qualified name or "use function"
Compile-time evaluation
<?php
namespace Foo;
function doSomething() {
if (version_compare(PHP_VERSION, '7.3', '>=')) {
// PHP 7.3 implementation
} else {
// Fallback implementation
}
}
Can't evaluate due to
namespace fallback
Compile-time evaluation
<?php
namespace Foo;
function doSomething() {
if (version_compare(PHP_VERSION, '7.3', '>=')) {
// PHP 7.3 implementation
} else {
// Fallback implementation
}
}
Evaluated to
true/false by opcache
Compile-time evaluation
<?php
namespace Foo;
use function version_compare;
use const PHP_VERSION;
function doSomething() {
if (version_compare(PHP_VERSION, '7.3', '>=')) {
// PHP 7.3 implementation
} else {
// Fallback implementation
}
}
Evaluated to
true/false by opcache
Thank You!

PHP Performance Trivia