Index | Thread | Search

From:
Crystal Kolipe <kolipe.c@exoticsilicon.com>
Subject:
Reduce cpu load due to framebuffer updates
To:
tech@openbsd.org
Date:
Tue, 08 Jul 2025 05:49:54 -0300

Download raw body.

Thread
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 <errno.h>
 #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 <sys/task.h>
+#include <sys/types.h>
+#include <sys/timeout.h>
 
 #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;