Skip to content

Commit 7c8bd48

Browse files
committed
Get the benchmarks up and running
1 parent fbdf983 commit 7c8bd48

21 files changed

+418
-115
lines changed

README.md

+10-11
Original file line numberDiff line numberDiff line change
@@ -152,29 +152,28 @@ Checkout the committed [`demo.debug.c`](demo.debug.c) and [`demo.reproduce.c`](d
152152

153153
# Performance
154154

155-
So, is this thing any fast? Well, let's look at the internal benchmarks. You can run them yourself with `php bench.php`, and it'll give you the following output (running 5 iterations of each test, and averaging the time):
156-
155+
So, is this thing any fast? Well, let's look at the internal benchmarks. You can run them yourself with `php bench.php`, and it'll give you the following output (running 5 iterations of each test, and averaging the time).
157156

158157
| Test Name | 7.3 (s)| 7.3.NO.OPCACHE (s)| 7.4 (s)| 7.4.NO.OPCACHE (s)| 8.JIT (s)| 8.NOJIT (s)| bin/jit.php (s) | bin/compile.php (s) | compiled time (s) |
159158
|--------------------|-------------------|-------------------|-------------------|-------------------|-------------------|-------------------|-----------------|---------------------|-------------------|
160-
| Ack(3,10) | 1.1706 | 1.9168 | 1.1752 | 1.9196 | 0.6796 | 1.1634 | 0.5025 | 0.2939 | 0.2127 |
161-
| Ack(3,8) | 0.0866 | 0.1057 | 0.0973 | 0.1215 | 0.0534 | 0.0853 | 0.3053 | 0.2943 | 0.0148 |
162-
| Ack(3,9) | 0.3053 | 0.3709 | 0.3018 | 0.3730 | 0.1776 | 0.3010 | 0.3458 | 0.2937 | 0.0540 |
163-
| array_access | 2.5903 | 2.5624 | 2.5958 | 2.6941 | 1.6697 | 2.6075 | 0.5495 | 0.2936 | 0.2685 |
164-
| fibo(30) | 0.0747 | 0.1016 | 0.0760 | 0.1035 | 0.0429 | 0.0743 | 0.3065 | 0.2946 | 0.0110 |
165-
| mandelbrot | 0.0441 | 0.1348 | 0.0434 | 0.1090 | 0.0323 | 0.0440 | 0.3186 | 0.3075 | 0.0146 |
166-
| simple | 0.0557 | 0.0786 | 0.0650 | 0.0866 | 0.0391 | 0.0673 | 0.3094 | 0.2988 | 0.0120 |
159+
| Ack(3,10) | 1.1755 | 1.8759 | 1.1745 | 1.9215 | 0.6838 | 1.1649 | 0.3693 | 0.2000 | 0.1524 |
160+
| Ack(3,8) | 0.0876 | 0.1012 | 0.0924 | 0.1035 | 0.0552 | 0.0853 | 0.1694 | 0.2107 | 0.0110 |
161+
| Ack(3,9) | 0.3160 | 0.5450 | 0.3332 | 0.4631 | 0.1901 | 0.3213 | 0.2397 | 0.2286 | 0.0393 |
162+
| fibo(30) | 0.0812 | 0.0922 | 0.0836 | 0.0956 | 0.0447 | 0.0784 | 0.2014 | 0.2369 | 0.0091 |
163+
| mandelbrot | 0.0454 | 0.1303 | 0.0457 | 0.1094 | 0.0294 | 0.0459 | 0.2001 | 0.2479 | 0.0142 |
164+
| simple | 0.0567 | 0.0798 | 0.0565 | 0.0906 | 0.0275 | 0.0591 | 0.1755 | 0.2113 | 0.0114 |
167165

166+
This is after the port to using LLVM under the hood. So the port to LLVM appears to have been well worth it, even just from a performance standpoint.
168167

169168
To run the benchmarks yourself, you need to pass a series of ENV vars for each PHP version you want to test. For example, the above chart is generated with::
170169

171170
```console
172171
me@local:~$ PHP_7_3=php-7.3 PHP_7_4=php-7.4 PHP_8_JIT=php-8-jit PHP_8_NOJIT=php-8-nojit PHP_7_3_NO_OPCACHE="php-7.3 -dopcache.enable=0" PHP_7_4_NO_OPCACHE="php-7.4 -dopcache.enable=0" php bench.php
173172
```
174173

175-
Without opcache doing optimizations, the `bin/jit.php` is actually able to hang up with ack(3,9) and mandelbrot for 7.3 and 7.4. It's even able to hang with PHP 8's experimental JIT compiler for ack(3,9).
174+
Without opcache doing optimizations, the `bin/jit.php` is actually able to get close to native PHP with ack(3,9) and mandelbrot (without opcache) for 7.3 and 7.4. It's even able to hang with PHP 8's experimental JIT compiler for ack(3,9). For ack(3,10) it's able to be the fastest execution method.
176175

177-
Most other tests are actually WAY slower with the `bin/jit.php` compiler. That's because the test itself is slower than the baseline time to parse and compile a file (about 0.12 seconds right now).
176+
Most other tests are actually WAY slower with the `bin/jit.php` compiler. That's because the test itself is slower than the baseline time to parse and compile a file (about 0.2 seconds right now).
178177

179178
And note that this is running the compiler on top of PHP. At some point, the goal is to get the compiler to compile itself, hopefully cutting the time to compile down by at least a few hundred percent.
180179

File renamed without changes.

ext/types/Module.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Module extends ModuleAbstract {
99

1010
public function getFunctions(): array {
1111
return [
12-
//new strlen,
12+
new strlen,
1313
// new is_type('is_int', Variable::TYPE_INTEGER),
1414
// new is_type('is_integer', Variable::TYPE_INTEGER),
1515
// new is_type('is_long', Variable::TYPE_INTEGER),

ext/types/strlen.php

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
# This file is generated, changes you make will be lost.
4+
# Make your changes in /home/ircmaxell/Workspace/PHP-Compiler/PHP-Compiler/ext/types/strlen.pre instead.
5+
6+
// First, expand statements
7+
/*
8+
* This file is part of PHP-Compiler, a PHP CFG Compiler for PHP code
9+
*
10+
* @copyright 2015 Anthony Ferrara. All rights reserved
11+
* @license MIT See LICENSE at the root of the project for more info
12+
*/
13+
14+
namespace PHPCompiler\ext\types;
15+
16+
use PHPCompiler\Func\Internal;
17+
use PHPCompiler\Frame;
18+
19+
use PHPCompiler\JIT\Context;
20+
use PHPCompiler\JIT\Variable;
21+
22+
use PHPLLVM\Value;
23+
24+
25+
class strlen extends Internal {
26+
27+
public function execute(Frame $frame): void {
28+
if (count($frame->calledArgs) !== 1) {
29+
throw new \LogicException("Expecting exactly a single argument to strlen()");
30+
}
31+
$var = $frame->calledArgs[0];
32+
if (!is_null($frame->returnVar)) {
33+
$frame->returnVar->int(strlen($var->toString()));
34+
}
35+
}
36+
37+
public Context $context;
38+
39+
public function call(Context $context, Variable ... $args): Value {
40+
$this->context = $context;
41+
if (count($args) !== 1) {
42+
throw new \LogicException('Too few args passed to strlen()');
43+
}
44+
$argValue = $context->helper->loadValue($args[0]);
45+
switch ($args[0]->type) {
46+
case Variable::TYPE_STRING:
47+
$offset = $this->context->structFieldMap[$argValue->typeOf()->getElementType()->getName()]['length'];
48+
$result = $this->context->builder->load(
49+
$this->context->builder->structGep($argValue, $offset)
50+
);
51+
52+
return $result;
53+
}
54+
throw new \LogicException('Non-implemented type handled: ' . $args[0]->type);
55+
}
56+
57+
}

ext/types/strlen.pre

+16-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ namespace PHPCompiler\ext\types;
1212
use PHPCompiler\Func\Internal;
1313
use PHPCompiler\Frame;
1414

15-
use PHPCompiler\JIT;
15+
use PHPCompiler\JIT\Context;
16+
use PHPCompiler\JIT\Variable;
17+
18+
use PHPLLVM\Value;
1619

1720

1821
class strlen extends Internal {
@@ -27,17 +30,22 @@ class strlen extends Internal {
2730
}
2831
}
2932

30-
public function call(\gcc_jit_rvalue_ptr ... $args): \gcc_jit_rvalue_ptr {
33+
public Context $context;
34+
35+
public function call(Context $context, Variable ... $args): Value {
36+
$this->context = $context;
3137
if (count($args) !== 1) {
3238
throw new \LogicException('Too few args passed to strlen()');
3339
}
34-
$type = $this->jit->context->getStringFromType(\gcc_jit_rvalue_get_type($args[0]));
35-
switch ($type) {
36-
case '__string__*':
37-
return $this->jit->context->helper->call('__string__strlen', $args[0]);
38-
default:
39-
throw new \LogicException('Non-implemented type handled: ' . $type);
40+
$argValue = $context->helper->loadValue($args[0]);
41+
switch ($args[0]->type) {
42+
case Variable::TYPE_STRING:
43+
compile {
44+
$result = $argValue->length;
45+
}
46+
return $result;
4047
}
48+
throw new \LogicException('Non-implemented type handled: ' . $args[0]->type);
4149
}
4250

4351
}

lib/Func/Internal.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
use PHPCompiler\VM\Context;
1616
use PHPCompiler\VM\Variable;
1717
use PHPCompiler\JIT;
18+
use PHPCompiler\JIT\Call;
1819

19-
abstract class Internal extends Func implements Handler {
20+
abstract class Internal extends Func implements Handler, Call {
2021

2122
public function __construct(string $name = null) {
2223
if (is_null($name)) {
@@ -30,6 +31,4 @@ public function getFrame(Context $context, ?Frame $frame = null): Frame {
3031
return new Frame($this, null, null);
3132
}
3233

33-
public function jit(JIT $jit): void;
34-
3534
}

lib/JIT.php

+24-15
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function compileFunc(CoreFunc $func): void {
6262
// No need to do anything, already compiled
6363
return;
6464
} elseif ($func instanceof CoreFunc\Internal) {
65-
$this->context->functions[strtolower($func->getName())] = $func->jit($this);
65+
$this->context->functionProxies[strtolower($func->getName())] = $func;
6666
return;
6767
}
6868
throw new \LogicException("Unknown func type encountered: " . get_class($func));
@@ -104,6 +104,8 @@ private function compileBlock(Block $block, ?string $funcName = null): PHPLLVM\V
104104
throw new \LogicException("Non-typed functions not implemented yet");
105105
}
106106
$returnType = $this->context->getTypeFromString($callbackType);
107+
$this->context->functionReturnType[strtolower($internalName)] = $callbackType;
108+
107109
$callbackType .= '(*)(';
108110
$callbackSep = '';
109111
foreach ($block->func->params as $idx => $param) {
@@ -314,13 +316,21 @@ private function compileBlockInternal(
314316
$this->assignOperand(
315317
$block->getOperand($op->arg1),
316318
$this->context->helper->binaryOp(
317-
$op->type,
319+
$op,
318320
$this->context->getVariableFromOp($block->getOperand($op->arg2)),
319321
$this->context->getVariableFromOp($block->getOperand($op->arg3))
320322
)
321323
);
322324
break;
323-
// case OpCode::TYPE_UNARY_MINUS:
325+
case OpCode::TYPE_UNARY_MINUS:
326+
$this->assignOperand(
327+
$block->getOperand($op->arg1),
328+
$this->context->helper->unaryOp(
329+
$op,
330+
$this->context->getVariableFromOp($block->getOperand($op->arg2)),
331+
)
332+
);
333+
break;
324334
// case OpCode::TYPE_CASE:
325335
case OpCode::TYPE_JUMP:
326336
$newBlock = $this->compileBlockInternal($func, $op->block1, ...$args);
@@ -365,30 +375,30 @@ private function compileBlockInternal(
365375
throw new \LogicException("Variable function calls not yet supported");
366376
}
367377
$lcname = strtolower($nameOp->value);
368-
if (!isset($this->context->functions[$lcname])) {
378+
if (isset($this->context->functions[$lcname])) {
379+
$this->context->scope->toCall = new JIT\Call\Native(
380+
$this->context->functions[$lcname],
381+
$nameOp->value
382+
);
383+
} elseif (isset($this->context->functionProxies[$lcname])) {
384+
$this->context->scope->toCall = $this->context->functionProxies[$lcname];
385+
} else {
369386
throw new \RuntimeException("Call to undefined function $lcname");
370387
}
371-
$this->context->scope->toCall = $this->context->functions[$lcname];
372388
$this->context->scope->args = [];
373389
break;
374390
case OpCode::TYPE_ARG_SEND:
375-
$this->context->scope->args[] = $this->context->helper->loadValue($this->context->getVariableFromOp($block->getOperand($op->arg1)));
391+
$this->context->scope->args[] = $this->context->getVariableFromOp($block->getOperand($op->arg1));
376392
break;
377393
case OpCode::TYPE_FUNCCALL_EXEC_NORETURN:
378394
if (is_null($this->context->scope->toCall)) {
379395
// short circuit
380396
break;
381397
}
382-
$this->context->builder->call(
383-
$this->context->scope->toCall,
384-
... $this->context->scope->args
385-
);
398+
$this->context->scope->toCall->call($this->context, ...$this->context->scope->args);
386399
break;
387400
case OpCode::TYPE_FUNCCALL_EXEC_RETURN:
388-
$result = $this->context->builder->call(
389-
$this->context->scope->toCall,
390-
... $this->context->scope->args
391-
);
401+
$result = $this->context->scope->toCall->call($this->context, ...$this->context->scope->args);
392402
$this->assignOperandValue($block->getOperand($op->arg1), $result);
393403
break;
394404
// case OpCode::TYPE_DECLARE_CLASS:
@@ -420,7 +430,6 @@ private function compileBlockInternal(
420430
// );
421431
// break;
422432
default:
423-
var_dump($block);
424433
throw new \LogicException("Unknown JIT opcode: ". $op->getType());
425434
}
426435
}

lib/JIT.pre

+24-15
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class JIT {
5858
// No need to do anything, already compiled
5959
return;
6060
} elseif ($func instanceof CoreFunc\Internal) {
61-
$this->context->functions[strtolower($func->getName())] = $func->jit($this);
61+
$this->context->functionProxies[strtolower($func->getName())] = $func;
6262
return;
6363
}
6464
throw new \LogicException("Unknown func type encountered: " . get_class($func));
@@ -100,6 +100,8 @@ class JIT {
100100
throw new \LogicException("Non-typed functions not implemented yet");
101101
}
102102
$returnType = $this->context->getTypeFromString($callbackType);
103+
$this->context->functionReturnType[strtolower($internalName)] = $callbackType;
104+
103105
$callbackType .= '(*)(';
104106
$callbackSep = '';
105107
foreach ($block->func->params as $idx => $param) {
@@ -284,13 +286,21 @@ class JIT {
284286
$this->assignOperand(
285287
$block->getOperand($op->arg1),
286288
$this->context->helper->binaryOp(
287-
$op->type,
289+
$op,
288290
$this->context->getVariableFromOp($block->getOperand($op->arg2)),
289291
$this->context->getVariableFromOp($block->getOperand($op->arg3))
290292
)
291293
);
292294
break;
293-
// case OpCode::TYPE_UNARY_MINUS:
295+
case OpCode::TYPE_UNARY_MINUS:
296+
$this->assignOperand(
297+
$block->getOperand($op->arg1),
298+
$this->context->helper->unaryOp(
299+
$op,
300+
$this->context->getVariableFromOp($block->getOperand($op->arg2)),
301+
)
302+
);
303+
break;
294304
// case OpCode::TYPE_CASE:
295305
case OpCode::TYPE_JUMP:
296306
$newBlock = $this->compileBlockInternal($func, $op->block1, ...$args);
@@ -333,30 +343,30 @@ class JIT {
333343
throw new \LogicException("Variable function calls not yet supported");
334344
}
335345
$lcname = strtolower($nameOp->value);
336-
if (!isset($this->context->functions[$lcname])) {
346+
if (isset($this->context->functions[$lcname])) {
347+
$this->context->scope->toCall = new JIT\Call\Native(
348+
$this->context->functions[$lcname],
349+
$nameOp->value
350+
);
351+
} elseif (isset($this->context->functionProxies[$lcname])) {
352+
$this->context->scope->toCall = $this->context->functionProxies[$lcname];
353+
} else {
337354
throw new \RuntimeException("Call to undefined function $lcname");
338355
}
339-
$this->context->scope->toCall = $this->context->functions[$lcname];
340356
$this->context->scope->args = [];
341357
break;
342358
case OpCode::TYPE_ARG_SEND:
343-
$this->context->scope->args[] = $this->context->helper->loadValue($this->context->getVariableFromOp($block->getOperand($op->arg1)));
359+
$this->context->scope->args[] = $this->context->getVariableFromOp($block->getOperand($op->arg1));
344360
break;
345361
case OpCode::TYPE_FUNCCALL_EXEC_NORETURN:
346362
if (is_null($this->context->scope->toCall)) {
347363
// short circuit
348364
break;
349365
}
350-
$this->context->builder->call(
351-
$this->context->scope->toCall,
352-
... $this->context->scope->args
353-
);
366+
$this->context->scope->toCall->call($this->context, ...$this->context->scope->args);
354367
break;
355368
case OpCode::TYPE_FUNCCALL_EXEC_RETURN:
356-
$result = $this->context->builder->call(
357-
$this->context->scope->toCall,
358-
... $this->context->scope->args
359-
);
369+
$result = $this->context->scope->toCall->call($this->context, ...$this->context->scope->args);
360370
$this->assignOperandValue($block->getOperand($op->arg1), $result);
361371
break;
362372
// case OpCode::TYPE_DECLARE_CLASS:
@@ -388,7 +398,6 @@ class JIT {
388398
// );
389399
// break;
390400
default:
391-
var_dump($block);
392401
throw new \LogicException("Unknown JIT opcode: ". $op->getType());
393402
}
394403
}

0 commit comments

Comments
 (0)