当前位置: 首页 > news >正文

PHP中yield关键字的使用

PHP版本>=5.5

原理:yield关键字会生成一个Generator类的对象,PHP通过Generator实例计算出下一次迭代的值,再次返回一个Generator对象并停止循环(即循环一次执行一次)。

理解:使用在for/foreach/while循环内部,用来返回循环结构体本次生成的元素。yield会记录结构体本次循环所处的位置,下次执行循环时从上次的位置开始执行,再次生成一个元素。所以yield返回的数组内永远只有一个元素,所以叫做生成器。

目的:节省内存,防止内存溢出。

yield节省内存场景:
(1)在for/foreach/while循环结构体中,生成上亿个元素,在内存中都只有一个元素。
(2)在fgets读取文件中,每次都读取一行数据到内存中,大文件可以像小文件一样读取。
(3)在mysql读取数据中,每次都获取一行数据到内存中,就算几十万的数据也不会占用过多内存。

场景(1):

<?php
/**
 * 使用 yield关键字创建数组
 * @param $number
 * @return Generator
 */
function createRangeYield($number): Generator
{
    for ($i = 0; $i <= $number; $i++) {
        yield time();

        if (($i % 20000) == 0) {
            // 内存使用以 MB 为单位
            echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
        }
    }
}

/**
 * 直接生成数组
 * @param $number
 * @return array
 */
function createRange($number): array
{
    $data = [];
    for ($i = 0; $i <= $number; $i++) {
        $data[] = time();

        if (($i % 20000) == 0) {
            // 内存使用以 MB 为单位
            echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
        }
    }
    return $data;
}

$time1 = microtime(true) * 1000;
echo "常规函数循环 10W次内存使用量\n";
$result = createRange(100000); // 这里调用上面创建的函数
foreach ($result as $value) {
    # code...
}
$time2 = microtime(true) * 1000;
echo "耗时:".( round($time2 - $time1,3)). "毫秒\n";

$time1 = microtime(true) * 1000;
echo "\nyield函数循环 10W次内存使用量\n";
$result = createRangeYield(100000); // 这里调用上面创建的函数
foreach ($result as $value) {
    # code...
}
$time2 = microtime(true) * 1000;
echo "耗时:".( round($time2 - $time1,3)). "毫秒\n";

执行结果分析:

场景(2):

<?php
header("content-type:text/html;charset=utf-8");
/**
 * 使用 yield关键字读取大文件
 * @return Generator
 */
function readTxtYield(): Generator
{
    $handle = fopen("D:/ttt.txt", 'rb');

    $i = 0;
    while (feof($handle) === false) {
        yield fgets($handle);

        if (($i % 200000) == 0) {
            // 内存使用以 MB 为单位
            echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
        }

        $i++;
    }

    fclose($handle);
}

/**
 * 常规函数读取大文件
 * @return array
 */
function readTxt(): array
{
    $handle = fopen("D:/ttt.txt", 'rb');

    $i = 0;
    $lines = [];
    while (feof($handle) === false) {
        $lines[] = fgets($handle);

        if (($i % 200000) == 0) {
            // 内存使用以 MB 为单位
            echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
        }

        $i++;
    }

    fclose($handle);
    return $lines;
}

$time1 = microtime(true) * 1000;
echo "常规函数读取 100W行文件内存使用量\n";
foreach (readTxt() as $key => $value) {
    # code...
}
$time2 = microtime(true) * 1000;
echo "耗时:".( round($time2 - $time1,3)). "毫秒\n";

$time1 = microtime(true) * 1000;
echo "\nyield函数读取 100W行文件内存使用量\n";
foreach (readTxtYield() as $key => $value) {
    # code...
}
$time2 = microtime(true) * 1000;
echo "耗时:".( round($time2 - $time1,3)). "毫秒\n";

执行结果分析:

场景(3):

<?php

const DB_NAME = 'yibai_purchase';// 数据库名称
const DB_HOST = '192.168.73.73';
const DB_USERNAME = 'dev_purchase';
const DB_PASSWORD = 'yb123456';
const LIMIT = 50;
const DSN = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;


/**
 * 使用 yield关键字 读取数据库数据
 * @return Generator
 */
function readSqlDataYield(): Generator
{
    $i = 0;
    $lines = [];

    $options = [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ];
    $connectObj = new PDO(DSN, DB_USERNAME, DB_PASSWORD, $options);
    $query_tables = "SELECT * FROM yibai_purchase.pur_supplier_payment_record WHERE 1=1 limit 100000";
    $stmt = $connectObj->query($query_tables);
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        yield $row;

        if (($i % 20000) == 0) {
            // 内存使用以 MB 为单位
            echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
        }

        $i++;
    }
}


/**
 * 常规函数读取数据库数据
 * @return array
 */
function readSqlData(): array
{
    $i = 0;
    $lines = [];

    $options = [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ];
    $connectObj = new PDO(DSN, DB_USERNAME, DB_PASSWORD, $options);
    $query_tables = "SELECT * FROM yibai_purchase.pur_supplier_payment_record WHERE 1=1 limit 100000";
    $stmt = $connectObj->query($query_tables);
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $lines[] = $row;

        if (($i % 20000) == 0) {
            // 内存使用以 MB 为单位
            echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
        }

        $i++;
    }

    return $lines;

}

$time1 = microtime(true) * 1000;
echo "常规函数读取 10W行数据库数据内存使用量\n";
foreach (readSqlData() as $key => $value) {
    # code...
}
$time2 = microtime(true) * 1000;
echo "耗时:".( round($time2 - $time1,3)). "毫秒\n";

$time1 = microtime(true) * 1000;
echo "\nyield函数读取 10W行数据库数据内存使用量\n";
foreach (readSqlDataYield() as $key => $value) {
    # code...
}
$time2 = microtime(true) * 1000;
echo "耗时:".( round($time2 - $time1,3)). "毫秒\n";

执行结果分析:

综合三种场景,发现内存使用量明显减少,并且执行用时不会有太大差异。

相关文章:

  • 浏览器渲染原理与优化详解
  • Pytest的Fixture使用
  • 异常断电导致current redo损坏处理
  • 练手项目1:Dockerized Redis访问计数器
  • 【AI】MAC版本本地Stable Diffusion web ui安装
  • 深度剖析:域名与DNS安全的全方位解读
  • UFS Explorer Technician:处理最具挑战性数据恢复及取证案例
  • 【0】数据结构的绪论章
  • DS足球监控【比分直播】监控,钉钉实现自动提醒
  • MySQL-存储过程
  • 05.AI搭建preparationの(transformers01)BertTokenizer实现分词编码
  • 命令模式(Command Pattern)★
  • Linux内核调试 - Hung_task机制分析上
  • 语言模型理论基础-持续更新-思路清晰
  • 无人机螺旋桨平衡标准
  • LangChain开发(二)LangChain提示词模板Template使用
  • 2.7 进度控制习题-2
  • 【JS】反转字符串中的单词
  • 计算机网络基础:WiFi 与蓝牙的原理与应用
  • JavaScript中的继承有哪些方式?各有什么优缺点
  • 上海市政府常务会议部署提升入境旅游公共服务水平,让国际友人“无障碍”畅游上海
  • 上海市委常委会扩大会议传达学习习近平总书记考察上海重要讲话和在部分省区市“十五五”时期经济社会发展座谈会上的重要讲话精神
  • 山东如意集团及实控人等被出具警示函,此前实控人已被罚十年禁止入市
  • 许昌市场监管部门对胖东来玉石开展日常检查:平均毛利率不超20%
  • 巴基斯坦宣布禁止与印度的进口贸易
  • 五一假期,新任杭州市委书记刘非到嘉兴南湖瞻仰红船