最新的优化,减少了从recv_buffer到php 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:标准的glibcmalloc -
SWOOLE_G(php_allocator):PHP 的emalloc -
SWOOLE_G(zend_string_allocator):zend_string_alloc
数据处理
coroutine::Socket::recv_packet() 分为两个阶段从Socket中读取数据。
- 读取
PacketHeader数据,可能是一个比较小的值,如recv(sizeof(PacketHeader)),在头部中包含PacketLength字段,获取整个包的总长度 - 读取
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_string 的val,再使用 sw_get_zend_string(read_buffer->str) 就可以得到 zend_string 对象的内存地址,最后使用 ZVAL_STR() 或者 RETURN_STR() 可直接将 zend_string 对象作为 PHP 层函数调用的返回值。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。