WeakRef 弱引用

WeakRef 弱引用

WeakRef 弱引用

介绍

弱引用, 提供了一个对于短暂存在的对象的一个无打扰的访问方法, 不像是强引用, 若引用不会拒绝 gc 对它的清理, 所以 一个对象已经不存在了,但是这个若引用对象还是存在的, 这样看来若引用是可用的.同样提供了一个方法将若引用转化为强引用

安装

pecl

配置

inclued.enabled int Off PHP_INI_SYSTEM 是否开启本扩展, 由于默认的关闭,必须开启之后才起作用
函数


原文地址 : WeakRef 弱引用
本站是作者语雀文档的镜像站, 如对文章有任何疑问请移步语雀进行 提问

基础学习

version_compare(zend_version(), “2-dev”, “ge”);


第三个参数是操作符参数,而不是另外一个版本


pear私有变量是以’_’开头的变量


PHP4中的is_a()函数不推荐使用,推荐使用 instanceof


PHP5中可以通过foreach()循环的参数加上引用符号,让你在遍历数组的时候更改数组的值


变量的间接引用
$name = ‘john’;
$$name = ‘Rename John’;
echo $john;
显示  Rename John


超全局变量
$_GET   一个包含所有PHP从客户浏览器接受的GET变量的数组
$_POST 一个包含所有PHP从客户浏览器接收的POST变量的数组
$_COOKIE 一个包含所有PHP从客户浏览器接收的cookies的数组
$_ENV     一个包含环境变量的数组
$_SERVER 一个存放web服务器变量的数组


PHP特殊字符串
双引号

\n 换行符
\t 制表符
\“ 双引号
\\ 反斜线
\0 ASCII 0 null
\r 回到行的开始位置
\$ 标准的$符号,不会被当成变量
\[0-7]{1,3} 用8进制写的字符,
\x[0-9A-Fa-f]{1,2} 用16进制写的字符,
\u{[0-9A-Fa-f]+} 匹配正则表达式的字符序列是 unicode 码位, 该码位能作为 UTF-8 的表达方式输出字符串

单引号

\‘ 单引号
\\ 反斜线

定界符

阅读更多

PHP - FAQ

1. composer 下载的时候报错 Protocol “https” not supported or disabled in libcurl

查看下是否 php 版本支持 ssl, 如果不支持考虑重新安装并打开 ssl

2. Malformed UTF-8 characters, possibly incorrectly encoded

一般都是截取 utf-8 数据的时候出现的错误, 把中文截取为无法识别的内容

7.2 升级为 7.4 问题

EscapeShellArg 解码问题

在 php-fpm @ 7.4 版本中@ centos 系统, mac 系统无此问题

1
2
3
4
<?php
// 返回值为空
echo escapeshellarg('中文');
// string(2) "''"

解决方案

1
2
3
<?php
// 此行需要加入到文档前
setlocale(LC_CTYPE, "UTF8", "en_US.UTF-8");

原文地址 : PHP - FAQ
本站是作者语雀文档的镜像站, 如对文章有任何疑问请移步语雀进行 提问

Php (Code Review) - 01

1. 计算 1 次

1
2
3
4
5
6
7
8
9
10
<?php
# before
if (strlen($nickname) < 6 || strlen($nickname) > 18) {
return $this->setError('昵称长度必须在两到六个个汉字,六到十八个英文之间');
}
# after
$length = strlen($nickname);
if ($length < 6 || $length > 18) {
return $this->setError('昵称长度必须在两到六个个汉字,六到十八个英文之间');
}

2. divide by zero 的问题

优化点:

  • totalCount 是 数值
  • 需要考虑除数为 0 的情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # bad
    $totalCount = (clone $SDb)->count();
    $overNewRate = (new Number($overCount))->divide((new Number($totalCount)));
    $overNewRate = round($overNewRate->multiply(100)->getValue(), 2);
    # good
    try {
    $overNewRate = (new Number($overCount))->divide($totalCount);
    $overNewRate = round($overNewRate->multiply(100)->getValue(), 2);
    } catch (\Exception $e) {
    $overNewRate = 0;
    }

3. 使用 (string) 替代 strval

Analyzes if PHP4 functions (intval, floatval, doubleval, strval) are used for type casting and generates hints to use PHP5’s type casting construction (i.e. ‘(type) parameter’).

1
2
3
4
// bad
$input = strval($_POST['name']);
// good
$input = (string) $_POST['name'];

4. 合并 isset 的多重判定

The inspection is advising when multiple ‘isset(…)’ statements can be merged into one

1
2
3
4
5
6
7
8
// bad
if (isset($dir['origin']) && isset($dir['doc'])) {
// ...
}
// good
if (isset($dir['origin'], $dir['doc'])) {
// ...
}

5. If 多条件语法的合并

1
2
3
4
5
6
7
8
9
10
// bad
if ($profile->chid_status === UserProfile::STATUS_FAIL) {
if ($profile->chid_failed_at) {
// ...
}
}
// good
if ($profile->chid_status === UserProfile::STATUS_FAIL && $profile->chid_failed_at) {
// ...
}

6. 引号的使用

对于单纯字符串使用 ', 对于变量和文本进行混排的, 使用 "

1
2
3
4
5
6
7
8
// bad
$domain = "domain.com";
$random = str_random(7);
$randomMail = 'prefix_'.$random.'@'.$domain;
// good
$domain = 'domain.com';
$random = str_random(7);
$randomMail = "prefix_{$random}@{$domain}";
1
0 => '',

7. empty 使用的时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// bad
// 来源于订单取消过期
$orders = OrderHunter::
// ...
->where('created_at', '<', Carbon::now()->subMinutes($minutes))
->get();
if (!empty($orders)) {
$Play = new Play();
$orders->each(function (OrderHunter $item) use ($Play) {
// ...
});
}
// good
// 因为这里返回的是一个对象. empty 对象会返回的是 true, 当然这种对于实现了 countable 的来说又是另外一种概念
// todo 这里也需要测试
if ($orders->count()) {
$Play = new Play();
$orders->each(function (OrderHunter $item) use ($Play) {
// ...
});
}

8. 数组箭头后不允许换行

1
2
3
'hunter' => [
0 => 'lol',
],

9. If 后的 ; 需要去掉

1
2
3
if (!$info = $Order->info('003200330087', 62, 1)) {
\Log::error($Order->getError());
}

10. 参数等号对齐

1
2
$hunter_type    = OrderHunter::ORDER_NORMAL;
$subtotal_price = (new Number($price_id ?? 0))->multiply($num)->getValue();

11. 命名空间和 php 开始 <?php 存在于一行内

1
<?php namespace Order\Tests\Configuration;

12. 删除文件尾部的 ?>

php 文件的典型标记是以 <?php 开头, ?> 结尾。但是在 Zend Framework 中却不推荐在 php 文件末尾加 ?>
因为在<?php ?>之外的任何字符都会被输出到网页上,而之中的却不会。所以在末尾不加 ?> 可以预防 php 文件被恶意加入字符输出到网页。

13. 数组的键名

在 PHP 中, 使用不经单引号包含的字符串作为数组键名是合法的, 但是我们不希望如此 – 键名应该总是由单引号包含而避免引起混淆. 注意这是使用一个字符串, 而不是使用变量做键名的情况

1
2
3
4
5
6
7
8
// 错误
$foo = $assoc_array[blah];
// 正确
$foo = $assoc_array['blah'];
// 错误
$foo = $assoc_array["$var"];
// 正确
$foo = $assoc_array[$var];

避免在大数组上使用 in_array
避免在大的数组上使用 in_array(), 同时避免在循环中对包含 200 个以上元素的数组使用这个函数. in_array() 会非常消耗资源. 对于小的数组这种影响可能很小, 但是在一个循环中检查大数组可能会需要好几秒钟的时间. 如果您确实需要这个功能, 请使用 isset()来查找数组元素. 实际上是使用键名来查询键值. 调用 isset($array[$var]) 会比 in_array($var, array_keys($array)) 要快得多.
SQL 脚本格式
SQL 代码常常会变得很长, 如果不作一定的格式规范, 将很难读懂. SQL 代码一般按照以下的格式书写, 以关键字换行:

1
2
3
4
5
6
$sql = 'SELECT *
<-one tab->FROM ' . SOME_TABLE . '
<-one tab->WHERE a = 1
<-two tabs->AND (b = 2
<-three tabs->OR b = 3)
<-one tab->ORDER BY b';

这里是应用了制表符后的例子:

1
2
3
4
5
6
$sql = 'SELECT *
FROM ' . SOME_TABLE . '
WHERE a = 1
AND (b = 2
OR b = 3)
ORDER BY b';

禁止使用单字母开头的变量(无意义的变量)

1
$tKey, $tVal

14. 使用 (int) code 替代 intval(code)

1
2
3
4
// deprecated
$is_apply = intval(input('is_apply'));
// succcess
$is_apply = (int) input('is_apply');

15. 去除多余的 else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// deprecated
$id = (int) input('id');
if ($id) {
$item = PluginHelp::find($id, ['id', 'help_title as title', 'updated_at', 'content']);
return Resp::web(Resp::SUCCESS, '获取文章内容成功', $item);
}
else {
return Resp::web(Resp::SUCCESS, '列表为空');
}
// suggest
$id = (int) input('id');
if ($id) {
$item = PluginHelp::find($id, ['id', 'help_title as title', 'updated_at', 'content']);
return Resp::web(Resp::SUCCESS, '获取文章内容成功', $item);
}
return Resp::web(Resp::SUCCESS, '列表为空');

16. 对象和数组的不同

1
2
3
// Json 中对象返回 {}
// Json 中数组返回 []
// PHP 中空数组和对象均为 []

17. 使用临时变量以避免复合条件语句

1
2
3
4
5
6
7
8
9
# good
$itemValid = $itemMoney > 800 && $level > 3 && $valid > 0;
if($itemValid && isReady()) {
display();
}
# bad
if($itemMoney > 800 && $level > 3 && $valid > 0 && isReady()) {
display();
}

18. Switches 语句应该套用以下格式,并且每个分支必须注释清楚

1
2
3
4
5
6
7
8
switch (condition) {
case 0 :
// show something
break;
default :
// this is some code
break;
}

19. 数字变量转换

1
2
3
4
// bad
$ids = !is_array($id) ? [$id] : $id;
// good
$ids = (array) $id;

20. 不要使用硬编码

1
2
3
4
5
6
7
8
9
// 写常量是属于硬编码, 这里不要使用硬编码
// Bad
if ($owner->pub_is_good != 1) {
return $this->setError('您并非优质商人, 无法发布优质订单');
}
// Good
if ($owner->pub_is_good != Front::PUB_GOOD) {
return $this->setError('您并非优质商人, 无法发布优质订单');
}
1
2
3
4
// bad
$db->query("Update xd_company ...");
// good
$db->query("Update {$db_prefix}company ...");

21. 文件命名导致的冲突

1
2
3
4
5
6
7
8
// 文件名称
// TransToaccountTransfer.php
// 这里会出现文件类不存在的情况
// Found by Lipengtao
// 类名称
class TransToAccountTransfer {
...
}

22. 不正确的 Switch 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// bad : 目标值和匹配值不同
switch ($win_dot) { // 0
case ($win_dot == 0): // true
$result = $one_number->multiply($coefficient)->getValue();
break;
case ($win_dot >= 1 && $win_dot < 100): // false
$dis_price = $one_number->multiply($this->discount($win_dot))->multiply($coefficient)->getValue();
$result = $one_number->subtract($dis_price)->multiply($coefficient)->getValue();
break;
case ($win_dot > 99):
$result = $up_number->multiply($coefficient)->getValue();
break;
}
// good : 应该使用 if - else
if ($win_dot == 0):
$result = $one_number->multiply($coefficient)->getValue();
elseif ($win_dot >= 1 && $win_dot < 100):
$dis_price = $one_number->multiply($this->discount($win_dot))->multiply($coefficient)->getValue();
$result = $one_number->subtract($dis_price)->multiply($coefficient)->getValue();
else ($win_dot > 99):
$result = $up_number->multiply($coefficient)->getValue();
endif

23. 负数的写法

负数使用 ‘-‘ 来进行拼凑感觉会比较容易出问题

1
2
3
4
# bad : 这里是减去的总金额
$price = '-' . $order->total_price;
# good : 这里是我认为标准的写法
$price = (new Number($order->total_price))->negate()->getValue();

24. 注意 数组 + 和 array_merge 的不同

两个数组相加 如果前面的数组和后面数组 key 相同,前面的 key 值会覆盖后面的 key 值,array_merge() 后面的数组相同的 key 会覆盖前面的

25. 检测存在数据, 先进行检测, 然后再检测数据的值

1
2
3
4
5
6
7
8
9
10
// 修复前 : 发放会员折扣券
$setting = sys_setting(DiscountCoupon::settingKey());
if ($setting['week'] && $setting['send_at'] && isset($setting['week'], $setting['send_at'])) {
......
}
// 修复后, 先检测是否设定, 然后再检测值
$setting = sys_setting(DiscountCoupon::settingKey());
if (isset($setting['week'], $setting['send_at']) && $setting['week'] && $setting['send_at']) {
......
}

26. 数据库的 clone

1
2
3
4
5
6
7
// clone
$Db = OrderUserCoupon::where('account_id', $account_id)
->where('status', OrderUserCoupon::STATUS_UNUSED)
->where('start_at', '<=', Carbon::now()->toDateString());
if (!(clone $Db)->exists()) {
return $this->setError('暂无可用优惠券');
}

27. 同一个函数中存在多个不同类型的变量采用匈牙利命名法

1
2
3
// 匈牙利命名法
$arrCoupon = $coupon->toArray();
$objCoupon = $coupon;

28. 空数组约束

1
2
3
// 空数组
$array = null;
$array = (array) $array;

29. 注释的写法

需要支持代码提示

1
2
3
4
5
6
/**
* 系统推荐优惠券
* @param array|OrderUserCoupon[] $coupons 可用优惠券列表
* @param float $price 订单价格
* @return array|mixed
*/

30. 三元运算符的简写[?:/??]

Using null coalescing operator in PHP 7 simplifies code structure.

1
2
3
4
// bad
Form::model(isset($data['params']) ? $data['params'] : null)
// good
Form::model($data['params'] ?? null)

下面这种情况使用与 value 已经定义的情况

1
2
3
4
// bad
$output = $value ? $value : 'No value set.';
// good
$output = $value ?: 'No value set.';

原文地址 : Php (Code Review) - 01
本站是作者语雀文档的镜像站, 如对文章有任何疑问请移步语雀进行 提问

肉眼看到的相同两个字串的不同

祭出两个相同(其实不同)的字符串

1
2
$strA = '8888‬';
$strB = '8888';

我们肉眼看到的这两个字符串是绝对相同的, 对的, 他是一个’8888’, 让我们用 php (世界上最好的语言) 输出下两个字串的长度

1
2
3
4
5
6
# code
var_dump($strA);
var_dump($strB);
# result
string(7) "8888‬"
string(4) "8888"

我滴妈呀, 怎么会不同呢, 纳闷..

让我们撕下她的伪装

1
2
3
for ($i = 0, $iMax = strlen($strA); $i < $iMax; $i++) {
var_dump($strA[$i]);
}
1
2
3
4
5
6
7
string(1) "8"
string(1) "8"
string(1) "8"
string(1) "8"
string(1) "�"
string(1) "�"
string(1) "�"

咦, 这是个什么鬼. 不像是正常字符啊. 哦, 对了, 我们是用的Utf-8 字符集, 这三个应该是一个字符, 我们把它组合起来
获取到 ASCII 码值

1
2
3
for ($i = 0, $iMax = strlen($strA); $i < $iMax; $i++) {
var_dump(ord($strA[$i]));
}
1
2
3
4
5
6
7
int(56)
int(56)
int(56)
int(56)
int(226)
int(128)
int(172)

这个编码值是 [226 128 172], 让我们找到它.
看, 在这里

来自于这个网站, 好像是输出格式化标识符. 以上.


原文地址 : 肉眼看到的相同两个字串的不同
本站是作者语雀文档的镜像站, 如对文章有任何疑问请移步语雀进行 提问

关于 Php 的周计算

对于周计算(周榜)会在年底的时候出现了问题, 所以这里重新看了下关于周的定义并且重新改写了周函数, 如果不知道周定义, 函数的写法依旧会出现问题.
Php 的周定义是 ISO Week
我们看下定义

ISO周日历系统是ISO 8601日期和时间标准的一部分,是一种闰周历系统。这个系统主要用在政府和商务的会计年度,用以维持时序。这个系统依据格里历的年度中特定的一个周日,决定该年是否要增加一个星期。
格里历的置闰循环是400年97个闰日,包含20,871个完整的星期。在每个循环中有71年会有额外的第53周,一年的平均长度是52.1775周;平均每个月有4.348125个星期。
一个ISO周数年(也可以简称为ISO年)有52或53个完整的星期,也就是以364天或371天取代了常用的365或366天。这额外增加出来的一个星期称为闰周,然而在ISO 8601并没有这个名词。每个星期从星期一开始。每年的第一个星期包含当年的第一个星期四(并且总是包含1月4日)。ISO周的年编号因此会稍微偏离1月1日几天。

这里有几个定义, 我们拿一个来进行说明, 详细查看 wiki

下面是实现代码:

阅读更多

PHP函数的实现原理及性能分析

前言

在任何语言中,函数都是最基本的组成单元。对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行回答,在了解实现的同时更好的编写php程序。同时也会对一些常见的php函数进行介绍。

php函数的分类

在php中,横向划分的话,函数分为两大类: user function(内置函数) 和internal function(内置函数)。前者就是用户在程序中自定义的一些函数和方法,后者则是php本身提供的各类库函数(比如sprintf、array_push等)。用户也可以通过扩展的方法来编写库函数,这个将在后面介绍。对于user function,又可以细分为function(函数)和method(类方法),本文中将就这三种函数分别进行分析和测试。

php函数的实现

一个php函数最终是如何执行,这个流程是怎么样的呢?
要回答这个问题,我们先来看看php代码的执行所经过的流程。

php函数的实现
从图1可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看做是一个c开发的软件。
通过上面描述不难看出,php中函数的执行也是被翻译成了opcodes来调用,每次函数调用实际上是执行了一条或多条指令。
对于每一个函数,zend都通过以下的数据结构来描述

typedef union _zend_function { zend_uchar type; /* MUST be the first element of this struct! */ struct { zend_uchar type; /* never used */ char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; } common; zend_op_array op_array; zend_internal_function internal_function;} zend_function; typedef struct _zend_function_state { HashTable *function_symbol_table; zend_function *function; void *reserved[ZEND_MAX_RESERVED_RESOURCES];} zend_function_state;

其中type标明了函数的类型:用户函数、内置函数、重载函数。Common中包含函数的基本信息,包括函数名,

参数信息,函数标志(普通函数、静态方法、抽象方法)等内容。另外,对于用户函数,还有一个函数符号表,

记录了内部变量等,这个将在后面详述。 Zend维护了一个全局function_table,这是一个大的hahs表。

函数调用的时候会首先根据函数名从表中找到对应的zend_function。当进行函数调用时候,虚拟机会

根据type的不同决定调用方法, 不同类型的函数,其执行原理是不相同的 。

内置函数

内置函数,其本质上就是真正的c函数,每一个内置函数,php在最终编译后都会展开成为一个名叫zif_xxxx的function,比如我们常见的sprintf,对应到底层就是zif_sprintf。Zend在执行的时候,如果发现是内置函数,则只是简单的做一个转发操作。
Zend提供了一系列的api供调用,包括参数获取、数组操作、内存分配等。内置函数的参数获取,通过zend_parse_parameters方法来实现,对于数组、字符串等参数,zend实现的是浅拷贝,因此这个效率是很高的。可以这样说,对于php内置函数,其效率和相应c函数几乎相同,唯一多了一次转发调用。
内置函数在php中都是通过so的方式进行动态加载,用户也可以根据需要自己编写相应的so,也就是我们常说的扩展。ZEND提供了一系列的api供扩展使用

用户函数

和内置函数相比,用户通过php实现的自定义函数具有完全不同的执行过程和实现原理。如前文所述,我们知道php代码是被翻译成为了一条条opcode来执行的,用户函数也不例外,实际中每个函数对应到一组opcode,这组指令被保存在zend_function中。于是,用户函数的调用最终就是对应到一组opcodes的执行。

  • 局部变量的保存及递归的实现
    我们知道,函数递归是通过堆栈来完成的。在php中,也是利用类似的方法来实现。Zend为每个php函数分配了一个活动符号表(active_sym_table),记录当前函数中所有局部变量的状态。所有的符号表通过堆栈的形式来维护,每当有函数调用的时候,分配一个新的符号表并入栈。当调用结束后当前符号表出栈。由此实现了状态的保存和递归。

对于栈的维护,zend在这里做了优化。预先分配一个长度为N的静态数组来模拟堆栈,这种通过静态数组来模拟动态数据结构的手法在我们自己的程序中也经常有使用,这种方式避免了每次调用带来的内存分配、销毁。ZEND只是在函数调用结束时将当前栈顶的符号表数据clean掉即可。
因为静态数组长度为N,一旦函数调用层次超过N,程序不会出现栈溢出,这种情况下zend就会进行符号表的分配、销毁,因此会导致性能下降很多。在zend里面,N目前取值是32。因此,我们编写php程序的时候,函数调用层次最好不要超过32。当然,如果是web应用,本身可以函数调用层次的深度。

  • 参数的传递
    和内置函数调用zend_parse_params来获取参数不同,用户函数中参数的获取是通过指令来完成的。函数有几个参数就对应几条指令。具体到实现上就是普通的变量赋值。
    通过上面的分析可以看出,和内置函数相比,由于是自己维护堆栈表,而且每条指令的执行也是一个c函数,用户函数的性能相对会差很多,后面会有具体的对比分析。因此,如果一个功能有对应php内置函数实现的尽量不要自己重新写函数去实现。

类方法

类方法其执行原理和用户函数是相同的,也是翻译成opcodes顺次调用。类的实现,zend用一个数据结构zend_class_entry来实现,里面保存了类相关的一些基本信息。这个entry是在php编译的时候就已经处理完成。
在zend_function的common中,有一个成员叫做scope,其指向的就是当前方法对应类的zend_class_entry。关于php中面向对象的实现,这里就不在做更详细的介绍,今后将专门写一篇文章来详述php中面向对象的实现原理。就函数这一块来说,method实现原理和function完全相同,理论上其性能也差不多,后面我们将做详细的性能对比。

性能对比

函数名长度对性能的影响

  • 测试方法
    对名字长度为1、2、4、8、16的函数进行比较,测试比较它们每秒可执行次数,确定函数名长度对性能的影响
  • 测试结果如下图

函数名长度对性能的影响

  • 结果分析
    从图上可以看出,函数名的长度对性能还是会有一定的影响。一个长度为1的函数和长度为16的  空函数调用  ,其性能差了1倍。分析一下源码不难找到原因,如前面叙述所说,函数调用的时候zend会先在一个全局的funtion_table中通过函数名查询相关信息,function_table是一个哈希表。必然的,名字越长查询所需要的时间就越多。  因此,在实际编写程序的时候,对多次调用的函数,名字建议不要太长

虽然函数名长度对性能有一定影响,但具体有多大呢?这个问题应该还是需要结合实际情况来考虑,如果一个函数本身比较复杂的话,那么对整体的性能影响并不大。
一个建议是对于那些会调用很多次,本身功能又比较简单的函数,可以适当取一些言简意赅的名字。

函数个数对性能的影响

  • 测试方法
    在以下三种环境下进行函数调用测试,分析结果:1.程序仅包含1个函数 2.程序包含100个函数 3.程序包含1000个函数。
    测试这三种情况下每秒所能调用的函数次数
  • 测试结果如下图
    函数个数对性能的影响
  • 结果分析
    从测试结果可以看出,这三种情况下性能几乎相同,函数个数增加时性能下降微乎其微,可以忽略。
    从实现原理分析,几种实现下唯一的区别在于函数获取的部分。如前文所述,所有的函数都放在一个hash表中,在不同个数下查找效率都应该还是接近于O(1),所以性能差距不大。

不同类型函数调用消耗

  • 测试方法
    选取用户函数、类方法、静态方法、内置函数各一种,函数本身不做任何事情,直接返回,主要测试空函数调用的消耗。测试结果为每秒可执行次数
    测试中为去除其他影响,所有函数名字长度相同
  • 测试结果如下图
    不同类型函数调用消耗
  • 结果分析
    通过测试结果可以看到,对于用户自己编写的php函数,不管是哪种类型,其效率是差不多的,均在280w/s左右。如我们预期,即使是空调,内置函数其效率也要高很多,达到780w/s,是前者是3倍。可见,内置函数调用的开销还是远低于用户函数。从前面原理分析可知主要差距在于用户函数调用时初始化符号表、接收参数等操作。

内置函数和用户函数性能对比

  • 测试方法
    内置函数和用户函数的性能对比,这里我们选取几个常用的函数,然后用php实现相同功能的函数进行一下性能对比。
    测试中,我们选取字符串、数学、数组中各一个典型进行对比,这几个函数分别是字符串截取(substr)、10进制转2进制(decbin)、求最小值(min)和返回数组中的所以key(array_keys)。
  • 测试结果如下图
    内置函数和用户函数性能对比
  • 结果分析
    从测试结果可以看出,如我们预期,内置函数在总体性能上远高于普通用户函数。尤其对于涉及到字符串类操作的函数,差距达到了1个数量级。因此,函数使用的一个原则就是如果某功能有相应的内置函数,尽量使用它而不是自己编写php函数。
    对于一些涉及到大量字符串操作的功能,为提高性能,可以考虑用扩展来实现。比如常见的富文本过滤等。

和C函数性能对比

  • 测试方法
    我们选取字符串操作和算术运算各3种函数进行比对,php用扩展实现。三种函数是简单的一次算法运算、字符串比较和多次的算法运算。
    除了本身的两类函数外,还会测试将函数空调开销去掉后的性能,一方面比对一下两种函数(c和php内置)本身的性能差异,另外就是侧面印证空调函数的消耗
    测试点为执行10w次操作的时间消耗
  • 测试结果如下图

      


  • 和C函数性能对比
  • 结果分析
    内置函数和C函数的开销在去掉php函数空调用的影响后差距较小,随着函数功能越来越复杂,双方性能趋近于相同。这个从之前的函数实现分析中也容易得到论证,毕竟内置函数就是C实现的。
    函数功能越复杂,c和php的性能差距越小
    相对c来说,php函数调用的开销大很多,对于简单函数来说性能还是有一定影响。因此php中函数不宜嵌套封装太深。

伪函数及其性能

在php中,有这样一些函数,它们在使用上是标准的函数用法,但底层实现却和真正函数调用完全不同,这些函数不属于前文提到的三种function中的任何一类,其实质是一条单独的opcode,这里估且叫做伪函数或者指令函数。
如上所说,伪函数使用起来和标准的函数并无二致,看起来具有相同的特征。但是他们最终执行的时候是被zend反映成了一条对应的指令(opcode)来调用,因此其实现更接近于if、for、算术运算等操作。

  • php中的伪函数
    isset
    empty
    unset
    eval

通过上面的介绍可以看出,伪函数由于被直接翻译成指令来执行,和普通函数相比少了一次函数调用所带来的开销,因此性能会更好一些。我们通过如下测试来做一个对比。 Array_key_exists和isset两者都可以判断数组中某个key是否存在,看一下他们的性能

伪函数及其性能

从图上可以看出,和array_key_exists相比,isset性能要高出很多,基本是前者的4倍左右,而即使是和空函数调用相比,其性能也要高出1倍左右。由此也侧面印证再次说明了php函数调用的开销还是比较大的。

常用php函数实现及介绍

count

count是我们经常用到的一个函数,其功能是返回一个数组的长度。
count这个函数,其复杂度是多少呢?
一种常见的说法是count函数会遍历整个数组然后求出元素个数,因此复杂度是O(n)。那实际情况是不是这样呢?
我们回到count的实现来看一下,通过源码可以发现,对于数组的count操作,函数最终的路径是zif_count->  php_count_recursive-> zend_hash_num_elements,而zend_hash_num_elements的行为是 return ht->nNumOfElements,可见,这是一个O(1)而不是O(n)的操作。实际上,数组在php底层就是一个hash_table,对于hash表,zend中专门有一个元素nNumOfElements记录了当前元素的个数,因此对于一般的count实际上直接就返回了这个值。由此,我们得出结论:  count是O(1)的复杂度,和具体数组的大小无关。
非数组类型的变量,count的行为时怎样?
对于未设置变量返回0,而像int、double、string等则会返回1

strlen

Strlen用于返回一个字符串的长度。那么,他的实现原理是如何的呢?
我们都知道在c中strlen是一个o(n)的函数,会顺序遍历字符串直到遇到\0,然后出长度。Php中是否也这样呢?答案是否定的,php里字符串是用一个复合结构来描述,包括指向具体数据的指针和字符串长度(和c++中string类似),因此strlen就直接返回字符串长度了,是常数级别的操作。
另外,对于非字符串类型的变量调用strlen,它会首先将变量强制转换为字符串再求长度,这点需要注意。

isset和array_key_exists

这两个函数最常见的用法都是判断一个key是否在数组中存在。但是前者还可以用于判断一个变量是否被设置过。
如前文所述,isset并非真正的函数,因此它的效率会比后者高很多。推荐用它代替array_key_exists。

array_push和array[]

两者都是往数组尾部追加一个元素。不同的是前者可以一次push多个。他们最大的区别在于一个是函数一个是语言结构,因此后者效率要更高。因此如果只是普通的追加元素,建议使用array []。

rand和mt_rand

两者都是提供产生随机数的功能,前者使用libc标准的rand。后者用了 Mersenne Twister 中已知的特性作为随机数发生器,它可以产生随机数值的平均速度比 libc 提供的 rand() 快四倍。因此如果对性能要求较高,可以考虑用mt_rand代替前者。
我们都知道,rand产生的是伪随机数,在C中需要用srand显示指定种子。但是在php中,rand会自己帮你默认调用一次srand,一般情况下不需要自己再显示的调用。
需要注意的是,如果特殊情况下需要调用srand时,一定要配套调用。就是说srand对于rand,mt_srand对应srand,切不可混合使用,否则是无效的。

sort和usort

两者都是用于排序,不同的是前者可以指定排序策略,类似我们C里面的qsort和C++的sort。
在排序上两者都是采用标准的快排来实现,对于有排序需求的,如非特殊情况调用php提供的这些方法就可以了,不用自己重新实现一遍,效率会低很多。原因见前文对于用户函数和内置函数的分析比对。

urlencode和rawurlencode

这两个都是用于url编码, 字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数。两者唯一的区别在于对于空格,urlencode会编码为+,而rawurlencode会编码为%20。
一般情况下除了搜索引擎,我们的策略都是空格编码为%20。因此采用后者的居多。
注意的是encode和decode系列一定要配套使用。

strcmp系列函数

这一系列的函数包括strcmp、strncmp、strcasecmp、strncasecmp,实现功能和C函数相同。但也有不同,由于php的字符串是允许\0出现,因此在判断的时候底层使用的是memcmp系列而非strcmp,理论上来说更快。
另外由于php直接能获取到字符串长度,因此会首先这方面的检查,很多情况下效率就会高很多了。

is_int和is_numeric

这两个函数功能相似又不完全相同,使用的时候一定需要注意他们的区别。
Is_int:判断一个变量类型是否是整数型,php变量中专门有一个字段表征类型,因此直接判断这个类型即可,是一个绝对O(1)的操作
Is_numeric:判断一个变量是否是整数或数字字符串,也就是说除了整数型变量会返回true之外,对于字符串变量,如果形如”1234”,”1e4”等也会被判为true。这个时候会遍历字符串进行判断。

总结及建议

通过对函数实现的原理分析和性能测试,我们总结出以下一些结论
1. Php的函数调用开销相对较大。
2. 函数相关信息保存在一个大的hash_table中,每次调用时通过函数名在hash表中查找,因此函数名长度对性能也有一定影响。
3. 函数返回引用没有实际意义
4. 内置php函数性能比用户函数高很多,尤其对于字符串类操作。
5. 类方法、普通函数、静态方法效率几乎相同,没有太大差异
6. 除去空函数调用的影响,内置函数和同样功能的C函数性能基本差不多。
7. 所有的参数传递都是采用引用计数的浅拷贝,代价很小。
8. 函数个数对性能影响几乎可以忽略
因此,对于php函数的使用,有如下一些建议
1. 一个功能可以用内置函数完成,尽量使用它而不是自己编写php函数。
2. 如果某个功能对性能要求很高,可以考虑用扩展来实现。
3. Php函数调用开销较大,因此不要过分封装。有些功能,如果需要调用的次数很多本身又只用1、2行代码就行实现的,建议就不要封装调用了。
4. 不要过分迷恋各种设计模式,如上一条描述,过分的封装会带来性能的下降。需要考虑两者的权衡。Php有自己的特点,切不可东施效颦,过分效仿java的模式。
5. 函数不宜嵌套过深,递归使用要谨慎。
6. 伪函数性能很高,同等功能实现下优先考虑。比如用isset代替array_key_exists
7. 函数返回引用没有太大意义,也起不到实际作用,建议不予考虑。
8. 类成员方法效率不比普通函数低,因此不用担心性能损耗。建议多考虑静态方法,可读性及安全性都更好。
9. 如不是特殊需要,参数传递都建议使用传值而不是传引用。当然,如果参数是很大的数组且需要修改时可以考虑引用传递。
[作者:HDK   原文链接]


原文地址 : PHP函数的实现原理及性能分析
本站是作者语雀文档的镜像站, 如对文章有任何疑问请移步语雀进行 提问

error_reporting 设置php运行时错误报警

定义和用法

error_reporting() 设置 PHP 的报错级别并返回当前级别。

语法

int error_reporting(report_level)

如果参数 level 未指定,当前报错级别将被返回。下面几项是 level 可能的值:

错误和日志记录

常量 说明
1 E_ERROR (integer) 致命的运行时错误。这类错误一般是不可恢复的情况,例如内存分配导致的问题。后果是导致脚本终止不再继续运行。
2 E_WARNING (integer) 运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
4 E_PARSE (integer) 编译时语法解析错误。解析错误仅仅由分析器产生。
8 E_NOTICE (integer) 运行时通知。表示脚

本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。
16 E_CORE_ERROR (integer) 在PHP初始化启动过程中发生的致命错误。该错误类似 E_ERROR,但是是由PHP引擎核心产生的。
32 E_CORE_WARNING (integer) PHP初始化启动过程中发生的警告 (非致命错误) 。类似 E_WARNING,但是是由PHP引擎核心产生的。
64 E_COMPILE_ERROR (integer) 致命编译时错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。
128 E_COMPILE_WARNING (integer) 编译时警告 (非致命错误)。类似 E_WARNING,但是是由Zend脚本引擎产生的。
256 E_USER_ERROR (integer) 用户产生的错误信息。类似 E_ERROR, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
512 E_USER_WARNING (integer) 用户产生的警告信息。类似 E_WARNING, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
1024 E_USER_NOTICE (integer) 用户产生的通知信息。类似 E_NOTICE, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
2048 E_STRICT (integer) 启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
4096 E_RECOVERABLE_ERROR (integer) 可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被用户自定义句柄捕获 (参见 set_error_handler()),将成为一个 E_ERROR 从而脚本会终止运行。
8192 E_DEPRECATED (integer) 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。
16384 E_USER_DEPRECATED (integer) 用户产少的警告信息。 类似 E_DEPRECATED, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
30719 E_ALL (integer) E_STRICT出外的所有错误和警告信息。

上面的值(数值或者符号)用于建立一个二

例子

任意数目的以上选项都可以用“或”来连接(用 OR 或 |),这样可以报告所有需要的各级别错误。例如,下面的代码关闭了用户自定义的错误和警告,执行了某些操作,然后恢复到原始的报错级别:

1
2
3
4
5
6
7
8
<?php
//禁用错误报告
error_reporting(0);
//报告运行时错误
error_reporting(E_ERROR | E_WARNING | E_PARSE);
//报告所有错误
error_reporting(E_ALL);
?>

原文地址 : error_reporting 设置php运行时错误报警
本站是作者语雀文档的镜像站, 如对文章有任何疑问请移步语雀进行 提问