From 1b36a66243b405d49dabba21fdc40da37543d1bb Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Mon, 2 Apr 2018 17:25:47 +0200 Subject: [PATCH] Optimized decimal and hex string parsing in hot path. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using specialized and inline-able parsers instead of strtoul() improved pixel throughput from ~63MB/s to ~83MB/s (+30%) on a single core on my machine. As a side effect, the PX command now allows any non-decimal character as separator between parameters ¯\_(ツ)_/¯ --- pixelnuke/Makefile | 5 ++- pixelnuke/pixelnuke.c | 87 +++++++++++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/pixelnuke/Makefile b/pixelnuke/Makefile index ee8dcd1..085e053 100644 --- a/pixelnuke/Makefile +++ b/pixelnuke/Makefile @@ -1,15 +1,16 @@ .PHONY: default all clean CC = gcc -CFLAGS = -Wall -pthread -O2 +CFLAGS = -Wall -pthread LIBS = -levent -levent_pthreads -lrt -lGL -lGLEW -lglfw TARGET = pixelnuke +default: CFLAGS += -O2 -flto default: $(TARGET) all: default debug: CFLAGS += -DDEBUG -g -debug: default +debug: $(TARGET) OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) HEADERS = $(wildcard *.h) diff --git a/pixelnuke/pixelnuke.c b/pixelnuke/pixelnuke.c index 898eca8..d879328 100644 --- a/pixelnuke/pixelnuke.c +++ b/pixelnuke/pixelnuke.c @@ -10,9 +10,14 @@ unsigned int px_height = 1024; unsigned int px_pixelcount = 0; unsigned int px_clientcount = 0; +// User sessions +typedef struct PxSession { + +} PxSession; + // Helper functions -static int util_str_starts_with(const char* prefix, const char* str) { +static inline int fast_str_startswith(const char* prefix, const char* str) { char cp, cs; while ((cp = *prefix++) == (cs = *str++)) { if (cp == 0) @@ -21,39 +26,71 @@ static int util_str_starts_with(const char* prefix, const char* str) { return !cp; } +// Decimal string to unsigned int. This variant does NOT consume +, - or whitespace. +// If **endptr is not NULL, it will point to the first non-decimal character, which +// may be \0 at the end of the string. +static inline uint32_t fast_strtoul10(const char *str, const char **endptr) { + uint32_t result = 0; + unsigned char c; + for (; (c = *str - '0') <= 9; str++) + result = result * 10 + c; + if (endptr) + *endptr = str; + return result; +} + +// Same as fast_strtoul10, but for hex strings. +static inline uint32_t fast_strtoul16(const char *str, const char **endptr) { + uint32_t result = 0; + unsigned char c; + while ((c = *str - '0') <= 9 // 0-9 + || ((c -= 7) >= 10 && c <= 15) // A-F + || ((c -= 32) >= 10 && c <= 15)) { // a-f + result = result * 16 + c; + str++; + } + if (endptr) + *endptr = str; + return result; +} + // server callbacks void px_on_connect(NetClient *client) { px_clientcount++; } - void px_on_close(NetClient *client, int error) { px_clientcount--; } - void px_on_read(NetClient *client, char *line) { - if (util_str_starts_with("PX ", line)) { + if (fast_str_startswith("PX ", line)) { const char * ptr = line + 3; - char * endptr = (char*) ptr; + const char * endptr = ptr; errno = 0; - unsigned int x = strtoul(ptr, &endptr, 10); - if (endptr == ptr || errno) { + uint32_t x = fast_strtoul10(ptr, &endptr); + if (endptr == ptr) { net_err(client, - "First parameter missing or invalid (should be decimal)"); + "Invalid command (expected decimal as first parameter)"); + return; + } + if (*endptr == '\0') { + net_err(client, "Invalid command (second parameter required)"); return; } - unsigned int y = strtoul((ptr = endptr), &endptr, 10); - if (endptr == ptr || errno) { + endptr++; // eat space (or whatever non-decimal is found here) + + uint32_t y = fast_strtoul10((ptr = endptr), &endptr); + if (endptr == ptr) { net_err(client, - "Second parameter missing or invalid (should be decimal)"); + "Invalid command (expected decimal as second parameter)"); return; } // PX -> Get RGB color at position (x,y) or '0x000000' for out-of-range queries - if (*endptr == 0) { + if (*endptr == '\0') { uint32_t c; canvas_get_px(x, y, &c); char str[64]; @@ -62,20 +99,22 @@ void px_on_read(NetClient *client, char *line) { return; } + endptr++; // eat space (or whatever non-decimal is found here) + // PX BB|RRGGBB|RRGGBBAA - unsigned long int c = strtoul((ptr = endptr), &endptr, 16); - if (endptr == ptr || errno) { + uint32_t c = fast_strtoul16((ptr = endptr), &endptr); + if (endptr == ptr) { net_err(client, "Third parameter missing or invalid (should be hex color)"); return; } - if (endptr - 1 - 6 == ptr) { - // RGB -> RGBA + if (endptr - ptr == 6) { + // RGB -> RGBA (most common) c = (c << 8) + 0xff; - } else if (endptr - 1 - 8 == ptr) { + } else if (endptr - ptr == 8) { // done - } else if (endptr - 1 - 2 == ptr) { + } else if (endptr - ptr == 2) { // WW -> RGBA c = (c << 24) + (c << 16) + (c << 8) + 0xff; } else { @@ -87,22 +126,23 @@ void px_on_read(NetClient *client, char *line) { px_pixelcount++; canvas_set_px(x, y, c); - } else if (util_str_starts_with("SIZE", line)) { + } else if (fast_str_startswith("SIZE", line)) { char str[64]; snprintf(str, 64, "SIZE %d %d", px_width, px_height); net_send(client, str); - } else if (util_str_starts_with("STATS", line)) { + } else if (fast_str_startswith("STATS", line)) { char str[128]; - snprintf(str, 128, "STATS px:%u conn:%u", px_pixelcount, px_clientcount); + snprintf(str, 128, "STATS px:%u conn:%u", px_pixelcount, + px_clientcount); net_send(client, str); - } else if (util_str_starts_with("HELP", line)) { + } else if (fast_str_startswith("HELP", line)) { net_send(client, -"\ + "\ PX x y: Get color at position (x,y)\n\ PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\ SIZE: Get canvas size\n\ @@ -115,7 +155,6 @@ STATS: Return statistics"); } } - void px_on_key(int key, int scancode, int mods) { printf("Key pressed: key:%d scancode:%d mods:%d\n", key, scancode, mods);