wip
This commit is contained in:
parent
5b8ce3781c
commit
592f15fa1d
5 changed files with 737 additions and 106 deletions
249
new.c
249
new.c
|
|
@ -6,6 +6,13 @@
|
|||
* protocol. Pixels are picked in a random order so the picture "dissolves"
|
||||
* onto the wall instead of being drawn scanline by scanline.
|
||||
*
|
||||
* Throughput comes from parallelism: one sender core tops out around
|
||||
* ~0.65 Mpps on this hardware, so we run one self-contained worker thread per
|
||||
* CPU. Each worker owns its own socket, packet ring and PRNG and simply loops
|
||||
* "repaint the ring, blast it with sendmmsg()" as fast as it can. There is no
|
||||
* shared mutable state between workers, so it scales close to linearly. See
|
||||
* bench.c / pixelflut-bench for the measurements behind this design.
|
||||
*
|
||||
* Binary Pixelflut packet layout (little-endian on the wire):
|
||||
*
|
||||
* +------+---------+ pixelflut_packet
|
||||
|
|
@ -19,6 +26,7 @@
|
|||
|
||||
#define _GNU_SOURCE /* sendmmsg(), struct mmsghdr, SO_BINDTODEVICE */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -37,6 +45,8 @@
|
|||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "pixelflut.h"
|
||||
|
||||
/* Portable host -> little-endian 16-bit conversion (matches the wire format). */
|
||||
#if defined(__linux__)
|
||||
# include <endian.h>
|
||||
|
|
@ -58,76 +68,59 @@
|
|||
#define CANVAS_OFFSET_X 1200
|
||||
#define CANVAS_OFFSET_Y 40
|
||||
|
||||
/* ---- Pixelflut binary protocol. ---------------------------------------- */
|
||||
#define PIXELFLUT_CMD_SET 0x00 /* "set pixels" command ... */
|
||||
#define PIXELFLUT_SUBCMD_RGB 0x01 /* ... with 3-byte RGB colours */
|
||||
#define PIXELS_PER_PACKET 120 /* pixels batched into one UDP datagram */
|
||||
|
||||
/* Number of datagrams needed to cover a whole frame once. */
|
||||
#define PACKET_COUNT (FRAME_PIXELS / PIXELS_PER_PACKET)
|
||||
|
||||
/* ---- Target / input. --------------------------------------------------- */
|
||||
/* ---- Target / input (override at build time with -D...). --------------- */
|
||||
/* #define DISPLAY_HOST "100.65.0.2" */
|
||||
#define DISPLAY_HOST "fcda:f100::d"
|
||||
#define DISPLAY_PORT 5005
|
||||
#define INPUT_FILE "/tmp/video"
|
||||
#ifndef DISPLAY_HOST
|
||||
# define DISPLAY_HOST "fcda:f100::d"
|
||||
#endif
|
||||
#ifndef DISPLAY_PORT
|
||||
# define DISPLAY_PORT 5005
|
||||
#endif
|
||||
#ifndef INPUT_FILE
|
||||
# define INPUT_FILE "/tmp/video"
|
||||
#endif
|
||||
|
||||
/* A single pixel as it appears on the wire. */
|
||||
#pragma pack(push, 1)
|
||||
struct pixelflut_pixel {
|
||||
uint16_t x; /* little-endian */
|
||||
uint16_t y; /* little-endian */
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
#define MAX_WORKERS 256
|
||||
|
||||
/* A full datagram: header followed by a batch of pixels. */
|
||||
struct pixelflut_packet {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
struct pixelflut_pixel pixels[PIXELS_PER_PACKET];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
_Static_assert(sizeof(struct pixelflut_pixel) == 7,
|
||||
"pixel must be exactly 7 bytes on the wire");
|
||||
_Static_assert(FRAME_PIXELS % PIXELS_PER_PACKET == 0,
|
||||
"frame pixels must divide evenly into packets");
|
||||
|
||||
/* Pre-built datagrams and the scatter/gather metadata sendmmsg() needs. */
|
||||
static struct pixelflut_packet packets[PACKET_COUNT];
|
||||
static struct mmsghdr messages[PACKET_COUNT];
|
||||
static struct iovec iovecs[PACKET_COUNT];
|
||||
/* xorshf96 PRNG (Marsaglia), period 2^96-1. Per-worker so there is no
|
||||
* contention and each worker paints a different random pixel stream. */
|
||||
struct rng {
|
||||
unsigned long x, y, z;
|
||||
};
|
||||
|
||||
/* xorshf96 PRNG state (Marsaglia), period 2^96-1. */
|
||||
static unsigned long rng_x = 123456789;
|
||||
static unsigned long rng_y = 362436069;
|
||||
static unsigned long rng_z = 521288629;
|
||||
|
||||
static unsigned long xorshf96(void)
|
||||
static unsigned long xorshf96(struct rng *r)
|
||||
{
|
||||
rng_x ^= rng_x << 16;
|
||||
rng_x ^= rng_x >> 5;
|
||||
rng_x ^= rng_x << 1;
|
||||
r->x ^= r->x << 16;
|
||||
r->x ^= r->x >> 5;
|
||||
r->x ^= r->x << 1;
|
||||
|
||||
unsigned long t = rng_x;
|
||||
rng_x = rng_y;
|
||||
rng_y = rng_z;
|
||||
rng_z = t ^ rng_x ^ rng_y;
|
||||
unsigned long t = r->x;
|
||||
r->x = r->y;
|
||||
r->y = r->z;
|
||||
r->z = t ^ r->x ^ r->y;
|
||||
|
||||
return rng_z;
|
||||
return r->z;
|
||||
}
|
||||
|
||||
/* Continuously blast the pre-built packets at the display. */
|
||||
static void *send_thread(void *arg)
|
||||
{
|
||||
const char *ifname = arg;
|
||||
struct worker {
|
||||
const char *ifname;
|
||||
const uint8_t *frame;
|
||||
int index;
|
||||
};
|
||||
|
||||
/* Create a UDP socket connected to the display, optionally pinned to a NIC. */
|
||||
static int open_display_socket(const char *ifname)
|
||||
{
|
||||
int fd = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
if (fd == -1) {
|
||||
perror("socket");
|
||||
exit(EXIT_FAILURE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef SO_BINDTODEVICE
|
||||
|
|
@ -137,9 +130,8 @@ static void *send_thread(void *arg)
|
|||
memset(&ifr, 0, sizeof ifr);
|
||||
snprintf(ifr.ifr_name, sizeof ifr.ifr_name, "%s", ifname);
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr,
|
||||
sizeof ifr) == -1) {
|
||||
sizeof ifr) == -1)
|
||||
perror("setsockopt(SO_BINDTODEVICE)");
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)ifname;
|
||||
|
|
@ -151,26 +143,92 @@ static void *send_thread(void *arg)
|
|||
addr.sin6_port = htons(DISPLAY_PORT);
|
||||
if (inet_pton(AF_INET6, DISPLAY_HOST, &addr.sin6_addr) != 1) {
|
||||
fprintf(stderr, "invalid display host: %s\n", DISPLAY_HOST);
|
||||
exit(EXIT_FAILURE);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
|
||||
perror("connect");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Repaint the whole ring by sampling random pixels from the current frame. */
|
||||
static void repaint(struct pixelflut_packet *packets, const uint8_t *frame,
|
||||
struct rng *rng)
|
||||
{
|
||||
for (int i = 0; i < PACKET_COUNT; i++) {
|
||||
for (int j = 0; j < PIXELS_PER_PACKET; j++) {
|
||||
uint64_t src = xorshf96(rng) % FRAME_PIXELS;
|
||||
uint16_t fx = (uint16_t)(src % FRAME_WIDTH);
|
||||
uint16_t fy = (uint16_t)(src / FRAME_WIDTH);
|
||||
const uint8_t *rgb = &frame[src * FRAME_CHANNELS];
|
||||
|
||||
struct pixelflut_pixel *px = &packets[i].pixels[j];
|
||||
px->x = htole16((uint16_t)(CANVAS_OFFSET_X + fx));
|
||||
px->y = htole16((uint16_t)(CANVAS_OFFSET_Y + fy));
|
||||
px->r = rgb[0];
|
||||
px->g = rgb[1];
|
||||
px->b = rgb[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* One worker: repaint and blast its own ring over its own socket, forever. */
|
||||
static void *worker_main(void *arg)
|
||||
{
|
||||
const struct worker *w = arg;
|
||||
|
||||
struct rng rng = {
|
||||
.x = 123456789UL ^ (2654435761UL * (unsigned long)(w->index + 1)),
|
||||
.y = 362436069UL + (unsigned long)(w->index + 1),
|
||||
.z = 521288629UL,
|
||||
};
|
||||
|
||||
int fd = open_display_socket(w->ifname);
|
||||
if (fd == -1)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
struct pixelflut_packet *packets = malloc(PACKET_COUNT * sizeof *packets);
|
||||
struct mmsghdr *messages = calloc(PACKET_COUNT, sizeof *messages);
|
||||
struct iovec *iovecs = calloc(PACKET_COUNT, sizeof *iovecs);
|
||||
if (packets == NULL || messages == NULL || iovecs == NULL) {
|
||||
fprintf(stderr, "worker %d: out of memory\n", w->index);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
sleep(1); /* give the producer time to fill the first frame */
|
||||
for (int i = 0; i < PACKET_COUNT; i++) {
|
||||
packets[i].cmd = PIXELFLUT_CMD_SET;
|
||||
packets[i].subcmd = PIXELFLUT_SUBCMD_RGB;
|
||||
iovecs[i].iov_base = &packets[i];
|
||||
iovecs[i].iov_len = sizeof packets[i];
|
||||
messages[i].msg_hdr.msg_iov = &iovecs[i];
|
||||
messages[i].msg_hdr.msg_iovlen = 1;
|
||||
}
|
||||
|
||||
int next = 0;
|
||||
for (;;) {
|
||||
int sent = sendmmsg(fd, &messages[next], PACKET_COUNT - next, 0);
|
||||
if (sent == -1) {
|
||||
perror("sendmmsg");
|
||||
continue;
|
||||
repaint(packets, w->frame, &rng);
|
||||
|
||||
int next = 0;
|
||||
while (next < PACKET_COUNT) {
|
||||
int sent = sendmmsg(fd, &messages[next],
|
||||
(unsigned int)(PACKET_COUNT - next), 0);
|
||||
if (sent == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
/* ECONNREFUSED is stale ICMP feedback on a connected
|
||||
* UDP socket; EAGAIN means the send buffer is full.
|
||||
* Both are transient - repaint and keep sending. */
|
||||
if (errno != ECONNREFUSED && errno != EAGAIN &&
|
||||
errno != EWOULDBLOCK)
|
||||
perror("sendmmsg");
|
||||
break;
|
||||
}
|
||||
next += sent;
|
||||
}
|
||||
next += sent; /* resume after a partial send, wrap at the end */
|
||||
if (next >= PACKET_COUNT)
|
||||
next = 0;
|
||||
}
|
||||
|
||||
return NULL; /* unreachable */
|
||||
|
|
@ -178,25 +236,19 @@ static void *send_thread(void *arg)
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s <interface>\n", argv[0]);
|
||||
if (argc < 2 || argc > 3) {
|
||||
fprintf(stderr, "usage: %s <interface> [threads]\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const char *ifname = argv[1];
|
||||
|
||||
rng_x = (unsigned long)rand();
|
||||
rng_y = (unsigned long)rand();
|
||||
rng_z = (unsigned long)rand();
|
||||
|
||||
/* Pre-fill the constant parts of every datagram once. */
|
||||
for (int i = 0; i < PACKET_COUNT; i++) {
|
||||
packets[i].cmd = PIXELFLUT_CMD_SET;
|
||||
packets[i].subcmd = PIXELFLUT_SUBCMD_RGB;
|
||||
|
||||
iovecs[i].iov_base = &packets[i];
|
||||
iovecs[i].iov_len = sizeof packets[i];
|
||||
messages[i].msg_hdr.msg_iov = &iovecs[i];
|
||||
messages[i].msg_hdr.msg_iovlen = 1;
|
||||
}
|
||||
int workers = (int)sysconf(_SC_NPROCESSORS_ONLN);
|
||||
if (argc == 3)
|
||||
workers = atoi(argv[2]);
|
||||
if (workers < 1)
|
||||
workers = 1;
|
||||
if (workers > MAX_WORKERS)
|
||||
workers = MAX_WORKERS;
|
||||
|
||||
int frame_fd = open(INPUT_FILE, O_RDONLY);
|
||||
if (frame_fd == -1) {
|
||||
|
|
@ -211,29 +263,28 @@ int main(int argc, char *argv[])
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
pthread_t sender;
|
||||
if (pthread_create(&sender, NULL, send_thread, argv[1]) != 0) {
|
||||
perror("pthread_create");
|
||||
printf("rendering %s to [%s]:%d via %s using %d worker(s)\n", INPUT_FILE,
|
||||
DISPLAY_HOST, DISPLAY_PORT, ifname, workers);
|
||||
|
||||
struct worker *wctx = calloc((size_t)workers, sizeof *wctx);
|
||||
pthread_t *tids = calloc((size_t)workers, sizeof *tids);
|
||||
if (wctx == NULL || tids == NULL) {
|
||||
fprintf(stderr, "out of memory\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Repeatedly repaint the frame in a random pixel order. */
|
||||
for (;;) {
|
||||
for (int i = 0; i < PACKET_COUNT; i++) {
|
||||
for (int j = 0; j < PIXELS_PER_PACKET; j++) {
|
||||
uint64_t src = xorshf96() % FRAME_PIXELS;
|
||||
uint16_t fx = (uint16_t)(src % FRAME_WIDTH);
|
||||
uint16_t fy = (uint16_t)(src / FRAME_WIDTH);
|
||||
const uint8_t *rgb = &frame[src * FRAME_CHANNELS];
|
||||
|
||||
struct pixelflut_pixel *px = &packets[i].pixels[j];
|
||||
px->x = htole16((uint16_t)(CANVAS_OFFSET_X + fx));
|
||||
px->y = htole16((uint16_t)(CANVAS_OFFSET_Y + fy));
|
||||
px->r = rgb[0];
|
||||
px->g = rgb[1];
|
||||
px->b = rgb[2];
|
||||
}
|
||||
for (int i = 0; i < workers; i++) {
|
||||
wctx[i] = (struct worker){.ifname = ifname, .frame = frame, .index = i};
|
||||
if (pthread_create(&tids[i], NULL, worker_main, &wctx[i]) != 0) {
|
||||
perror("pthread_create");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < workers; i++)
|
||||
pthread_join(tids[i], NULL);
|
||||
|
||||
free(wctx);
|
||||
free(tids);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue