Swoole-内核开发备忘内存管理优化swString

34次阅读

共计 1382 个字符,预计需要花费 4 分钟才能阅读完成。

最新的优化,减少了从 recv_bufferphp zval的内存 copy,可以从recv_buffer 变为 PHP 层的 string 类型变量,相当于直接从 Socket 接收缓存区中读取到了 PHP 层。

GitHub PR:https://github.com/swoole/swoole-src/pull/3423

swString 结构体

typedef struct _swString
{
    size_t length;
    size_t size;
    off_t offset;
    char *str;
    const swAllocator *allocator;
} swString;

在设计上,这几个字段的作用分别是:

  • size:内存容量长度,进行 append 写入操作时,如果有空余的空间,无需扩容。当实际数据长度等于 size 时,需要进行内存扩容,通过调用 swString_extend() 完成
  • length:实际数据长度,必须小于或等于 size 否则会内存越界,底层的 swString_* 系列函数会检查边界,避免越界读写
  • offset:操作游标,记录应用层实际处理数据的位置,offset必须小于或等于length

新增了 allocator 字段,可以设置内存分配器,目前有 3 种。

  • SwooleG.std_allocator:标准的glibc malloc
  • SWOOLE_G(php_allocator):PHP 的 emalloc
  • SWOOLE_G(zend_string_allocator)zend_string_alloc

数据处理

coroutine::Socket::recv_packet() 分为两个阶段从 Socket 中读取数据。

  1. 读取 PacketHeader 数据,可能是一个比较小的值,如 recv(sizeof(PacketHeader)),在头部中包含 PacketLength 字段,获取整个包的总长度
  2. 读取 Payload 数据,recv(PacketLength - sizeof(PacketHeader)

底层会预先将 offset 值设置为 PacketLength,然后分段从网络收取数据,调用recv 操作,将数据追加到缓存区,并更新 length 值。当 length==offset 时表示,接收完毕。coroutine::Socket::recv_packet()返回到应用层。这时应用层可以有两个操作。

  • 使用 memcpy 将数据从 recv_buffer 中读取出来,下一次调用 recv_packet 时,底层会自动调用 swString_reduce() 重置 recv_buffer 缓存区,这会存在一次内存拷贝,但是复用一块内存的
  • 使用 swString_pop()recv_buffer->str整块内存弹出,在应用层使用,底层会自动分配新的内存,用于接收下一个包

本次的 zerocopy 就是使用第二种方式,recv_buffer 使用了 SWOOLE_G(zend_string_allocator) 内存分配器,弹出来的 recv_buffer->str 内存,正好是一个 zend_stringval,再使用 sw_get_zend_string(recv_buffer->str) 就可以得到 zend_string 对象的内存地址,最后使用 ZVAL_STR() 或者 RETURN_STR() 可直接将 zend_string 对象作为 PHP 层函数调用的返回值。

正文完
 0