博采众长,精于一技。Live for love, work for dream.

用Zephir编写PHP扩展

自从NodeJS,和Golang出来后,很多人都投奔过去了。不为什么,冲着那牛X的性能。那PHP的性能什么时候能提升一下呢?要不然就会被人鄙视了。其实大牛们也深刻体会到了这些威胁,于是都在秘密开发各种秘密武器。

HHVM和Hack
Facebook自己弄了一套HHVM虚拟机,和一个新语言Hack。HHVM的性能不错,像Wordpress,PHPMyAdmin这样的项目,运行在上面很流畅,但是有个问题很致命,如果你引入了第三方扩展,现有的代码移植过去,没办法运行。如果你希望你的代码运行于HHVM,那么你需要编写基于HHVM的扩展,这时候你要学习C++了。很抓狂有木有?又或者,用Hack重写你的代码,这事谁会干呢?新项目可能可以拿来玩玩,但是旧的项目,如果代码量大,且代码混乱的项目,那就只能呵呵了。

PHPNG(next generation)
前些日子PHP核心开发组宣布了个利好消息,将在PHP 5.7版本会有很大的性能提升。这一次又打击到HHVM了。但是要等到5.7的版本才会发布。现在很多都只是停留在5.4的版本,鞭长莫及啊。

除了这些,就没有办法提升性能了吗?有!------ PHP扩展。

Phalcon没听过,Yaf总听过了吧。什么?都没听过?赶紧去Google下,听说面试会加分。

PHP扩展
PHP的流行,得益于它的扩展系统。开发者通过为PHP开发扩展,通过这个中间件,跟其他系统连接通信。例如我们常用的cURL,Memcache和Redis等扩展。这些扩展不包含在PHP核心,需要额外编译。这里有一份官方列出的PHP扩展列表:
http://www.php.net/manual/en/extensions.alphabetical.php

如果你想自己编写PHP扩展,意味着你需要掌握C语言,因为PHP的扩展是通过C编写的,而且你还需要掌握PHP的Zend API,了解它的核心原理。如果你有兴趣,可以参考:《深入理解PHP内核》。如果你懂C,那么你看完上面这本书,那么你大概也能写了。但是,对于C语言水平比较菜,或者不怎么懂C的人来说,就只能望而却步了。

为什么我要写PHP扩展呢?

  1. 访问现有的库。假设现在有一个库很好用(例如MongoDB),你希望在PHP也能用上它。如果这个库很热门,那么你就走运了,应该有大牛帮你实现了。要是运气不好,这个库比较冷门,但你业务需求又刚好需要用到的话,那只能干着急了(当然这种情况是极少出现的)。

  2. 性能。PHP是动态语言,代码性能比C语言相差一个级别。正是由于此原因,产生了Yaf,Phalcon这样的PHP的扩展框架。

对于那些不想学C,但又想要得到编写自己的扩展,怎么办?

Zephir
分析了以上的种种不靠谱,终于进入了正题。现在隆重向你介绍一个叫Zephir的项目。它可以帮助你使用类PHP的语法,来生成C语言代码,并帮助你编译成PHP扩展。是不是很酷?很酷,有没有?

Show Me The Code
如果你用C写一个Hello World的扩展,那么你需要这样写:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_test.h"
#include "test.h"

#include "kernel/main.h"

/**
 * This is a sample class
 */
ZEPHIR_INIT_CLASS(Test_Hello) {
    ZEPHIR_REGISTER_CLASS(Test, Hello, hello, test_hello_method_entry, 0);
    return SUCCESS;
}

/**
 * This is a sample method
 */
PHP_METHOD(Test_Hello, say) {
    php_printf("%s", "Hello World!");
}

而Zephir则只需要这样:

namespace Test;

/**
 * This is a sample class
 */
class Hello
{
    /**
     * This is a sample method
     */
    public function say()
    {
        echo "Hello World!";
    }
}

是不是跟写PHP代码没什么区别?

安装 Zephir

$ git clone https://github.com/json-c/json-c.git
$ cd json-c
$ sh autogen.sh
$ ./configure
$ make && sudo make install
$ git clone https://github.com/phalcon/zephir
$ cd zephir
$ ./install -c

安装完成后,运行

$ zephir help

如果没有报错,说明你已经安装成功了。

Zephir语法
Zephir跟PHP有几点区别:

  1. Zephir是强类型语言。变量有自己的类型。
namespace Test;

class Arithmetic
{
    public function intSum()
    {
        int a, b, c;

        let a = 1,
            b = 2,
            c = a + b;

        return c;
    }
}

这里需要特别注意的是Zephir有个let关键字,用于变量赋值。

编译扩展

  1. 初始化一个Zephir扩展
zephir init myframework
  1. 新建一个叫 calculator.zep的文件
namespace Myframework;
class Calculator {
    public function add(int a, int b) {
        return a + b;
    }
}

Zephir必须指定一个命名空间,上面的例子Myframework为这次Demo的命名空间。Zephir遵循PSR-1的标准进行命名。

  1. 把Zephir代码编译成PHP的C扩展
zephir build
  1. 开启扩展
    在你的php.ini文件加上
extension=myframework.so
  1. 测试
$ php -a
php > $calc = new Myframework\Calculator;
php > var_dump($calc->add(2, 1));
int(3)

是不是很简单?你也来尝试一下吧。

工程师与特种兵

最近看了《码农周刊》里面一篇关于云风的报道,有感而发,于是有了此文。

特种兵
组成特种部队单位人员,是世界一些国家军队中担负破袭敌方重要的政治、经济、军事目标和执行其他特殊任务的特殊兵种。单兵作战能力极强,适合在各种恶劣条件下,完成作战任务。往往是战争中决定战局的重要因素。

工程师中的特种兵
像云风这样的自由人,在现在的公司体系里面,是绝无仅有的。但是,像军队这样的机构都有一个特种兵的部门,在公司的体系里面,这样的组织能不能效仿呢?
可能你会想到,盛大创新院(解散了),网易研究院。是的,但,可能跟我所理解的不太一样。

理想状态下的工程师特种兵
平时的工作内容:

  1. 协助团队成员做代码审核;
  2. 新兵训练营和技术分享;
  3. 解决团队内部各种疑难杂症;
  4. 研究前沿技术,并应用到实际系统当中;
  5. 其他待定。

团队组成:
全栈工程师或精通某一技术的专家。初期人员可由架构师或资深工程师组成,每年进行技术选拔或评估,进行晋升,不接受外聘和空降,只从内部进行提拔。

什么公司需要这样的团队
个人认为,工程师人员规模至少要达到200人以上,才有必要。因为到了那个层次,人才培养和晋升问题会突显,系统问题也会日益暴露,这时候就需要工程师“消防员”去救火。

延伸阅读

  1. 云风:一个编程的自由人(图灵访谈)
  2. 特种兵 百度百科
  3. 盛大创新院300高手的失败
  4. 从Code Review 谈如何做技术
  5. 调教你的新工程师 – 谈新兵训练营

以上是我描绘的美好蓝图,不知道有没有公司跟我的想法相近呢?又或者已经存在?如果你觉得我的想法太可笑,呵呵,那就仅当个笑话看吧。

Nginx设置PHP的PATH_INFO

默认情况下,Nginx不支持PATH_INFO,但是PHP的路由规则需要用到这个变量。配置这个的时候浪费了不少时间,于是记录一下。

server {
    listen 80;
    server_name demo.com;
    root /path/to/document/root/demo;

    location / {
        index index.html index.php;

        try_files $uri $uri @php_fpm;
    }

    location @php_fpm {
        rewrite ^(.*)$ /index.php$1 last;
    }

    location ~ ^(.+\.php)(.*)$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_split_path_info       ^(.+\.php)(/.+)$;
        fastcgi_param  PATH_INFO          $fastcgi_path_info;
    }

    error_log /var/log/nginx.log;
}

重点在
fastcgi_split_path_info       ^(.+\.php)(/.+)$;
fastcgi_param  PATH_INFO          $fastcgi_path_info;

这两行。

PHP浮点数的精度问题

先看问题:

$f = 0.58;
var_dump(intval($f * 100)); //为啥输出57

我相信有很多的同学有过这样的疑问。

具体原理可阅读“鸟哥”的一篇文章,那里有详细的解说:PHP浮点数的一个常见问题的解答

那么如何避免这种问题呢?
办法有很多,这里列举两个:
1. sprintf

substr(sprintf("%.10f", ($a/ $b)), 0, -7);

2. round (注意会进行四舍五入)

round($a/$b, 3);

或者你有更好的办法,也可以了留言告诉我。

MySQL insert性能优化


对于一些数据量较大的系统,面临的问题除了是查询效率低下,还有一个很重要的问题就是插入时间长。我们就有一个业务系统,每天的数据导入需要4-5个钟。这种费时的操作其实是很有风险的,假设程序出了问题,想重跑操作那是一件痛苦的事情。因此,提高大数据量系统的MySQL insert效率是很有必要的。

经过对MySQL的测试,发现一些可以提高insert效率的方法,供大家参考参考。

1. 一条SQL语句插入多条数据。
常用的插入语句如: INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('1', 'userid_1', 'content_1', 1);

修改成: INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0), ('1', 'userid_1', 'content_1', 1);

修改后的插入操作能够提高程序的插入效率。这里第二种SQL执行效率高的主要原因有两个,一是减少SQL语句解析的操作, 只需要解析一次就能进行数据的插入操作,二是SQL语句较短,可以减少网络传输的IO。

这里提供一些测试对比数据,分别是进行单条数据的导入与转化成一条SQL语句进行导入,分别测试1百、1千、1万条数据记录。

记录数单条数据插入多条数据插入
1百0.149s0.011s
1千1.231s0.047s
1万11.678s0.218s

2. 在事务中进行插入处理。
把插入修改成: START TRANSACTION;
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('1', 'userid_1', 'content_1', 1);
...
COMMIT;

使用事务可以提高数据的插入效率,这是因为进行一个INSERT操作时,MySQL内部会建立一个事务,在事务内进行真正插入处理。通过使用事务可以减少数据库执行插入语句时多次“创建事务,提交事务”的消耗,所有插入都在执行后才进行提交操作。

这里也提供了测试对比,分别是不使用事务与使用事务在记录数为1百、1千、1万的情况。

记录数不使用事务使用事务
1百0.149s0.033s
1千1.231s0.115s
1万11.678s1.050s

性能测试:
这里提供了同时使用上面两种方法进行INSERT效率优化的测试。即多条数据合并为同一个SQL,并且在事务中进行插入。
记录数单条数据插入合并数据+事务插入
1万0m15.977s0m0.309s
10万1m52.204s0m2.271s
100万18m31.317s0m23.332s

从测试结果可以看到,insert的效率大概有50倍的提高,这个一个很客观的数字。
注意事项:

1. SQL语句是有长度限制,在进行数据合并在同一SQL中务必不能超过SQL长度限制,通过max_allowed_packe配置可以修改,默认是1M。

2. 事务需要控制大小,事务太大可能会影响执行的效率。MySQL有innodb_log_buffer_size配置项,超过这个值会日志会使用磁盘数据,这时,效率会有所下降。所以比较好的做法是,在事务大小达到配置项数据级前进行事务提交。

原文地址:http://blog.csdn.net/tigernorth/article/details/8094277