最新的優化,減少了從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()
重置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
層函式呼叫的返回值。