swoole / swoole-src

🚀 Coroutine-based concurrency library for PHP
https://www.swoole.com
Apache License 2.0
18.25k stars 3.16k forks source link

Discuss: differences in memory usage between websocket servers with coroutine and asynchronous styles #5295

Closed xuanyanwow closed 1 month ago

xuanyanwow commented 1 month ago

使用文档最小demo代码启动websocket server,模拟250个客户端以连接到两种类型的WebSocket服务器。

得到结果协程风格的内存使用量约为3倍。

是底层预期之中的差距还是代码配置有优化空间?是何原因导致该差异。

Coroutine Server

<?php

declare(strict_types=1);

use Swoole\Coroutine\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;

use function Swoole\Coroutine\run;

// 内存3G
ini_set('memory_limit', '3072M');

run(function () {
    go(static function () {
        while (true) {
            // 内存使用
            $memory = memory_get_usage();
            $memory = $memory / 1024;
            $memory = $memory / 1024;
            $memory = round($memory, 2);
            echo '当前内存使用:' . $memory . 'MB' . PHP_EOL;
            sleep(1);
        }
    });

    $server = new Server('127.0.0.1', 9502, false);
    $server->handle('/websocket', static function (Request $request, Response $ws) {
        $ws->upgrade();
        while (true) {
            $frame = $ws->recv(-1);
            if ($frame === '') {
                $ws->close();
                break;
            }
            if ($frame === false) {
                $ws->close();
                break;
            }
            if ($frame->data == 'close' || get_class($frame) === CloseFrame::class) {
                $ws->close();
                break;
            }
        }
    });

    $server->start();
});

Asynchronous Server

<?php

declare(strict_types=1);
use Swoole\Coroutine;
use Swoole\Coroutine\Http\Server;

// 内存3G
ini_set('memory_limit', '3072M');

// 创建WebSocket Server对象,监听0.0.0.0:9502端口。
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
$ws->set(['worker_num' => 1]);

// 监听WebSocket连接打开事件。
$ws->on('Open', function ($ws, $request) {
});
$ws->on('Message', function ($ws, $request) {
});
// 监听WebSocket连接关闭事件。
$ws->on('Close', function ($ws, $fd) {
});

$ws->on('Start', function () {
    go(static function () {
        while (true) {
            // 内存使用
            $memory = memory_get_usage();
            $memory = $memory / 1024;
            $memory = $memory / 1024;
            $memory = round($memory, 2);
            echo '当前内存使用:' . $memory . 'MB' . PHP_EOL;
            Coroutine::sleep(1);
        }
    });
});

$ws->start();

Go Client

package main

import (
    "fmt"
    "log"
    "os"
    "os/signal"
    "time"

    "github.com/gorilla/websocket"
)

var addr = "127.0.0.1:9502" // WebSocket 服务器地址

func main() {
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)

    for i := 0; i < 250; i++ {
        go func() {
            conn, _, err := websocket.DefaultDialer.Dial("ws://"+addr+"/websocket", nil)
            if err != nil {
                log.Fatal("dial:", err)
            }
            defer conn.Close()

            for {
                select {
                case <-time.After(1 * time.Second):
                    err := conn.WriteMessage(websocket.TextMessage, []byte("hello"))
                    if err != nil {
                        log.Println("write:", err)
                        return
                    }

                    _, _, err = conn.ReadMessage()
                    if err != nil {
                        log.Println("read:", err)
                        return
                    } else {
                        // log.Println("read:", string(data))
                    }
                }
            }
        }()
    }

    fmt.Println("Press Ctrl+C to stop")
    select {1
    case <-interrupt:
        fmt.Println("退出进程")
        return
    }
}

Coroutine Server

当前内存使用:54.32MB
当前内存使用:54.32MB

Asynchronous Server

当前内存使用:18.42MB
当前内存使用:18.42MB
  1. What version of Swoole are you using (show your php --ri swoole)?

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 5.1.2
Built => Mar  5 2024 08:08:42
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
spinlock => enabled
rwlock => enabled
openssl => OpenSSL 3.1.4 24 Oct 2023
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
zlib => 1.3.1
brotli => E16781312/D16781312
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
mysqlnd => enabled
async_redis => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_fiber_mock => Off => Off
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608
  1. What is your machine environment used (show your uname -a & php -v & gcc -v) ?
PHP 8.2.15 (cli) (built: Jan 27 2024 04:53:38) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.15, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.15, Copyright (c), by Zend Technologies
matyhtf commented 1 month ago

异步 Server 在执行完 onMessage 回调之后协程就会退出,而 Coroutine Server 不同,同一个客户端连接总会有一个协程,因此 250 个客户端就会有 250 协程,需要占用内存。

xuanyanwow commented 1 month ago

异步 Server 在执行完 onMessage 回调之后协程就会退出,而 Coroutine Server 不同,同一个客户端连接总会有一个协程,因此 250 个客户端就会有 250 协程,需要占用内存。

250个协程不至于造成3倍的资源消耗吧

matyhtf commented 1 month ago

3倍只是巧合

xuanyanwow commented 1 month ago

并不是纠结于这个“3”,而是对于其中的消耗点想要更详细的了解


run(function () {
    $i = 250;
    while ($i--) {
        Coroutine::create(function () {
            while (true) {
                sleep(1);
            }
        });
    }

    // 内存使用
    $memory = memory_get_usage();
    $memory = $memory / 1024;
    $memory = $memory / 1024;
    $memory = round($memory, 2);
    echo '当前内存使用:' . $memory . 'MB' . PHP_EOL;

    $channel = new Channel(1);
    $channel->pop();
});

当前内存使用:3.84MB

而上面的实验结果差异为:54-18=36MB,不止协程占用的内存消耗