什么是opcode
也许你曾经尝试过用C/C++编写动态内容,虽然开发过程极其繁琐,但为了获得性能提升,这样做或许是值得的,它们可以将动态内容编译成二进制可执行文件,也就是目标代码,由操作系统进程直接装载运行。如今已经很少有人使用C/C++编写动态内容了,绝大多数的Web开发人员享受着当下的幸福时光,很多优秀的脚本语言可供选择,比如PHP,Ruby,Python,它们都属于解释型语言,所以用它们编写的动态内容都需要依赖响应的解释器程序来运行。
解释器程序也是一个二进制可执行文件,比如/bin/ruby,它同样可以直接在进程中运行,在运行过程中,解释器程序需要对输入的脚本代码进行分析,领会它们的旨意,然后执行它们。比如下面我们调用PHP的解释器,让它执行一段简单的脚本代码:
~ zfs$ php -r 'print 1+1;'
很好,它很快的输出了正确的结果,也许你觉的1+1的计算太小儿科,的确,在人类的大脑中计算1+1是很简单,几乎不用思考,但解释器的工作方式可不是你想象的那样,1+1和100+1000对它来说几乎没有什么区别,因为解释器核心引擎根本看不懂这些脚本代码,无法直接执行,所以需要进行一系列的代码分析工作,当解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码(Operate Code,opcode)。
从程序代码到中间代码的这个过程,我们称为解释(parse),它由解释器来完成。如此相似的是,编译型语言中的编译器(如C语言的编译器gcc),也要将程序代码生成中间代码,这个过程我们称为编译(compile)。编译器和解释器的一个本质不同在于,解释器在生成中间代码后,便直接执行它,所以运行时的控制权在解释器;而编译器则将中间代码进一步优化,生成可以直接运行的目标程序,但不执行它,用户可以在随后的任意时间执行它,这时的控制权在目标程序,和编译器没有任何关系。
事实上,就解释和编译本身而言,它们的原理是相似的,都包括词法分析,语法分析,语义分析等,所以,有些时候将解释型语言中生成opcode的过程也称为"编译",需要你根据上下文来理解。
那么,opcode究竟是什么样的呢? 它又是如何解释生成的呢? 我们以PHP为例,来寻找这些答案。
PHP解释器的核心引擎为Zend Engine,可以很容易的查看它的版本:
- ~ zfs$ php -v
- PHP 5.5.14 (cli) (built: Sep 9 2014 19:09:25)
- Copyright (c) 1997-2014 The PHP Group
- Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
还记得前面我们曾经用PHP计算1+1的脚本代码吗? 我们来看看这段代码的opcode。在此之前,需要安装PHP的Parsekit扩展,它是一个用C编写的二进制扩展,由PECL来维护。有了Parsekit扩展后,我们就可以通过它提供的运行时API来查看任何PHP文件或者代码段的opcode。我们直接在命令行中调用parsekit_compile_string(),如下所示:
/usr/local/php/bin/php -r "var_dump(parsekit_compile_string('print 1+1;'));"
这样一来,我们便获得了这段代码的opcode,返回的是数组形式,结果如下所示:
- array(20) {
- ["type"]=>
- int(4)
- ["type_name"]=>
- string(14) "ZEND_EVAL_CODE"
- ["fn_flags"]=>
- int(0)
- ["num_args"]=>
- int(0)
- ["required_num_args"]=>
- int(0)
- ["pass_rest_by_reference"]=>
- bool(false)
- ["uses_this"]=>
- bool(false)
- ["line_start"]=>
- int(0)
- ["line_end"]=>
- int(0)
- ["return_reference"]=>
- bool(false)
- ["refcount"]=>
- int(1)
- ["last"]=>
- int(5)
- ["size"]=>
- int(5)
- ["T"]=>
- int(2)
- ["last_brk_cont"]=>
- int(0)
- ["current_brk_cont"]=>
- int(4294967295)
- ["backpatch_count"]=>
- int(0)
- ["done_pass_two"]=>
- bool(true)
- ["filename"]=>
- string(17) "Parsekit Compiler"
- ["opcodes"]=>
- array(5) {
- [0]=>
- array(8) {
- ["address"]=>
- int(33847532)
- ["opcode"]=>
- int(1)
- ["opcode_name"]=>
- string(8) "ZEND_ADD"
- ["flags"]=>
- int(197378)
- ["result"]=>
- array(3) {
- ["type"]=>
- int(2)
- ["type_name"]=>
- string(10) "IS_TMP_VAR"
- ["var"]=>
- int(0)
- }
- ["op1"]=>
- array(3) {
- ["type"]=>
- int(1)
- ["type_name"]=>
- string(8) "IS_CONST"
- ["constant"]=>
- &int(1)
- }
- ["op2"]=>
- array(3) {
- ["type"]=>
- int(1)
- ["type_name"]=>
- string(8) "IS_CONST"
- ["constant"]=>
- &int(1)
- }
- ["lineno"]=>
- int(1)
- }
- [1]=>
- array(7) {
- ["address"]=>
- int(33847652)
- ["opcode"]=>
- int(41)
- ["opcode_name"]=>
- string(10) "ZEND_PRINT"
- ["flags"]=>
- int(770)
- ["result"]=>
- array(3) {
- ["type"]=>
- int(2)
- ["type_name"]=>
- string(10) "IS_TMP_VAR"
- ["var"]=>
- int(1)
- }
- ["op1"]=>
- array(3) {
- ["type"]=>
- int(2)
- ["type_name"]=>
- string(10) "IS_TMP_VAR"
- ["var"]=>
- int(0)
- }
- ["lineno"]=>
- int(1)
- }
- [2]=>
- array(7) {
- ["address"]=>
- int(33847772)
- ["opcode"]=>
- int(70)
- ["opcode_name"]=>
- string(9) "ZEND_FREE"
- ["flags"]=>
- int(271104)
- ["op1"]=>
- array(4) {
- ["type"]=>
- int(2)
- ["type_name"]=>
- string(10) "IS_TMP_VAR"
- ["var"]=>
- int(1)
- ["EA.type"]=>
- int(0)
- }
- ["op2"]=>
- array(3) {
- ["type"]=>
- int(8)
- ["type_name"]=>
- string(9) "IS_UNUSED"
- ["opline_num"]=>
- string(1) "0"
- }
- ["lineno"]=>
- int(1)
- }
- [3]=>
- array(7) {
- ["address"]=>
- int(33847892)
- ["opcode"]=>
- int(62)
- ["opcode_name"]=>
- string(11) "ZEND_RETURN"
- ["flags"]=>
- int(16777984)
- ["op1"]=>
- array(3) {
- ["type"]=>
- int(1)
- ["type_name"]=>
- string(8) "IS_CONST"
- ["constant"]=>
- &NULL
- }
- ["extended_value"]=>
- int(0)
- ["lineno"]=>
- int(1)
- }
- [4]=>
- array(5) {
- ["address"]=>
- int(33848012)
- ["opcode"]=>
- int(149)
- ["opcode_name"]=>
- string(21) "ZEND_HANDLE_EXCEPTION"
- ["flags"]=>
- int(0)
- ["lineno"]=>
- int(1)
- }
- }
- }
parsekit扩展的安装请参见这篇文章
系统缓存
它是指APC把PHP文件源码的编译结果缓存起来,然后在每次调用时先对比时间标记。如果未过期,则使用缓存的中间代码运行。默认缓存3600s(一小时)。但是这样仍会浪费大量CPU时间。因此可以在php.ini中设置system缓存为永不过期(apc.ttl=0)。不过如果这样设置,改运php代码后需要重启WEB服务器。目前使用较多的是指此类缓存。
用户数据缓存
缓存由用户在编写PHP代码时用apc_store和apc_fetch函数操作读取、写入的。如果数据量不大的话,可以一试。如果数据量大,使用类似memcache此类的更加专著的内存缓存方案会更好。
APC模块的安装
最简单的方法是直接使用pecl,在命令行下输入:/usr/local/php/bin/pecl install apc
然后按照提示一步步完成即可,示例如下:
- [root@iZ23bm1tc0pZ ~]# /usr/local/php/bin/pecl install apc
- downloading APC-3.1.13.tgz ...
- Starting to download APC-3.1.13.tgz (171,591 bytes)
- .....................................done: 171,591 bytes
- 55 source files, building
- running: phpize
- Configuring for:
- PHP Api Version: 20100412
- Zend Module Api No: 20100525
- Zend Extension Api No: 220100525
- Enable internal debugging in APC [no] : no
- Enable per request file info about files used from the APC cache [no] : no
- Enable spin locks (EXPERIMENTAL) [no] : no
- Enable memory protection (EXPERIMENTAL) [no] : no
- Enable pthread mutexes (default) [no] : no
- Enable pthread read/write locks (EXPERIMENTAL) [yes] : yes
然后重启服务器即可:
lnmp nginx restart
先看一下没有使用apc情况下的压测结果:
- [root@iZ23bm1tc0pZ ~]# ab -n1000 -c100 http://Vevb.com/index.php
- This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
- Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.Vevb.com/
- Licensed to The Apache Software Foundation, http://www.apache.org/
- Benchmarking zfsphp.cn (be patient)
- Completed 100 requests
- Completed 200 requests
- Completed 300 requests
- Completed 400 requests
- Completed 500 requests
- Completed 600 requests
- Completed 700 requests
- Completed 800 requests
- Completed 900 requests
- Completed 1000 requests
- Finished 1000 requests
- Server Software: nginx
- Server Hostname: zfsphp.cn
- Server Port: 80
- Document Path: /index.php
- Document Length: 14341 bytes
- Concurrency Level: 100
- Time taken for tests: 15.517 seconds
- Complete requests: 1000
- Failed requests: 0
- Total transferred: 14544000 bytes
- HTML transferred: 14341000 bytes
- Requests per second: 64.45 [#/sec] (mean)
- Time per request: 1551.671 [ms] (mean)
- Time per request: 15.517 [ms] (mean, across all concurrent requests)
- Transfer rate: 915.34 [Kbytes/sec] received
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 2 4.8 0 17
- Processing: 46 1481 277.0 1560 1638
- Waiting: 42 1481 277.1 1560 1638
- Total: 58 1482 272.8 1560 1638
- Percentage of the requests served within a certain time (ms)
- 50% 1560
- 66% 1576
- 75% 1582
- 80% 1587
- 90% 1602
- 95% 1612
- 98% 1622
- 99% 1629
- 100% 1638 (longest request)
可见最大吞吐率只有64.45reqs/s,然后我们开启apc,测试结果如下:
- [root@iZ23bm1tc0pZ ~]# ab -n1000 -c100 http://Vevb.com/index.php
- This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
- Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.Vevb.com/
- Licensed to The Apache Software Foundation, http://www.apache.org/
- Benchmarking zfsphp.cn (be patient)
- Completed 100 requests
- Completed 200 requests
- Completed 300 requests
- Completed 400 requests
- Completed 500 requests
- Completed 600 requests
- Completed 700 requests
- Completed 800 requests
- Completed 900 requests
- Completed 1000 requests
- Finished 1000 requests
- Server Software: nginx
- Server Hostname: Vevb.com
- Server Port: 80
- Document Path: /index.php
- Document Length: 14341 bytes
- Concurrency Level: 100
- Time taken for tests: 7.122 seconds
- Complete requests: 1000
- Failed requests: 0
- Total transferred: 14544000 bytes
- HTML transferred: 14341000 bytes
- Requests per second: 140.41 [#/sec] (mean)
- Time per request: 712.189 [ms] (mean)
- Time per request: 7.122 [ms] (mean, across all concurrent requests)
- Transfer rate: 1994.29 [Kbytes/sec] received
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 1 2.4 0 10
- Processing: 23 677 125.3 705 775
- Waiting: 22 677 125.4 705 775
- Total: 30 678 123.1 705 775
- Percentage of the requests served within a certain time (ms)
- 50% 705
- 66% 719
- 75% 726
- 80% 730
- 90% 742
- 95% 750
- 98% 760
- 99% 765
- 100% 775 (longest request)
[root@iZ23bm1tc0pZ ~]# ab -n1000 -c100 http://Vevb.com/index.php
新闻热点
疑难解答