Swoole 内核开发备忘:内存管理优化(swString)

最新的优化,减少了从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

swString.jpg

新增了 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()重置read_buffer缓存区,这会存在一次内存拷贝,但是复用一块内存的
  • 使用swString_pop()read_buffer->str整块内存弹出,在应用层使用,底层会自动分配新的内存,用于接收下一个包
zval zdata;
ssize_t retval = sock->recv_packet();

// 复制内存
ZVAL_STRINGL(&zdata, sock->get_read_buffer()->str, retval);

// 弹出内存
ZVAL_STR(&zdata, sw_get_zend_string(sock->pop_packet()));

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

阅读 275

推荐阅读