Skip to content

Commit bd941d6

Browse files
committed
Optimize fifo_queue
1 parent 4adfaa1 commit bd941d6

File tree

2 files changed

+111
-71
lines changed

2 files changed

+111
-71
lines changed

libretro-common/include/queues/fifo_queue.h

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -33,45 +33,62 @@
3333

3434
RETRO_BEGIN_DECLS
3535

36-
/**
37-
* Returns the available data in \c buffer for reading.
38-
*
39-
* @param buffer <tt>fifo_buffer_t *</tt>. The FIFO queue to check.
40-
* @return The number of bytes available for reading from \c buffer.
41-
*/
42-
#define FIFO_READ_AVAIL(buffer) (((buffer)->end + (((buffer)->end < (buffer)->first) ? (buffer)->size : 0)) - (buffer)->first)
43-
44-
/**
45-
* Returns the available space in \c buffer for writing.
46-
*
47-
* @param buffer <tt>fifo_buffer_t *</tt>. The FIFO queue to check.
48-
* @return The number of bytes that \c buffer can accept.
49-
*/
50-
#define FIFO_WRITE_AVAIL(buffer) (((buffer)->size - 1) - (((buffer)->end + (((buffer)->end < (buffer)->first) ? (buffer)->size : 0)) - (buffer)->first))
51-
52-
/**
53-
* Returns the available data in \c buffer for reading.
54-
*
55-
* @param buffer \c fifo_buffer_t. The FIFO queue to check.
56-
* @return The number of bytes available for reading from \c buffer.
57-
*/
58-
#define FIFO_READ_AVAIL_NONPTR(buffer) (((buffer).end + (((buffer).end < (buffer).first) ? (buffer).size : 0)) - (buffer).first)
59-
60-
/**
61-
* Returns the available space in \c buffer for writing.
62-
*
63-
* @param buffer \c fifo_buffer_t. The FIFO queue to check.
64-
* @return The number of bytes that \c buffer can accept.
65-
*/
66-
#define FIFO_WRITE_AVAIL_NONPTR(buffer) (((buffer).size - 1) - (((buffer).end + (((buffer).end < (buffer).first) ? (buffer).size : 0)) - (buffer).first))
36+
#ifdef __GNUC__
37+
/* GCC / Clang: use statement-expression to evaluate end_adj once. */
38+
39+
#define FIFO_READ_AVAIL(buffer) \
40+
__extension__({ \
41+
const size_t _end_adj_ = (buffer)->end + \
42+
(((buffer)->end < (buffer)->first) ? (buffer)->size : 0u); \
43+
_end_adj_ - (buffer)->first; \
44+
})
45+
46+
#define FIFO_WRITE_AVAIL(buffer) \
47+
__extension__({ \
48+
const size_t _end_adj_ = (buffer)->end + \
49+
(((buffer)->end < (buffer)->first) ? (buffer)->size : 0u); \
50+
((buffer)->size - 1u) - (_end_adj_ - (buffer)->first); \
51+
})
52+
53+
#define FIFO_READ_AVAIL_NONPTR(buffer) \
54+
__extension__({ \
55+
const size_t _end_adj_ = (buffer).end + \
56+
(((buffer).end < (buffer).first) ? (buffer).size : 0u); \
57+
_end_adj_ - (buffer).first; \
58+
})
59+
60+
#define FIFO_WRITE_AVAIL_NONPTR(buffer) \
61+
__extension__({ \
62+
const size_t _end_adj_ = (buffer).end + \
63+
(((buffer).end < (buffer).first) ? (buffer).size : 0u); \
64+
((buffer).size - 1u) - (_end_adj_ - (buffer).first); \
65+
})
66+
67+
#else
68+
/* Strict C89 fallback: double-evaluation is safe because the macro
69+
* arguments are plain struct-member accesses with no side effects. */
70+
71+
#define FIFO_READ_AVAIL(buffer) \
72+
(((buffer)->end + (((buffer)->end < (buffer)->first) ? (buffer)->size : 0)) - (buffer)->first)
73+
74+
#define FIFO_WRITE_AVAIL(buffer) \
75+
(((buffer)->size - 1) - (((buffer)->end + (((buffer)->end < (buffer)->first) ? (buffer)->size : 0)) - (buffer)->first))
76+
77+
#define FIFO_READ_AVAIL_NONPTR(buffer) \
78+
(((buffer).end + (((buffer).end < (buffer).first) ? (buffer).size : 0)) - (buffer).first)
79+
80+
#define FIFO_WRITE_AVAIL_NONPTR(buffer) \
81+
(((buffer).size - 1) - (((buffer).end + (((buffer).end < (buffer).first) ? (buffer).size : 0)) - (buffer).first))
82+
83+
#endif /* __GNUC__ */
6784

6885
/** @copydoc fifo_buffer_t */
6986
struct fifo_buffer
7087
{
7188
uint8_t *buffer;
72-
size_t size;
73-
size_t first;
74-
size_t end;
89+
size_t size;
90+
size_t first;
91+
size_t end;
7592
};
7693

7794
/**
@@ -108,12 +125,11 @@ fifo_buffer_t *fifo_new(size_t len);
108125
bool fifo_initialize(fifo_buffer_t *buf, size_t len);
109126

110127
/**
111-
* Resets the bounds of \c buffer,
112-
* effectively clearing it.
128+
* Resets the bounds of \c buffer, effectively clearing it.
129+
*
130+
* No memory will actually be freed, but the contents of \c buffer
131+
* will be overwritten with the next call to \c fifo_write.
113132
*
114-
* No memory will actually be freed,
115-
* but the contents of \c buffer will be overwritten
116-
* with the next call to \c fifo_write.
117133
* @param buffer The FIFO queue to clear.
118134
* Behavior is undefined if \c NULL.
119135
*/
@@ -153,8 +169,7 @@ void fifo_read(fifo_buffer_t *buffer, void *in_buf, size_t len);
153169
void fifo_free(fifo_buffer_t *buffer);
154170

155171
/**
156-
* Deallocates the contents of \c buffer,
157-
* but not \c buffer itself.
172+
* Deallocates the contents of \c buffer, but not \c buffer itself.
158173
*
159174
* Suitable for use with static or automatic \c fifo_buffer_t instances.
160175
*
@@ -164,7 +179,6 @@ void fifo_free(fifo_buffer_t *buffer);
164179
*/
165180
bool fifo_deinitialize(fifo_buffer_t *buffer);
166181

167-
168182
RETRO_END_DECLS
169183

170-
#endif
184+
#endif /* __LIBRETRO_SDK_FIFO_BUFFER_H */

libretro-common/queues/fifo_queue.c

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,30 @@
2929

3030
#include <queues/fifo_queue.h>
3131

32+
/* Branchless wrap: equivalent to (pos + delta) % size, but avoids
33+
* integer division. Valid only when pos + delta < 2 * size, which
34+
* is guaranteed here because callers limit delta to available space. */
35+
static INLINE size_t fifo_wrap(size_t pos, size_t delta, size_t size)
36+
{
37+
pos += delta;
38+
if (pos >= size)
39+
pos -= size;
40+
return pos;
41+
}
42+
3243
static bool fifo_initialize_internal(fifo_buffer_t *buf, size_t len)
3344
{
34-
uint8_t *buffer = (uint8_t*)calloc(1, len + 1);
45+
/* malloc instead of calloc: first/end are set explicitly below,
46+
* so zero-initialising every byte is unnecessary work. */
47+
uint8_t *buffer = (uint8_t*)malloc(len + 1);
3548

3649
if (!buffer)
3750
return false;
3851

39-
buf->buffer = buffer;
40-
buf->size = len + 1;
41-
buf->first = 0;
42-
buf->end = 0;
52+
buf->buffer = buffer;
53+
buf->size = len + 1;
54+
buf->first = 0;
55+
buf->end = 0;
4356

4457
return true;
4558
}
@@ -63,8 +76,10 @@ bool fifo_deinitialize(fifo_buffer_t *buffer)
6376
if (!buffer)
6477
return false;
6578

66-
if (buffer->buffer)
67-
free(buffer->buffer);
79+
/* buffer->buffer may legitimately be NULL if a previous
80+
* deinitialize already ran; free(NULL) is defined by C89 §4.10.3.2
81+
* to be a no-op, so the explicit NULL guard is unnecessary. */
82+
free(buffer->buffer);
6883
buffer->buffer = NULL;
6984
buffer->size = 0;
7085
buffer->first = 0;
@@ -91,36 +106,47 @@ fifo_buffer_t *fifo_new(size_t len)
91106

92107
void fifo_write(fifo_buffer_t *buffer, const void *in_buf, size_t len)
93108
{
94-
size_t first_write = len;
95-
size_t rest_write = 0;
109+
/* Cache to avoid repeated pointer dereferences in the hot path. */
110+
const size_t size = buffer->size;
111+
const size_t end = buffer->end;
112+
const uint8_t *src = (const uint8_t*)in_buf;
96113

97-
if (buffer->end + len > buffer->size)
114+
if (end + len <= size)
98115
{
99-
first_write = buffer->size - buffer->end;
100-
rest_write = len - first_write;
116+
/* Common case: data fits without wrapping. Single copy. */
117+
memcpy(buffer->buffer + end, src, len);
118+
}
119+
else
120+
{
121+
/* Wrap-around case: split into two copies. */
122+
const size_t first_write = size - end;
123+
memcpy(buffer->buffer + end, src, first_write);
124+
memcpy(buffer->buffer, src + first_write, len - first_write);
101125
}
102126

103-
memcpy(buffer->buffer + buffer->end, in_buf, first_write);
104-
if (rest_write > 0)
105-
memcpy(buffer->buffer, (const uint8_t*)in_buf + first_write, rest_write);
106-
107-
buffer->end = (buffer->end + len) % buffer->size;
127+
/* Subtract instead of modulo: end + len < 2 * size is guaranteed
128+
* by the caller honouring FIFO_WRITE_AVAIL. */
129+
buffer->end = fifo_wrap(end, len, size);
108130
}
109131

110132
void fifo_read(fifo_buffer_t *buffer, void *in_buf, size_t len)
111133
{
112-
size_t first_read = len;
113-
size_t rest_read = 0;
114-
115-
if (buffer->first + len > buffer->size)
134+
/* Cache to avoid repeated pointer dereferences in the hot path. */
135+
const size_t size = buffer->size;
136+
const size_t first = buffer->first;
137+
uint8_t *dst = (uint8_t*)in_buf;
138+
139+
/* Common case: data is contiguous. Single copy. */
140+
if (first + len <= size)
141+
memcpy(dst, buffer->buffer + first, len);
142+
else
116143
{
117-
first_read = buffer->size - buffer->first;
118-
rest_read = len - first_read;
144+
/* Wrap-around case: split into two copies. */
145+
const size_t first_read = size - first;
146+
memcpy(dst, buffer->buffer + first, first_read);
147+
memcpy(dst + first_read, buffer->buffer, len - first_read);
119148
}
120149

121-
memcpy(in_buf, (const uint8_t*)buffer->buffer + buffer->first, first_read);
122-
if (rest_read > 0)
123-
memcpy((uint8_t*)in_buf + first_read, buffer->buffer, rest_read);
124-
125-
buffer->first = (buffer->first + len) % buffer->size;
150+
/* Same wrap optimisation as fifo_write. */
151+
buffer->first = fifo_wrap(first, len, size);
126152
}

0 commit comments

Comments
 (0)