PHP 性能优化:生成器 yield 的初体验

需求:用户发贴,粉丝收到通知。
了解了一下生成器,就用一下,虽然理解的还不是很深刻。

<?php

namespace App\Jobs;

use App\Models\User;
use App\Models\UserFollow;
use App\Notifications\NewThread;

class NewThreadNotification extends Job {

    /** @var mixed */
    private $thread;

    private $actor;



    public function __construct($thread,$actor) {
        $this->thread = $thread;
        $this->actor = $actor;
    }

    public function handle()
    {
        app('log')->info('----粉丝通知--这里是发送通知队列-----');
        //获取这个用户的所有粉丝
        $follow = UserFollow::query()
            ->where('to_user_id',$this->thread->user_id)
            ->with('fromUser')
            ->get();

        $users = $this->getFollowUser($follow);

        foreach ($users as $v){
            //发送微信通知
            $v->fromUser->notify(new NewThread($this->actor,$this->thread));
        }
        app('log')->info('----粉丝通知--发送完成-----');

        $this->delete();
    }

    public function getFollowUser($follow)
    {
        foreach ($follow as $v){
            yield $v;
        }
    }
}

还在考虑,要不要分组,目前粉丝量顶天几万。以上是初体验。。。

参考文章:segmentfault.com/a/119000001233485...

分片处理:#

PHP性能优化:生成器 yield的初体验

laravel cursor () 处理:#

PHP性能优化:生成器 yield的初体验

cursor () 源码也看了一下,我猜 laravel 里面的懒加载应该是这个原理;
file

file

file

file

本作品采用《CC 协议》,转载必须注明作者和本文链接
放肆疯狂,委婉洒脱。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 40

如果这就是你的正式代码,那不是平白浪费资源吗?生成器也没用上呀

3年前 评论

也许你要的是这样?


public function handle()
    {
        app('log')->info('----粉丝通知--这里是发送通知队列-----');

        //获取这个用户的所有粉丝
        $users = $this->getFollowUser();

        foreach ($users as $v){
            //发送微信通知
            $v->fromUser->notify(new NewThread($this->actor,$this->thread));
        }
        app('log')->info('----粉丝通知--发送完成-----');

        $this->delete();
    }

public function getFollowUser()
{
    $limit=100;
    $id=0;
    do{
        $follow = UserFollow::query()
            ->where('to_user_id',$this->thread->user_id)
            ->where('id','>',$id)
            ->with('fromUser')
            ->limit($limit)
            ->orderBy('id')
            ->get();
        foreach ($follow as $v){
            $id=$v->id;
            yield $v;
        }
    }while(count($follow)==$limit);
}
3年前 评论
liaosp 3年前

@renxiaotu

这是我改之前的,我觉得在循环里里面放 sql 查询不是很科学,还有你这样我得看看先 :sweat_smile:

file

3年前 评论
renxiaotu 3年前
bigdaxin (作者) (楼主) 3年前
renxiaotu 3年前
bigdaxin (作者) (楼主) 3年前

这样写没啥用啊

3年前 评论
bigdaxin (楼主) 3年前
人厶八夂 (作者) 3年前
bigdaxin (楼主) 3年前
人厶八夂 (作者) 3年前
bigdaxin (楼主) 3年前

@ 比如按照下面的写法,假设数据总量 10000 条,每次查询 100 条,共查 100 次,那内存最大占用就是 100 条数据,如果你换成生成器,这个情况有什么改观呢?

public function handle()
    {
        app('log')->info('----粉丝通知--这里是发送通知队列-----');

        //获取这个用户的所有粉丝
        $limit=100;
        $id=0;
        do{
            $follow = UserFollow::query()
                ->where('to_user_id',$this->thread->user_id)
                ->where('id','>',$id)
                ->with('fromUser')
                ->limit($limit)
                ->orderBy('id')
                ->get();
            foreach ($follow as $v){
                $id=$v->id;
                //发送微信通知
                $v->fromUser->notify(new NewThread($this->actor,$this->thread));
            }
        }while(count($follow)==$limit);
        app('log')->info('----粉丝通知--发送完成-----');

        $this->delete();
    }
3年前 评论

@你的这句就已经把 10000 条数据读到内存了,后面的循环无论什么方案都一样

$follow = UserFollow::query()
            ->where('to_user_id',$this->thread->user_id)
            ->with('fromUser')
            ->get();
3年前 评论

@renxiaotu 这里开始理解了,那我重要的是发送通知这个操作啊。这也没用吗

3年前 评论
renxiaotu 3年前
人厶八夂 3年前
renxiaotu 3年前
bigdaxin (作者) (楼主) 3年前
renxiaotu 3年前
renxiaotu 3年前
bigdaxin (作者) (楼主) 3年前
renxiaotu 3年前
bigdaxin (作者) (楼主) 3年前
renxiaotu 3年前
bigdaxin (作者) (楼主) 3年前

可以先理解 yeild,再运用到项目中 php 中 yield 的用法

3年前 评论
UserFollow::query()
    ->where('to_user_id',$this->thread->user_id)
    ->with('fromUser')
    ->cursor()
    ->each(function($v) {
        $v->fromUser->notify(new NewThread($this->actor,$this->thread));
    });
3年前 评论
bigdaxin (楼主) 3年前
renxiaotu 3年前
renxiaotu 3年前

关于 laravel cursor 原理,参考如下代码:

<?php

class Db{
    public $dbh;

    public function __construct()
    {
        $this->dbh = new PDO('mysql:host=127.0.0.1;port=3306;dbname=test', 'root', '',
            [
                PDO::ATTR_CASE => PDO::CASE_NATURAL,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
                PDO::ATTR_STRINGIFY_FETCHES => false,
                PDO::ATTR_EMULATE_PREPARES => true,
            ]);

    }

    public function fetch()
    {
        $sth = $this->dbh->prepare("select * from `stored_events`");

        $sth->setFetchMode(PDO::FETCH_OBJ);
        $sth->execute();

        while ($record = $sth->fetch()) {
            yield $record;
        }
    }
}

$db = new Db();

foreach ($db->fetch() as $v) {
    var_dump($v);
};
3年前 评论
bigdaxin (楼主) 3年前

@喝卵形 昨天我看了一下这个源码,大概就是这样子的。 :smile:

3年前 评论

:joy: 官网生成器举例 range 那个例子只是为了举例。官网的那个例子是可以用 for($i=0;$i<100;$i++) 完美代替的。生成器主要还是有点甜吧。感觉你说的场景不是很需要生成器。 生成器 这个名字很好的 。生成 调用的时候代表生成数据。其实和性能优化不是强关联。一些场景用 生成器可以优化代码,写的好看一些。比如之前我读 csv 文件用 yeild 封装了一下,每次调用读一行。代码也比较好看。不过要说不用生成器。其他写法也可以做到内存,速度上相差无几

3年前 评论
bigdaxin (楼主) 3年前
我们只希望世界和平 (作者) 3年前