perf(zmq): optimize RX path using SIMD conversion and direct storage

Optimize the RX path by converting float complex samples received on ZMQ
to int16 complex format using SIMD right inside the polling thread. This
allows storing int16 complex (c16_t) samples directly inside the overflow_buffer,
skipping a temporary float buffer allocation and a memcpy in the receive function.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
This commit is contained in:
Bartosz Podrygajlo
2026-06-30 15:32:58 +02:00
parent 5fad2465f0
commit 9e94084e9d
3 changed files with 14 additions and 12 deletions

View File

@@ -63,18 +63,13 @@ bool zmq_tx_channel::align(uint64_t timestamp, std::chrono::milliseconds timeout
void zmq_rx_channel::receive(c16_t *samples, size_t nsamps)
{
size_t samples_popped = 0;
cf_t samples_float[nsamps];
while (samples_popped < (size_t)nsamps && !stopped_) {
size_t popped_now = buffer_.pop_samples(samples_float + samples_popped, nsamps - samples_popped);
size_t popped_now = buffer_.pop_samples(samples + samples_popped, nsamps - samples_popped);
samples_popped += popped_now;
if (popped_now == 0) {
usleep(100); // wait for more samples to arrive
}
}
for (size_t i = 0; i < nsamps; i++) {
samples[i].r = samples_float[i].r * c16_t_to_cf_t_factor + 0.5;
samples[i].i = samples_float[i].i * c16_t_to_cf_t_factor + 0.5;
}
}
void zmq_rx_channel::stop()
{

View File

@@ -36,11 +36,11 @@ class zmq_tx_channel {
class zmq_rx_channel {
public:
void *socket_;
overflow_buffer<cf_t> buffer_;
bool request_sent_;
std::atomic<bool> stopped_;
zmq_rx_channel(void *s, uint64_t buffer_size) : socket_(s), buffer_(buffer_size), stopped_(false)
void *socket_;
overflow_buffer<c16_t> buffer_;
bool request_sent_;
std::atomic<bool> stopped_;
zmq_rx_channel(void *s, uint64_t buffer_size) : socket_(s), buffer_(buffer_size), stopped_(false)
{
}
void receive(c16_t *samples, size_t nsamps);

View File

@@ -40,6 +40,7 @@
#include <ring_buffer.h>
#include <zmq.h>
#include "zmq_imported.h"
#include "zmq_simd.h"
#define ZMQ_SECTION "zmq"
#define ZMQ_TX_CHANNELS "tx_channels"
@@ -68,6 +69,7 @@ static void poll_thread(zmq_state_t *s)
{
s->poll_thread_running = true;
unsigned char *rx_buffer = static_cast<unsigned char *>(malloc(rx_buffer_size));
c16_t *rx_buffer_c16 = static_cast<c16_t *>(malloc(rx_buffer_size / sizeof(cf_t) * sizeof(c16_t)));
const auto num_tx_channels = s->tx_stream.channels_.size();
const auto num_rx_channels = s->rx_stream.channels_.size();
std::vector<zmq_pollitem_t> items(num_tx_channels + num_rx_channels);
@@ -145,7 +147,11 @@ static void poll_thread(zmq_state_t *s)
}
size_t num_samples_received = std::min(received_bytes, rx_buffer_size) / sizeof(cf_t);
cf_t *samples = reinterpret_cast<cf_t *>(rx_buffer);
size_t overflow = chan->buffer_.push_samples(samples, num_samples_received);
convert_samples_avx512_rx(reinterpret_cast<const float *>(samples),
reinterpret_cast<int16_t *>(rx_buffer_c16),
num_samples_received * 2,
c16_t_to_cf_t_factor);
size_t overflow = chan->buffer_.push_samples(rx_buffer_c16, num_samples_received);
if (rx_buffer_size < received_bytes) {
overflow += chan->buffer_.push_zeros((received_bytes - rx_buffer_size) / sizeof(cf_t));
}
@@ -162,6 +168,7 @@ static void poll_thread(zmq_state_t *s)
}
}
free(rx_buffer);
free(rx_buffer_c16);
}
static int zmq_write(openair0_device_t *device, openair0_timestamp_t timestamp, void **buff, int nsamps, int cc, int flags)