[PATCH] include: Add single-producer single-consumer ringbuffer implementation.

Stefan Dösinger stefandoesinger at gmail.com
Fri Feb 18 08:49:17 CST 2022


Hi,

Am Dienstag, 15. Februar 2022, 09:45:11 EAT schrieb Davide Beatrici:
> The ringbuffer was written for the PipeWire driver I'm working on.
> 
> I'm committing it separately because its implementation is generic,
> allowing it to be used pretty much universally.

Having written the wined3d command stream ringbuffer (and now banging my head 
against making it faster and multiple-producer single-consumer I think there 
are a few improvements here.

> +struct ringbuffer
> +{
> +    BYTE *buf;
> +    UINT32 head;
> +    UINT32 tail;
> +    UINT32 size;
> +    volatile UINT32 pending;
> +};

You can avoid the separate allocation by making buf a flexible array at the end 
of the structure:

struct ringbuffer
{
    UINT32 head, tail, size;
    BYTE buf[1];
};

And then malloc this with sizeof(ringbuffer) - sizeof(ringbuffer->buf) + 
requested size). Or even better, offsetof: malloc(offsetof(struct ringbuffer, 
buf[size]))

The pending is redundant. You can extrapolate the amount of bytes in the buffer 
from head and tail, although you have to be careful re wraparound. There might 
be an advantage because only one field is InterlockedExchange'd (pending) 
instead of two (head, tail), but I'd need some benchmarking to be convinced 
that it is worth it. I think by having a redundant pending member you'll never 
be able to make this multiple-writer capable.

> +static inline UINT32 ringbuffer_read(struct ringbuffer *ringbuffer,
> void *dst, UINT32 size)
> ...
> +static inline UINT32 ringbuffer_write(struct ringbuffer *ringbuffer,
> const void *src, UINT32 size)

The API of passing a destination / source pointer and memcpy'ing is 
unfortunate. For any user that writes a large amount of data the memcpy will 
ruin performance.

Consider a function that returns a pointer into the buffer data where the 
caller can write plus a submission function that can be called once the data 
is ready to be consumed. Likewise, the _read function could return a pointer 
to the data in the buffer rather than copy it.

The behavior of no-write or partial writes if the buffer is full is troublesome 
too. If the caller has packets that only make sense as a whole it'd force the 
caller to add logic for short writes/reads. It would be good to have a 
blocking version of write and read that waits until space/data is available.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: This is a digitally signed message part.
URL: <http://www.winehq.org/pipermail/wine-devel/attachments/20220218/8dcd610d/attachment.sig>


More information about the wine-devel mailing list