Movatterモバイル変換


[0]ホーム

URL:


rfc:jit

PHP RFC: JIT

Introduction

It's no secret that the performance jump of PHP 7 was originally initiated by attempts to implement JIT for PHP. We started these efforts at Zend (mostly by Dmitry) back in 2011 and since that time tried 3 different implementations. We never moved forward to propose to release any of them, for three main reasons: They resulted in no substantial performance gains for typical Web apps; They were complex to develop and maintain; We still had additional directions we could explore to improve performance without having to use JIT.

The Case for JIT Today

Even though most of the fundamentals for JIT-enabling PHP haven't changed - we believe there is a good case today for JIT-enabling PHP.

First, we believe we've reached the extent of our ability to improve PHP's performance using other optimization strategies. In other words - we can't further improve the performance of PHP unless we use JIT.

Secondly - using JIT may open the door for PHP being more frequently used in other, non-Web, CPU-intensive scenarios - where the performance benefits will actually be very substantial - and for which PHP is probably not even being considered today.

Lastly - making JIT available can provide us (with additional efforts) with the ability to develop built-in functions in PHP, instead of (or in addition to) C - without suffering the huge performance penalty that would be associated with such a strategy in today's, non-JITted engine. This, in turn, can open the door to faster innovation - and also more secure implementations, that would be less susceptible to memory management, overflows and similar issues associated with C-based development.

Proposal

We propose to include JIT in PHP 8 and provide additional efforts to increase its performance and usability.

In addition, we propose to consider including JIT in PHP 7.4 as an experimental feature (disabled by default).

PHP JIT is implemented as an almost independent part of OPcache. It may be enabled/disabled at PHP compile time and at run-time.When enabled, native code of PHP files is stored in an additional region of the OPcache shared memory and op_array->opcodes[].handler(s) keep pointers to the entry points of JIT-ed code. This approach doesn't require engine modification at all.

We use DynAsm (developed for LuaJIT project) for generation of native code. It's a very lightweight and advanced tool, but does assume good, and very low-level development knowledge of target assembler languages. In the past we tried LLVM, but its code generation speed was almost 100 times slower, making it prohibitively expensive to use. Currently we support x86 and x86_64 CPUs on POSIX platforms and Windows. DynAsm also supports ARM. ARM64, MIPS, MIPS64 and PPC, so in theory we should be able to support all of the platforms that are popular for PHP deployments (given enough efforts).

PHP JIT doesn't introduce any additional IR (Intermediate Representation) form. It generates native code directly from PHP byte-code and information collected by SSA static analyses framework (a part of opcache optimizer). Code is usually generated separately for each PHP byte-code instruction. Only few combinations are considered together (e.g. compare + conditional jump).

If type of PHP variable is exactly inferred (in SSA) to LONG or DOUBLE, and it can't be accessed indirectly, JIT may store its value directly in CPU registers, avoiding memory stores and loads. PHP JIT liner-scan register allocation algorithm, tat combines high speed with reasonable quality.

The quality of the JIT may be demonstrated on Mandelbrot benchmark published athttps://gist.github.com/dstogov/12323ad13d3240aee8f1, where it improves performance more than 4 times (0.011 sec vs 0.046 sec on PHP 7.4).

function iterate($x,$y){$cr=$y-0.5;$ci=$x;$zr=0.0;$zi=0.0;$i=0;while(true){$i++;$temp=$zr*$zi;$zr2=$zr*$zr;$zi2=$zi*$zi;$zr=$zr2-$zi2+$cr;$zi=$temp+$temp+$ci;if($zi2+$zr2> BAILOUT)return$i;if($i> MAX_ITERATIONS)return0;} }

The following is the complete assembler code generated for the PHP function above, with the main loop code visible between .L5 and .L7:

JIT$Mandelbrot::iterate:; (/home/dmitry/php/bench/b.php)sub$0x10,%espcmp$0x1,0x1c(%esi)jb.L14jmp.L1.ENTRY1:sub$0x10,%esp.L1:cmp$0x2,0x1c(%esi)jb.L15mov$0xec3800f0,%edijmp.L2.ENTRY2:sub$0x10,%esp.L2:cmp$0x5,0x48(%esi)jnz.L16vmovsd0x40(%esi),%xmm1vsubsd0xec380068,%xmm1,%xmm1.L3:mov0x30(%esi),%eaxmov0x34(%esi),%edxmov%eax,0x60(%esi)mov%edx,0x64(%esi)mov0x38(%esi),%edxmov%edx,0x68(%esi)test$0x1,%dhjz.L4add$0x1,(%eax).L4:vxorps%xmm2,%xmm2,%xmm2vxorps%xmm3,%xmm3,%xmm3xor%edx,%edx.L5:cmp$0x0, EG(vm_interrupt)jnz.L18add$0x1,%edxvmulsd%xmm3,%xmm2,%xmm4vmulsd%xmm2,%xmm2,%xmm5vmulsd%xmm3,%xmm3,%xmm6vsubsd%xmm6,%xmm5,%xmm7vaddsd%xmm7,%xmm1,%xmm2vaddsd%xmm4,%xmm4,%xmm4cmp$0x5,0x68(%esi)jnz.L19vaddsd0x60(%esi),%xmm4,%xmm3.L6:vaddsd%xmm5,%xmm6,%xmm6vucomisd0xec3800a8,%xmm6jp.L13jbe.L13mov0x8(%esi),%ecxtest%ecx,%ecxjz.L7mov%edx,(%ecx)mov$0x4,0x8(%ecx).L7:test$0x1,0x39(%esi)jnz.L21.L8:test$0x1,0x49(%esi)jnz.L23.L9:test$0x1,0x69(%esi)jnz.L25.L10:movzx0x1a(%esi),%ecxtest$0x496,%ecxjnz JIT$$leave_functionmov0x20(%esi),%eaxmov%eax, EG(current_execute_data)test$0x40,%ecxjz.L12mov0x10(%esi),%eaxsub$0x1,(%eax)jnz.L11mov%eax,%ecxcall zend_objects_store_deljmp.L12.L11:mov0x4(%eax),%ecxand$0xfffffc10,%ecxcmp$0x10,%ecxjnz.L12mov%eax,%ecxcall gc_possible_root.L12:mov%esi, EG(vm_stack_top)mov0x20(%esi),%esicmp$0x0, EG(exception)mov(%esi),%edijnz JIT$$leave_throwadd$0x1c,%ediadd$0x10,%espjmp(%edi).L13:cmp$0x3e8,%edxjle.L5mov0x8(%esi),%ecxtest%ecx,%ecxjz.L7mov$0x0,(%ecx)mov$0x4,0x8(%ecx)jmp.L7.L14:mov%edi,(%esi)mov%esi,%ecxcall zend_missing_arg_errorjmp JIT$$exception_handler.L15:mov%edi,(%esi)mov%esi,%ecxcall zend_missing_arg_errorjmp JIT$$exception_handler.L16:cmp$0x4,0x48(%esi)jnz.L17vcvtsi2sd0x40(%esi),%xmm1,%xmm1vsubsd0xec380068,%xmm1,%xmm1jmp.L3.L17:mov%edi,(%esi)lea0x50(%esi),%ecxlea0x40(%esi),%edxsub$0xc,%esppush$0xec380068call sub_functionadd$0xc,%espcmp$0x0, EG(exception)jnz JIT$$exception_handlervmovsd0x50(%esi),%xmm1jmp.L3.L18:mov$0xec38017c,%edijmp JIT$$interrupt_handler.L19:cmp$0x4,0x68(%esi)jnz.L20vcvtsi2sd0x60(%esi),%xmm3,%xmm3vaddsd%xmm4,%xmm3,%xmm3jmp.L6.L20:mov$0xec380240,(%esi)lea0x80(%esi),%ecxvmovsd%xmm4,0xe0(%esi)mov$0x5,0xe8(%esi)lea0xe0(%esi),%edxsub$0xc,%esplea0x60(%esi),%eaxpush%eaxcall add_functionadd$0xc,%espcmp$0x0, EG(exception)jnz JIT$$exception_handlervmovsd0x80(%esi),%xmm3jmp.L6.L21:mov0x30(%esi),%ecxsub$0x1,(%ecx)jnz.L22mov$0x1,0x38(%esi)mov$0xec3802b0,(%esi)call rc_dtor_funcjmp.L8.L22:mov0x4(%ecx),%eaxand$0xfffffc10,%eaxcmp$0x10,%eaxjnz.L8call gc_possible_rootjmp.L8.L23:mov0x40(%esi),%ecxsub$0x1,(%ecx)jnz.L24mov$0x1,0x48(%esi)mov$0xec3802b0,(%esi)call rc_dtor_funcjmp.L9.L24:mov0x4(%ecx),%eaxand$0xfffffc10,%eaxcmp$0x10,%eaxjnz.L9call gc_possible_rootjmp.L9.L25:mov0x60(%esi),%ecxsub$0x1,(%ecx)jnz.L26mov$0x1,0x68(%esi)mov$0xec3802b0,(%esi)call rc_dtor_funcjmp.L10.L26:mov0x4(%ecx),%eaxand$0xfffffc10,%eaxcmp$0x10,%eaxjnz.L10call gc_possible_rootjmp.L10

In comparison to V8, HHVM, PyPy and most others modern JIT implementations PHP JIT is extremely simple, but anyway it increases the level of the whole PHP complexity, risk of new kind of bugs and cost of development and maintenance.

Backward Incompatible Changes

none

Proposed PHP Version(s)

PHP 8 and PHP 7.4 (separate votes)

RFC Impact

To SAPIs

none

To Existing Extensions

JIT is going to affect third party debuggers (e.g. xdebug) and profilers (e.g. XHProf, Blackfire, Tideways).

For debugging a particular request, it's possible to disable JIT (together with opcache) changing “opcache.enable” through CAPI (zend_alter_ini_entry) at RINIT stage.

Run-time profiling should work even with JIT-ed code, but this might require development of additional tracingAPI and corresponding JIT extension, to generate tracing callbacks.

To Opcache

New Constants

php.ini Defaults

If there are any php.ini settings then list:

Performance

JIT Debugging

As any complication, JIT increases risk of bugs in JIT itself. They may be caused by inaccurate analyses, bugs in code-generator or register-allocator. Fixing these new kind of bugs is going to be more difficult, because we'll have to catch the place of the failure, get and analyse the assemble code generated for bogus function, find the mistake and understand why it was done by JIT compiler.

In case of crash, we may just run app under gdb until the crash, check that JIT is involved in crash backtrace and find the place:

$ gdb php (gdb) r app.php...(gdb) bt#1  0xe960dc11 in ?? ()#2  0x08689524 in zend_execute (op_array=0xf4074460, return_value=0x0) at Zend/zend_vm_execute.h:69122#3  0x085cb93b in zend_execute_scripts (type=8, retval=0x0, file_count=3) at Zend/zend.c:1639#4  0x0855a890 in php_execute_script (primary_file=0xffffcbfc) at main/main.c:2607#5  0x0868ba25 in do_cli (argc=2, argv=0x9035820) at sapi/cli/php_cli.c:992#6  0x0868c65b in main (argc=2, argv=0x9035820) at sapi/cli/php_cli.c:1384

Unknown function “??” called from zend_execute() is a JIT-ed code. We may determine the failure location analysing execution context.

(gdb) p (char*)executor_global.current_execute_data.func.op_array.filename.val(gdb) p executor_global.current_execute_data.opline.lineno

Line number may be inaccurate, because JIT doesn't keep “opline” in consistency.We may disassemble the code around the bogus instruction to understand the real “opline”.

(gdb) disassemble 0xe960dc00,0xe960dc30

Also, it may be useful to analyse bytecode and assembler dump of the bogus JIT-ed function.

$ php --opcache.jit_debug=1 app.php$ php --opcache.jit_debug=2 app.php

To catch the mistake, we might need to trace the JIT code generator (when it generates the bogus code), or instrument it to generate breakpoint (int3 x86 instruction) and then trace the generated code.

PHP JIT may use GDBAPI to provide information about generated code to debugger. However, it works only for reasonable small scripts. In case of big amount of JIT-ed code, GDB just stuck registering functions. In case we can isolate the bogus code, we may debug JIT in more comfortable way.

$ gdb php (gdb) r -dopcache.jit_debug=0x100 test.php...(gdb) bt#1  0xe960dc11 in JIT$foo () at test.php:2#2  0x08689524 in zend_execute (op_array=0xf4074460, return_value=0x0) at Zend/zend_vm_execute.h:69122#3  0x085cb93b in zend_execute_scripts (type=8, retval=0x0, file_count=3) at Zend/zend.c:1639#4  0x0855a890 in php_execute_script (primary_file=0xffffcbfc) at main/main.c:2607#5  0x0868ba25 in do_cli (argc=2, argv=0x9035820) at sapi/cli/php_cli.c:992#6  0x0868c65b in main (argc=2, argv=0x9035820) at sapi/cli/php_cli.c:1384(gdb) disassemble...(gdb) layout asm

State and compatibility

Future Scope

Proposed Voting Choices

Support for JIT is more a strategic PHP question. JIT definitely requires a lot of work, but it may be actively developed only as a part of PHP, with common effort.

This project requires a 2/3+1 majority. Voting opened 2019-03-21 and closes 2019-03-28.

Include JIT into PHP 8?
Real nameYesNo
ab 
ashnazg 
beberlei 
brandon 
bwoebi 
carusogabriel 
cmb 
cpriest 
dams 
danack 
derick 
diegopires 
dmitry 
duncan3dc 
emir 
galvao 
guilhermeblanco 
jhdxr 
jmikola 
jpauli 
jwage 
kalle 
klaussilveira 
krakjoe 
laruence 
lcobucci 
levim 
malukenho 
mariano 
mbeccati 
mike 
narf 
neeke 
nikic 
ocramius 
pajoye 
peehaa 
petk 
pmmaga 
pollita 
remi 
reywob 
rtheunissen 
salathe 
sammyk 
stas 
svpernova09 
tianfenghan 
wjx 
yunosh 
zeev 
zimt 
Final result:502
This poll has been closed.

As PHP 7.4 is already branched and its engine is not expected to be significantly changed (consequently requiring corresponding changes to the JIT implementation), we can also consider including JIT in PHP-7.4 as an experimental feature (disabled by default), to provide early access and receive more feedback. This also requires a 2/3+1 majority.

In case JIT is not included in PHP-7.4 and PHP-8 introduces language compatibility breaks (it already does), existing applications couldn't be tested with JIT without porting to PHP-8.

Include JIT into PHP 7.4 (experimental)?
Real nameYesNo
ab 
ashnazg 
beberlei 
brandon 
bwoebi 
carusogabriel 
cpriest 
dams 
danack 
derick 
diegopires 
dmitry 
emir 
girgias 
guilhermeblanco 
ircmaxell 
jhdxr 
jmikola 
jpauli 
jwage 
kalle 
kelunik 
klaussilveira 
krakjoe 
laruence 
lcobucci 
levim 
malukenho 
mariano 
mbeccati 
mike 
narf 
neeke 
nikic 
ocramius 
pajoye 
peehaa 
petk 
pmmaga 
pollita 
rasmus 
remi 
reywob 
rtheunissen 
salathe 
sammyk 
sebastian 
stas 
svpernova09 
tianfenghan 
wjx 
yunosh 
zeev 
zimt 
Final result:1836
This poll has been closed.

Patches and Tests

  1. https://github.com/zendtech/php-src/ - The PHP JIT branch was announced more than two years ago, and since that time was kept in consistency with PHP master.

Implementation

Merged into PHP master by9a06876072b9ccb023d4a14426ccb587f10882f3 commit

References

rfc/jit.txt · Last modified: by127.0.0.1



Table of Contents


[8]ページ先頭

©2009-2026 Movatter.jp