php之生成器

2017-03-20

作为协程的基础资料

生成器

​  一个生成器函数看起来像一个普通的函数,不同的是普通函数返回一个值,而一个生成器可以 yield 生成许多它所需要的值。

​  生成器被调用的时候,返回的是一个可被遍历的generator对象,当你遍历这个对象时,PHP 会按需调用生成器函数,并在产生一个值后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。

yield 关键字

​  生成器的核心是 yield 关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。

  • 表达式中使用:

​  php 7 以下是必须加括号的:

1
$data = (yield $value);
  • 生成的时候使用键名:
1
2
yield $id => $fields;
$data = (yield $key => $value);
  • 生成 null 值:

    yield可以在没有参数传入的情况下被调用来生成一个 NULL 值并配对一个自动的键名。

  • 使用引用来生成值:

  • PHP 7 中 generator 允许嵌套:

    允许您通过使用 yield from 关键字从另一个生成器,Traversable对象或数组生成值。

测试

验证官方文档中关于range(0,100000)的优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require "../common/common.php";

function xrange(start,limit,$step) {
if($start < $limit) {
if($step <= 0) {
throw new Exception("step must be +ev",1);
}

for ($i = $start; $i <= $limit; $i += $step) {
yield $i;
}
} else {
if($step >= 0) {
throw new Exception("step must be -ev", 1);//正值
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}

测试:range 函数本机使用900万会使内存分配用尽,所以 range 使用800万测试,xrange 使用1000 万测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo '使用xrange函数:';
memory_change();
xrange(1,10000000);
$res = memory_change(1);
$t1 = $res['t'];$m1 = $res['m'];$p1 = $res['p'];
echo "耗时:{$t1},内存峰值:{$p1}<br>";

echo '使用range函数:';
memory_change();
range(1,8000000);
$res = memory_change(1);
$t2 = $res['t'];$m2 = $res['m'];$p2 = $res['p'];
echo "耗时:{$t2},内存峰值:{$p2}<br>";

echo "时间提升:".@round(($t2-$t1)/$t2*100,2)."%;内存消耗减少:".@round(($p2-$p1)/$p2*100,2)."%<br>";
1
2
3
使用xrange函数:耗时:0.000007,内存峰值:2 mb
使用range函数: 耗时:0.139639,内存峰值:258 mb
时间提升:99.99%;内存消耗减少:99.22%

生成器从不返回值,只是产出值,所以这个结果是必然的,因为 xrange 内的函数并没有运行,知识返回了一个迭代器,你只有在遍历它的时候才回生成,节省了内存消耗。

问题

但是不同配置和不同的 php 版本出现的 结果又不一样:

https://gist.github.com/nikic/2975796

结论

生成器的用法目前是可以替代需要消耗大量内存的地方,通过 github 上的测试来看,时间并不占优势,但是内存的占用是一大亮点。

参考:php.net

相关代码可以在git中有。