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 协议》,转载必须注明作者和本文链接
放肆疯狂,委婉洒脱。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 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年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!