From: Crystal Kolipe Subject: Reduce cpu load due to framebuffer updates To: tech@openbsd.org Date: Tue, 08 Jul 2025 05:49:54 -0300 Programs which write fast enough to the framebuffer console create a significant bottleneck in the character-by-character glyph plotting code. This wastes a lot of cpu cycles and affects other processes on the system. We can avoid this, and gain anything up to a nine fold performance increase by simply detecting large floods of output and in this case not updating the bitmapped display for every character but instead only updating the scrollback buffer, and then re-painting the screen at a fixed refresh rate. With this approach, characters which scroll off the display before the next refresh cycle are never even drawn. The attached patch is a proof of concept. The algorithm for detecting sufficient output and the threshold for enabling the new mode of operation can likely be improved. Despite this, it already works quite well. An optional debugging feature can be enabled by defining FLUX_INDICATOR_ENABLE which will display a red exclamation mark indicator in the top left of the screen when the new code is active, and a blue dot indicator when it's not active. --- sys/dev/rasops/rasops.c Tue Jul 8 08:20:40 2025 +++ sys/dev/rasops/rasops.c Tue Jul 8 09:07:28 2025 @@ -45,6 +45,23 @@ #include #endif +#define FLUX_INDICATOR_ENABLE +#ifdef FLUX_INDICATOR_ENABLE +#define FLUX_INDICATOR_OFF(ri) ri->ri_putchar(ri, 0, 0, '.', 0x0b040000); +#define FLUX_INDICATOR_ON(ri) ri->ri_putchar(ri, 0, 0, '!', 0x0b090000); +#define FLUX_INDICATOR_SKIP (row == 0) ? 1 : 0 +#endif +#ifndef FLUX_INDICATOR_ENABLE +#define FLUX_INDICATOR_ON(ri) do {} while (0); +#define FLUX_INDICATOR_OFF(ri) do {} while (0); +#define FLUX_INDICATOR_SKIP 0 +#endif +#define FLUX_TIME_PERIOD 1000000000 +#define FLUX_TIME_PERIOD_MAX (2 * FLUX_TIME_PERIOD) +#define FLUX_FLOW_LIMIT 384 +#define FLUX_REFRESH 60 +#define FLUX_REFRESH_NANO (1000000000 / FLUX_REFRESH) + /* ANSI colormap (R,G,B) */ #define NORMAL_BLACK 0x000000 @@ -192,6 +209,7 @@ int rasops_add_font(struct rasops_info *, struct wsdisplay_font *); int rasops_use_font(struct rasops_info *, struct wsdisplay_font *); int rasops_list_font_cb(void *, struct wsdisplay_font *); +void rasops_flux(void *); /* * Initialize a 'rasops_info' descriptor. @@ -322,6 +340,9 @@ task_set(&ri->ri_switchtask, rasops_doswitch, ri); rasops_init_devcmap(ri); + ri->ri_flux_count = 0; + ri->ri_flux_t = 0; + timeout_set(&ri->ri_flux_to, rasops_flux, ri); return (0); } @@ -1532,6 +1553,7 @@ { struct rasops_screen *scr = cookie; int off = row * scr->rs_ri->ri_cols + col + scr->rs_dispoffset; + uint64_t delta; if (scr->rs_visible && scr->rs_visibleoffset != scr->rs_dispoffset) rasops_scrollback(scr->rs_ri, scr, 0); @@ -1542,6 +1564,22 @@ if (!scr->rs_visible) return 0; + (scr->rs_ri->ri_flux_count)++; + if (scr->rs_ri->ri_flux_active == 1) + return 0; + delta = getnsecuptime() - scr->rs_ri->ri_flux_t; + if (delta > FLUX_TIME_PERIOD) { + if (scr->rs_ri->ri_flux_count > FLUX_FLOW_LIMIT && + delta < FLUX_TIME_PERIOD_MAX) { + FLUX_INDICATOR_ON(scr->rs_ri); + scr->rs_ri->ri_flux_active = 1; + timeout_add_nsec(&(scr->rs_ri->ri_flux_to), + FLUX_REFRESH_NANO); + } + scr->rs_ri->ri_flux_count = 0; + scr->rs_ri->ri_flux_t = getnsecuptime(); + } + return scr->rs_ri->ri_putchar(scr->rs_ri, row, col, uc, attr); } @@ -1638,7 +1676,8 @@ continue; scr->rs_bs[off].uc = newc; scr->rs_bs[off].attr = newa; - rc = ri->ri_putchar(ri, row, col, newc, newa); + if (scr->rs_ri->ri_flux_active == 0) + rc = ri->ri_putchar(ri, row, col, newc, newa); if (rc != 0) return rc; } @@ -1999,4 +2038,32 @@ } return 0; +} + +void +rasops_flux(void * cookie) +{ + struct rasops_info *ri = cookie; + struct rasops_screen *scr = ri->ri_active; + int row, col, pos; + + if (ri->ri_flux_count <= FLUX_FLOW_LIMIT) { + FLUX_INDICATOR_OFF(ri); + ri->ri_flux_active = 0; + } else { + timeout_add_nsec(&(ri->ri_flux_to), FLUX_REFRESH_NANO); + } + ri->ri_flux_count = 0; + /* + * Repaint screen + */ + for (row = 0; row < ri->ri_rows; row++) + for (col = FLUX_INDICATOR_SKIP ; col < ri->ri_cols; col++) { + pos = (row * ri->ri_cols + col) + scr->rs_visibleoffset; + ri->ri_putchar(ri, row, col, scr->rs_bs[pos].uc, + scr->rs_bs[pos].attr); + } + if (ri->ri_flg & RI_CURSOR) + rasops_do_cursor(ri); + return ; } --- sys/dev/rasops/rasops.h Mon Apr 7 04:25:22 2025 +++ sys/dev/rasops/rasops.h Tue Jul 8 09:12:57 2025 @@ -34,6 +34,8 @@ #define _RASOPS_H_ 1 #include +#include +#include #ifdef SMALL_KERNEL #define RASOPS_SMALL @@ -107,6 +109,10 @@ int ri_xorigin; /* where ri_bits begins (x) */ int ri_yorigin; /* where ri_bits begins (y) */ int32_t ri_devcmap[16]; /* color -> framebuffer data */ + int ri_flux_count; /* calls to putchar() since last reset */ + uint64_t ri_flux_t; /* nanosecond time of last counter reset */ + struct timeout ri_flux_to; /* timeout for ri_flux */ + int ri_flux_active; /* flag set when periodic refresh is active */ /* The emulops you need to use, and the screen caps for wscons */ struct wsdisplay_emulops ri_ops;