PHP中使用文件锁

 
  1. <?php
  2. $lock_file = "/tmp/logging.lock";
  3. if($should_do_sth) {
  4.     $fp = @fopen($lock_file"w+");
  5.     if ($fp && flock($fp, LOCK_EX)) {
  6.         do_sth();
  7.         flock($fp, LOCK_UN);
  8.         fclose($fp);
  9.         @unlink($lock_file);  // 1.  这里删除自己创建的文件,避免因属主权限问题导致其他进程无法打开该所文件 2. 这里也可能多进程间重复删除,所以使用 @
  10.     }
  11. }

form 表单的text/plain 编码方式

form表单的enctype属性可以设置表单提交时表单内容的编码方式,可用的编码方式有:

  enctype
When the value of the method attribute is post, this attribute is the MIME type of content that is used to submit the form to the server. Possible values are:

  • application/x-www-form-urlencoded: The default value if the attribute is not specified.
  • multipart/form-data: Use this value if you are using an <input>  element with the type attribute set to "file".
  • text/plain (HTML5)

其中, text/plain 很少使用,其编码方式如下:

关于PHP中popen使用的一点注意

引子:
PHP的popen可以并发调起多个进程吗?

我们做如下测试:
测试1

分析1
我们发现,这里只输出了"a", 没有输出"b",说明sleep 100;执行了, sleep 200; 没有立即执行

测试2

分析2
这里a、b都输出了,说明两条命令都被调起执行了;同时,我们也发现,主进程并没有立即退出,而是在等待所有子进程执行完毕,如果使用 "&" 将启动的命令推倒后台,主进程也就不等待了。

测试3

分析3
这里a没有输出,说明如果不将popen赋值给一个变量,则进程阻塞,如果使用 "&" 将启动的命令推倒后台,则亦不阻塞。

关于ttserver的异常退出

使用Tokyotrant有相当一段时间了,确实有几次莫名的退出,今天再次出现,查了一下系统日志,报错如下:
$grep ttserver /var/log/messages
Jun 18 22:00:42 localhost kernel: [2463228.773583] ttserver[32282] general protection rip:2b6557922dcb rsp:4a0b2c08 error:0

 

又出现一次;

Jul 18 07:00:21 localhost kernel: [11461817.641400] ttserver[612]: segfault at 000000001ffc2000 rip 0000003f5407c08f rsp 000000004732ec08 error 4

关于PHP中配置文件的定义

定义方法1
—– conf.php ——

  1. <?php
  2. $conf = "hello world";
  3. ?>

引用配置文件:
—– test.php ——

  1. <?php
  2. test();
  3. function test() {
  4.     include("conf.php");
  5.     echo $conf;
  6. }
  7. ?>

———————
分析
如果代码就这么简单,我们发现程序工作的很好,但是:
1. 如果$conf文件很大,每次都include势必浪费很多时间
2. 如果把include修改为include_once,则情况就会变得怪异了; 如果外部不小心include的了一次conf.php ,则test中不再执行conf.php,则test() 不能正常工作; 如果保证外面没有include("conf.php"); 则test() 第一次可以工作正常,第二次就无法正常工作,因为第二次执行test()函数并没有执行conf.php,也就没有$conf变量
3. conf.php 中 $conf 变量的作用域是不固定的,依赖于conf.php 的执行环境,所以简单地把 $conf看做是global的,更是容易犯错误的

结论: 这样写配置文件很容易犯错误,所以不要这样定义配置文件

定义方法2
——- conf.php —–

  1. <?php
  2. $GLOBALS["conf"] = "hello phpor";
  3. ?>

引用配置文件:
——- test.php ——-

  1. <?php
  2. test();
  3. function test() {
  4.     include_once("conf.php");
  5.     echo $GLOBALS["conf"];
  6. }
  7. ?>

分析
1. 配置文件包含进来之后,将一直占用内存空间,所以配置文件不宜太大,不过一般都不太太大
2. 为了提高效率,这里最好使用include_once, 而不是include
3. 这种写法不管怎么调用都不会有问题
4. 需要注意一点的是: 如果配置文件确实很大,解析该配置文件需要10毫秒, 而且不是每次请求都会用到大部分配置,或许整个请求之间只用到了其中1/10的配置,但是还是不得不耗费10ms来解析整个配置文件; 如果确实这这种情况,不妨考虑第三种配置方法

定义方法3
——- conf.php ——

 
  1. <?php
  2. class conf {
  3.     private static $cache = array();
  4.     public static function get($alias) {
  5.         if (isset(self::$cache[$alias])) return self::$cache[$alias];
  6.         $method = "get_$alias";
  7.         if (!method_exists(self, $method)) return array();
  8.         return self::$method();
  9.     }
  10.     private static function get_conf1() {
  11.         return self::$cache["conf1"] = "hello phpor";
  12.     }
  13.     private static function get_conf2() {
  14.         return self::$cache["conf2"] = "maybe this is a big array";
  15.     }
  16. }
  17. ?>

引用配置文件:
——- test.php ——-

  1. <?php
  2. include_once("conf.php");  // 对于应用了PHP加速器的程序来讲,因为conf.php中没有包含就执行的语句,所以使用include或者include_once也没什么区别
  3. test();
  4. function test() {
  5.    var_dump(conf::get("conf1"));
  6. }
  7. ?>

分析
1. 这种办法似乎规避了前两种写法的所有缺点
2. 我们还可以在配置文件中添加一些逻辑,避免代码的重复

关于PHP中的变量作用于问题

引子:
有文件1.php:

  1. <?php
  2. $conf = array("hello world");
  3. ?>

文件2.php

  1. <?php
  2. include_once("1.php");
  3. function do_sth() {
  4.      global $conf;
  5.      print_r($conf);
  6. }
  7. ?>

文件3.php

 
  1. <?php
  2. include_once("2.php");
  3. do_sth(); // 这里能不能打印出数组内容取决于3.php的上下文环境
  4. ?>

直接执行 3.php 可以打印出数组内容。

下面使用文件4.php来include文件3.php
文件4.php

  1. <?php
  2. wrap(); // 这里不能打印出$conf数组的内容
  3. function wrap() {
  4.     include_once("3.php");
  5. }
  6. ?>

分析
1) 因为1.php 中的$conf数组没有使用global声明,所以1.php中 $conf数组的作用于取决于 1.php的上下文环境;
2) 3.php 直接执行的话,工作的很好,但是一旦被一个函数给包裹住,则$conf 数组不再是一个全局变量,这样do_str() 也就不能按照预期执行了
3) 如果在1.php中也使用global声明一下 $conf , 然后再赋值,就比较靠谱了

结论
1. 不要指望在任何函数外部声明的变量就是全局的
2. 虽然使用global 声明后同样可以确保是一个全局变量,但是这里还是建议使用 $GLOBALS 数组来访问全局变量

关于PHP加速器APC的使用

PHP加速器APC除了缓存字节码,还有一个很重要的应用就是 apc_store, 通常会将配置信息使用apc 缓存起来,更多时候是我们发现配置信息的数组太大了,才考虑将整个数组使用apc缓存起来。

下面我们明确一点基本的知识: apc缓存PHP的数组是序列化之后存储的,

下面我们做一个测试:
测试代码:

 
  1. <?php
  2. test_exec_code();
  3. test_unserialize();
  4. test_apc_fetch_arr();
  5. test_apc_fetch_str();
  6. test_apc_fetch_str_then_unserialize();
  7. function test_exec_code() {
  8.         $s = microtime(1);
  9.         while($i++<1000) {
  10.                 $arr = getData();
  11.         }
  12.         echo "exec time:", microtime(1) – $s , "s\n";
  13. }
  14. function test_unserialize() {
  15.         $arr = getData();
  16.         $str = serialize($arr);
  17.         $s = microtime(1);
  18.         $i = 0;
  19.         while($i++<1000) {
  20.                 $arr = unserialize($str);
  21.         }
  22.         echo "unserialize time:", microtime(1) – $s , "s\n";
  23. }
  24. function test_apc_fetch_arr() {
  25.         $arr = getData();
  26.         apc_store("arr"$arr);
  27.         $s = microtime(1);
  28.         $i = 0;
  29.         while($i++<1000) {
  30.                 $arr = apc_fetch("arr");
  31.         }
  32.         echo "apc_fetch_arr time:", microtime(1) – $s , "s\n";
  33. }
  34. function test_apc_fetch_str() {
  35.         $arr = getData();
  36.         $str = serialize($arr);
  37.         apc_store("str"$str);
  38.         $s = microtime(1);
  39.         $i = 0;
  40.         while($i++<1000) {
  41.                 $str = apc_fetch("str");
  42.         }
  43.         echo "apc_fetch_str time:", microtime(1) – $s , "s\n";
  44. }
  45. function test_apc_fetch_str_then_unserialize() {
  46.         $arr = getData();
  47.         $str = serialize($arr);
  48.         apc_store("str"$str);
  49.         $s = microtime(1);
  50.         $i = 0;
  51.         while($i++<1000) {
  52.                 $str = apc_fetch("str");
  53.                 $arr = unserialize($str);
  54.         }
  55.         echo "apc_fetch_str_then_unserialize time:", microtime(1) – $s , "s\n";
  56. }
  57. function getData() {
  58.         $arr = array(
  59.             ‘220.181.7.41’,
  60.             ‘113.5.32.130’,
  61.             //…  共 9000 个IP
  62.         );
  63.         return $arr;
  64. }

测试结果:
$ php test.php
exec time:5.3702118396759s
unserialize time:7.4545278549194s
apc_fetch_arr time:50.132069826126s
apc_fetch_str time:0.18340110778809s
apc_fetch_str_then_unserialize time:7.9918370246887s

分析:
1. 不做缓存,每次都执行字节码的效率是最高的,大约每次执行需要 5ms 的时间
2. PHP 反序列话的速度也赶不上执行字节码的速度(至少在这种情况下是这样的)
3. 使用apc缓存数组的效率是相当低的, 每次约 50ms,不如不缓存
4. 使用apc缓存字符串的速度还是不错的,这里的数据量约为260KB,fetch一次的时间约0.18ms
5. 如果说apc的序列化和反序列化使用的是php标准的序列化和反序列化函数,则: apc_fetch_arr 的时间应该基本和 apc_fetch_str_then_unserialize time 的时间一样,但是,这里差别太大了,有些不太理解; 如此看来,如果真要使用apc,则最好先显式地序列化然后在存储,fetch后在显式地反序列化一下

Firefox 禁用cache

httpwatch是个很好的东西,但是对于常用的禁用cache的功能却没有,而Firebug 有禁用cache的功能,后来才发现,Firebug是如此禁用cache的:

Firebug只是一个Firefox的插件,是简单利用了Firefox的一个特性来实现的“禁用浏览器缓存”; 而httpwatch不仅用于Firefoxo,还用于IE,就不方便来如此实现了。

如果你使用的Firefox,但是没有安装Firebug,则不妨通过 about:config 的方式来实现“禁用浏览器缓存”

关于sqlite

1. php的sqlite的模块依然使用的是sqlite2, pdo_sqlite 模块使用的是sqlite3
2. sqlite的写操作是非常慢的,如下:
见表语句:
CREATE TABLE wireless (v varchar(16), PRIMARY KEY(v));

插入10000个IP:

插入速度如下:
$ php sqlite.php -t init
1000:45.657394886017
2000:91.184693813324
3000:133.79960989952
4000:176.86646604538
5000:218.9348089695
6000:261.49800491333
7000:306.47433900833
8000:351.71416902542
9000:397.750207901
9243:408.81617093086

结论:
1. 在所测试的数据量内, 每 1000 条数据需要45s时间, 即 45ms/条  , 应该算是比较慢的。

下面测试一个没有主键时的插入情况:
$ php sqlite.php -t init -d test.db
1000:41.796957969666
2000:83.843362092972
3000:125.97302293777
4000:169.13699889183
5000:212.61609697342
6000:255.99843406677
7000:299.17888689041
8000:339.0949409008
9000:376.1487801075
9243:385.17269802094

结论:
1. 每 1000 条数据需要41s时间, 即 41ms/条, 没有明显变化, 依然很慢
2. 虽然没有索引,但是查询速度却一点也没有变慢

关于黑白名单的实现

就在不经意的时候,我们可能会把一个大的黑白名单直接写在PHP的数组中,然后使用in_array判断指定的值是否在名单内,这样每次查一个值是否存在则都需要将所有的名单值构造成一个数组,然后遍历这个数组来查找,相当于我们要遍历两次名单而且其中一次是需要字符串比较的。

首先补充一点知识:PHP数组的构造是通过执行字节码来完成的,如arr.php:

  1. <?php
  2. $arr = array(
  3.         "a" => "AAA",
  4.         "a1" => array( "AAA","MMM"),
  5.         "b" => "BBB",
  6.         );

编译后生成的字节码如下:

  1. # php -dvld.active=1 d.php
  2. Finding entry points
  3. Branch analysis from position: 0
  4. Return found
  5. filename:       arr.php
  6. function name:  (null)
  7. number of ops:  7
  8. compiled vars:  !0 = $arr
  9. line     # *  op                           fetch          ext  return  operands
  10. ———————————————————————————
  11.    3     0  >   INIT_ARRAY                                       ~0      ‘AAA’, ‘a’
  12.    4     1      INIT_ARRAY                                       ~1      ‘AAA’
  13.          2      ADD_ARRAY_ELEMENT                                ~1      ‘MMM’
  14.          3      ADD_ARRAY_ELEMENT                                ~0      ~1, ‘a1’
  15.    5     4      ADD_ARRAY_ELEMENT                                ~0      ‘BBB’, ‘b’
  16.    6     5      ASSIGN                                                   !0, ~0
  17.    7     6    > RETURN                                                   1
  18. branch: #  0; line:     3-    7; sop:     0; eop:     6
  19. path #1: 0,

下面我们测试一下构造一个数组的效率,测试方法:
实验1. 在函数中构造一个大的数组,第一次执行之前做编译的工作,后续的执行则不需要编译,这样的话,我们调用函数1000次,则我们认为时间基本上是消耗在数组的构造上的;为了测试构造数组的时间,函数中最后return true, 而不是 return in_array($key, $arr);
实现2. 改造程序,将数组定义成static的,则数组只构造一次,其它和实验1相同
总结: (实验1的时间 – 实验2的时间) / 1000 则基本是构造一次数组需要的时间

测试代码test.php如下:

  1. <?php
  2. $s = microtime(1);
  3. for($i = 0 ; $i < 1000; $i++) {
  4.     check($i);
  5. }
  6. echo microtime(1) – $s;
  7. echo "\n";
  8. function check($key) {
  9. $arr = array(  // 一个很大的数组,1万个值的数组
  10. ‘value1’,
  11. ‘value2’,
  12. );
  13. return true;
        
  14. }

$ php test.php
5.1018438339233

执行时间6s

给 $arr 添加static 关键字,代码如下:

  1. <?php
  2. $s = microtime(1);
  3. for($i = 0 ; $i < 1000; $i++) {
  4.     check($i);
  5. }
  6. echo microtime(1) – $s;
  7. echo "\n";
  8. function check($key) {
  9. static $arr = array(  // 一个很大的数组,1万个值的数组
  10. ‘value1’,
  11. ‘value2’,
  12. );
  13. return true;
  14. }
        

执行时间:
#php test.php
0.0016131401062012

总结,根据实验的步骤中的公式来计算,可以知道初始化字节码执行时间约: 5s/1000 = 5ms ; 时间是比较长的

或许你会说如果将value当做key出现,则,可以使用 isset($arr[$key]) 而不是 in_array($key, $arr) 会更快些,应为in_array会遍历数组,而isset则是一个简单的hash就能判断。 但是问题是,如果将value当做key出现,则构造数组的时候,每个value都要做一个hash,这样构造数组的速度会更慢,如果每次构造能使用n次,则不失为一个好的办法,如果每次构造只使用一次,就有些不合算了。

下面对初始化1万个key的数组和1万个value的数组做一个比较:

1. 我们上面已近知道初始化1000次value的数组时间为5.1018438339233s
2. 下面我们通过如下函数测试初始化1000次key的数组的时间:

  1. function check2($key) {
  2.     $arr = array(
  3. "value1" => true,
  4. "value2" => true,
  5. );
  6. return true;
  7. }

#php test.php
4.8184480667114

这里和我预想的不一样了,虽然每个key都要做hash,但是,初始化数组耗费的时间反而更少(虽然差别不大),稍后再研究一下初始化数组的内部逻辑

下面看一下如下代码的字节码:

 
  1. <?php
  2. $arr = array(
  3.     "a" => "AAA",
  4.     "a1" => array"AAA","MMM"),
  5.     "b" => "BBB",
  6.     );
  7. $arr2 = array(1,2,3,4);
 
  1. $ php -dvld.active=1 d.php
  2. Finding entry points
  3. Branch analysis from position: 0
  4. Return found
  5. filename:      xxx/d.php
  6. function name:  (null)
  7. number of ops:  12
  8. compiled vars:  !0 = $arr, !1 = $arr2
  9. line     # *  op                           fetch          ext  return  operands
  10. ———————————————————————————
  11.    3     0  >   INIT_ARRAY                                       ~0      ‘AAA’‘a’
  12.    4     1      INIT_ARRAY                                       ~1      ‘AAA’
  13.          2      ADD_ARRAY_ELEMENT                                ~1      ‘MMM’
  14.          3      ADD_ARRAY_ELEMENT                                ~0      ~1, ‘a1’
  15.    5     4      ADD_ARRAY_ELEMENT                                ~0      ‘BBB’‘b’
  16.    6     5      ASSIGN                                                   !0, ~0
  17.    7     6      INIT_ARRAY                                       ~3      1
  18.          7      ADD_ARRAY_ELEMENT                                ~3      2
  19.          8      ADD_ARRAY_ELEMENT                                ~3      3
  20.          9      ADD_ARRAY_ELEMENT                                ~3      4
  21.         10      ASSIGN                                                   !1, ~3
  22.    8    11    > RETURN                                                   1
  23. branch: #  0; line:     3-    8; sop:     0; eop:    11
  24. path #1: 0, 

似乎没有太大差别。。。