Swoole kernel development memo: memory management optimization (swString)

Posted Jun 26, 20202 min read

The latest optimization reduces the memory copy from recv_buffer to php zval, which can be changed from recv_buffer to string type variable at the PHP layer, which is equivalent to directly from the Socket receive buffer Read the PHP layer.

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

swString structure

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

In design, the roles of these fields are:

  • size:the length of the memory capacity, if there is free space during the append write operation, there is no need to expand the capacity. When the actual data length is equal to size, the memory needs to be expanded, by calling swString_extend()
  • length:The actual data length must be less than or equal to size, otherwise the memory will cross the boundary, and the underlying swString_* series functions will check the boundary to avoid reading and writing beyond the boundary
  • offset:Operate the cursor to record the actual data processing position of the application layer. offset must be less than or equal to length

swString.jpg

Added allocator field, you can set memory allocator, there are currently 3 types.

  • SwooleG.std_allocator:standard glibc malloc
  • SWOOLE_G(php_allocator):PHP's emalloc
  • SWOOLE_G(zend_string_allocator):zend_string_alloc

data processing

coroutine::Socket::recv_packet() reads data from Socket in two stages.

  1. Read the PacketHeader data, which may be a relatively small value, such as recv(sizeof(PacketHeader)), include the PacketLength field in the header to obtain the total length of the entire packet
  2. Read Payload data, recv(PacketLength-sizeof(PacketHeader)

The bottom layer will set the offset value to PacketLength in advance, and then collect data from the network in segments, call the recv operation, append the data to the cache area, and update the length value. When length==offset indicates that the reception is complete. coroutine::Socket::recv_packet() returns to the application layer. At this time, the application layer can have two operations.

  • Use memcpy to read the data from recv_buffer. The next time recv_packet is called, the bottom layer will automatically call swString_reduce() to reset the read_buffer cache area. This will cause a memory copy, but the complex With a piece of memory

  • Use swString_pop() to pop up the entire memory of read_buffer->str, used in the application layer, the bottom layer will automatically allocate new memory for receiving the next packet

    zval zdata;
    ssize_t retval = sock->recv_packet();

    //copy memory
    ZVAL_STRINGL(&zdata, sock->get_read_buffer()->str, retval);

    //pop up memory
    ZVAL_STR(&zdata, sw_get_zend_string(sock->pop_packet()));

This time zerocopy is the second way. read_buffer uses SWOOLE_G(zend_string_allocator) memory allocator. The popped up read_buffer->str memory is just a val of zend_string. Then use sw_get_zend_string(read_buffer->str) to get the memory address of the zend_string object, and finally use ZVAL_STR() or RETURN_STR() to directly call the zend_string object as a function of the PHP layer return value.