PHP 

如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明显。但是,生成器功能的确非常有用。

优点

直接讲概念估计你听完还是一头雾水,所以我们先来说说优点,也许能勾起你的兴趣。那么生成器有哪些优点,如下:

  • 生成器会对PHP应用的性能有非常大的影响
  • PHP代码运行时节省大量的内存
  • 比较适合计算大量的数据

那么,这些神奇的功能究竟是如何做到的?我们先来举个例子。

概念引入

首先,放下生成器概念的包袱,来看一个简单的PHP函数:

1
2
3
4
5
6
7
function ($number){
$data = [];
for($i=0;$i<$number;$i++){
$data[] = time();
}
return $data;
}

这是一个非常常见的PHP函数,我们在处理一些数组的时候经常会使用。这里的代码也非常简单:

  • 1、我们创建一个函数。
  • 2、函数内包含一个for循环,我们循环的把当前时间放到$data里面
  • 3、for循环执行完毕,把$data返回出去。

下面没完,我们继续。我们再写一个函数,把这个函数的返回值循环打印出来:

1
2
3
4
5
6
7
8
9
10
echo 'start time: ' . $start_time = microtime_float();

$result = createRange(10); 
foreach($result as $value){
sleep(1);//这里停顿1秒,我们后续有用
echo '<br>' . $value;
} echo '<br> end time: ' . $end_time = microtime_float();
echo '<br> 当前页面执行时间: ' . ($end_time - $start_time);

这里定义了一个辅助函数,用于打印程序的执行时间:

1
2
3
4
5
6
7
8
9
/**
* 返回微妙数(辅助函数)
* @return float
*/
function microtime_float()
{
list($usec, $sec) = explode(' ', microtime());
return $usec + $sec;
}

我们在浏览器里面看一下运行结果:

这里非常完美,没有任何问题。

思考一个问题

我们注意到,在调用函数 createRange 的时候给 $number 的传值是10,一个很小的数字。假设,现在传递一个值 10000000(1000万)。

那么,在函数 createRange 里面,for 循环就需要执行 1000万次。且有 1000 万个值被放到 $data 里面,而 $data 数组在是被放在内存内。所以,在调用函数时候会占用大量内存。

这里,生成器就可以大显身手了。

创建生成器

我们直接修改代码,你们注意观察:

1
2
3
4
5
function ($number){
for($i=0;$i<$number;$i++){
yield time();
}
}

看下这段和刚刚很像的代码,我们删除了数组 $data,而且也没有返回任何内容,而是在 time() 之前使用了一个关键字 yield

使用生成器

我们再运行一下第二段代码:

1
2
3
4
5
6
7
8
9
10
echo 'start time: ' . $start_time = microtime_float();

$result = createRange(10); 
foreach($result as $value){
sleep(1);//这里停顿1秒,我们后续有用
echo '<br>' . $value;
} echo '<br> end time: ' . $end_time = microtime_float();
echo '<br> 当前页面执行时间: ' . ($end_time - $start_time);

我们奇迹般的发现了,输出的值和第一次没有使用生成器的不一样。这里的值(时间戳)中间间隔了1秒。

这里的间隔一秒其实就是 sleep(1) 造成的后果。但是为什么第一次没有间隔?那是因为:

  • 未使用生成器时:createRange 函数内的 for 循环结果被很快放到 $data中,并且立即返回。所以,foreach 循环的是一个固定的数组。
  • 使用生成器时:createRange 的值不是一次性快速生成,而是依赖于 foreach 循环。foreach 循环一次,for 执行一次。

到这里,你应该对生成器有点儿头绪。

深入理解生成器

代码剖析

下面我们来对于刚刚的代码进行剖析(去掉冗余部分)。

1
2
3
4
5
6
7
8
大专栏  【转】PHP中被忽略的性能优化利器:生成器.mde">9
10
11
function ($number){
for($i=0;$i<$number;$i++){
yield time();
}
} $result = createRange(10);
foreach($result as $value){
sleep(1);
echo $value.'<br />';
}

我们来还原一下代码执行过程。

  • 首先调用 createRange 函数,传入参数 10,但是 for 值执行了一次然后停止了,并且告诉 foreach 第一次循环可以用的值。
  • foreach 开始对 $result 循环,进来首先 sleep(1),然后开始使用 for 给的一个值执行输出。
  • foreach 准备第二次循环,开始第二次循环之前,它向 for 循环又请求了一次。
  • for 循环于是又执行了一次,将生成的时间戳告诉foreach.
  • foreach 拿到第二个值,并且输出。由于 foreachsleep(1),所以,for 循环延迟了1秒生成当前时间

所以,整个代码执行中,始终只有一个记录值参与循环,内存中也只有一条信息。

无论开始传入的 $number 有多大,由于并不会立即生成所有结果集,所以内存始终是一条循环的值。

概念理解

到这里,你应该已经大概理解什么是生成器了。下面我们来说下生成器原理。

首先明确一个概念:生成器yield关键字不是返回值,他的专业术语叫产出值,只是生成一个值

那么代码中 foreach 循环的是什么?其实是PHP在使用生成器的时候,会返回一个 Generator 类的对象。foreach 可以对该对象进行迭代,每一次迭代,PHP会通过 Generator 实例计算出下一次需要迭代的值。这样 foreach 就知道下一次需要迭代的值了。

而且,在运行中 for 循环执行后,会立即停止。等待 foreach 下次循环时候再次和 for 索要下次的值的时候,for 循环才会再执行一次,然后立即再次停止。直到不满足条件不执行结束。

实际开发应用

很多PHP开发者不了解生成器,其实主要是不了解应用领域。那么,生成器在实际开发中有哪些应用?

读取超大文件

PHP开发很多时候都要读取大文件,比如csv文件、text文件,或者一些日志文件。这些文件如果很大,比如5个G。这时,直接一次性把所有的内容读取到内存中计算不太现实。

这里生成器就可以派上用场啦。简单看个例子:读取log文件

这是我系统的一个启动日志,拿来做示范读取

  • 通过数组输出文件内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    header("content-type:text/html;charset=utf-8");
    function readLog()
    {
    # code...
    $handle = fopen("./bootstrap.log", 'rb'); $data = [];
    while (feof($handle)===false) {
    # code...
    $data[] = fgets($handle);
    } fclose($handle);
    return $data;
    } $start_time = microtime_float();
    $start_mem = memory_get_usage(); $result = readLog();
    foreach ($result as $key => $value) {
    # code...
    echo '<br>' . $value;
    } echo 'start time: ' . $start_time;
    echo '<br>end time: ' . $end_time = microtime_float();
    echo '<br>读取文件总共用时: ' . ($end_time - $start_time); echo '<br>start memory: ' . $start_mem;
    echo '<br>end memory: ' . $end_mem = memory_get_usage();
    echo '<br>total memory: ' . ($end_mem - $start_mem) / 1024;

  • 通过生成器输出文件内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    ...
    ...
    function readLog()
    {
    # code...
    $handle = fopen("./bootstrap.log", 'rb'); $data = [];
    while (feof($handle)===false) {
    # code...
    yield = fgets($handle);
    } fclose($handle);
    }
    ...
    ...

通过上图的输出结果我们可以看出:

输出的内容完全相同,但使用数组输出文件内容和使用生成器输出内容所占用的内存差距很惊人(我这个文件大小为:300.8k)。

由此可见,两种方式背后的代码执行规则一点儿也不一样。使用生成器读取文件,第一次读取了第一行,第二次读取了第二行,以此类推,每次被加载到内存中的文字只有一行,大大的减小了内存的使用。

这样,即使读取上G的文本也不用担心,完全可以像读取很小文件一样编写代码。

【注】:本传转载自PHP中被忽略的性能优化利器:生成器

【转】PHP中被忽略的性能优化利器:生成器.md的更多相关文章

  1. PHP中被忽略的性能优化利器:生成器

    如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生.但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明显.但是,生成 ...

  2. PHP系列 | [转] PHP中被忽略的性能优化利器:生成器

    官方:https://www.php.net/manual/zh/language.generators.overview.php 原文:https://segmentfault.com/a/1190 ...

  3. LeakCanary 内存泄漏 监测 性能优化 简介 原理 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. C#中那些[举手之劳]的性能优化

    隔了很久没写东西了,主要是最近比较忙,更主要的是最近比较懒...... 其实这篇很早就想写了 工作和生活中经常可以看到一些程序猿,写代码的时候只关注代码的逻辑性,而不考虑运行效率 其实这对大多数程序猿 ...

  5. Java开发中程序和代码性能优化

    现在计算机的处理性能越来越好,加上JDK升级对一些代码的优化,在代码层针对一些细节进行调整可能看不到性能的明显提升, 但是我觉得在开发中注意这些,更多的是可以保持一种性能优先的意识,对一些敲代码时间比 ...

  6. js 性能优化利器:prepack

    1. js 性能优化 js 本身是没有像 python 一样的预编译功能,更没有像 java 一样的编译功能,所以,这里所说的 js 代码预编译 只是通过工具实现的类似功能而已. 这就要提到 prep ...

  7. PHP性能优化利器:生成器 yield理解

    如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生.但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明显.但是,生成 ...

  8. Android中数据库Sqlite的性能优化

    1.索引简单的说,索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以大大提高数据库查询的效率.(1). 优点大大加快了数据库检索的速度,包括 ...

  9. java开发代码中的50个性能优化细节

    在java程序中,性能的大部分原因并不在java与语言,而是程序本身.养成好的编码习惯非常重要,能够显著地提升程序性能. 1:尽量在合适的场合使用单例 使用单利可以减轻加载的负担,缩短加载时间,提高加 ...

随机推荐

  1. 洛谷 P3371 【模板】单源最短路径(弱化版)(dijkstra邻接链表)

    题目传送门 解题思路: 传送门 AC代码: #include<iostream> #include<cstdio> #include<cstring> using ...

  2. Python笔记_第三篇_面向对象_2.构造函数和析构函数(含self说明)

    1. 构造函数: 为什么要有构造函数? 打一个比方:类的创建就是好比你创建了好了一种格式的房间,你租给上一个住户的后,里面会对方很多“垃圾”和不规则的物品摆放.构造函数就是下一个住户再使用的时候进行物 ...

  3. semantic框架 - selection dropdown自定义样式

    semantic和Bootstrap是我最常用的前端框架. 这次需求,是要修改selection dropdown. 直接贴代码,不解释. .ui.form input:not([type]):foc ...

  4. 在python实现加密的方式总结

    基础知识扫盲 对称加密 对称密钥加密 , 又叫私钥加密.即信息发送的方和接受方用一个密钥去加密和揭秘数据. 最大的优势是 加解密速度快,适合对大量数据进行加密, 对称加密的缺点是密钥的管理和分配, 换 ...

  5. elasticsearch 大集群,双重别名,滚动更新分词方案

    elasticsearch 滚动更新分词 国内用ik.hanlp.ansj或基于其二次开发的比较多 必然有分词变更的操作(主要是是加词) reindex+别名可以解决一部分问题,但在大集群上会影响业务 ...

  6. Linux基础篇六:Linux文件属性和类型

    -:代表文件 s: sorket文件 b:block块设备 (磁盘,光驱等) c:字符设备 l:连接文件 p:管道文件 d:代表目录文件 为了更加区分- (文件的具体类型),系统提供了file命令更加 ...

  7. How Cocoa Beans Grow And Are Harvested Into Chocolate

    What is Cocoa Beans Do you like chocolate? Most people do. The smooth, brown candy is deliciously sw ...

  8. elasticserch-hadoop spak 网络配置异常排查

    elasticserch hadoop 在本地测试写入 elasticsearch:9200时成功 线上环境却报错如下 org.elasticsearch.hadoop.EsHadoopIllegal ...

  9. scala编程(六)——函数式对象

    Rational 的式样书 分数:rational number 是一种可以表达为比率 d n 的数字,这里的 n 和 d 是数字,其中 d 不能为零.n 被称作是分子:numerator,d 被称作 ...

  10. dhcp server

    centos yum install dhcp -y cat /etc/dhcp/dhcpd.conf default-lease-time 7200; max-lease-time 14400; s ...