共计 1382 个字符,预计需要花费 4 分钟才能阅读完成。
最新的优化,减少了从 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
:标准的glibc
malloc
-
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()
重置recv_buffer
缓存区,这会存在一次内存拷贝,但是复用一块内存的 - 使用
swString_pop()
将recv_buffer->str
整块内存弹出,在应用层使用,底层会自动分配新的内存,用于接收下一个包
本次的 zerocopy
就是使用第二种方式,recv_buffer
使用了 SWOOLE_G(zend_string_allocator)
内存分配器,弹出来的 recv_buffer->str
内存,正好是一个 zend_string
的val
,再使用 sw_get_zend_string(recv_buffer->str)
就可以得到 zend_string
对象的内存地址,最后使用 ZVAL_STR()
或者 RETURN_STR()
可直接将 zend_string
对象作为 PHP
层函数调用的返回值。
正文完