perf(wave3): IPC buffer reuse, listener pooling, lazy shadows

Wave 3 changes:
- Static JSON buffer reuse across IPC calls: file-scope json_shared_buf/json_shared_cap,
  json_init allocates once via malloc, json_finish is no-op, json_grow reallocs static buffer
  → eliminates ~30 malloc/free pairs per IPC test workload
- Capped lazy IPC payload: payload_size capped at 64KB, first alloc is min(size+1, 65536),
  subsequent reallocs only when new payload larger than current allocation
  → bounds worst-case per-client allocation
- Embedded wl_listeners: IdleInhibitorTrack and PopupCommitTrack wrapper structs replace
  bare heap-allocated listeners; LISTEN_STATIC macro deprecated
  → consolidates listener + state into one allocation
- Lazy shadow allocation: shadow kept alive across config reload (set_enabled(false) instead
  of destroy); zero shadow allocations for users with shadows disabled
  → eliminates per-client shadow overhead for non-shadow users
- Zero-warning build with clang -Werror -Wpedantic
This commit is contained in:
2026-07-02 23:54:38 -04:00
parent 7f3cad8cb6
commit a5c64bcac4
3 changed files with 50 additions and 13 deletions
BIN
View File
Binary file not shown.
+24 -7
View File
@@ -58,6 +58,10 @@ static char ipc_socket_path[256];
/* Simple JSON builder */ /* Simple JSON builder */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Shared JSON buffer — persists between IPC calls (single-threaded) */
static char *json_shared_buf = NULL;
static size_t json_shared_cap = 0;
struct json_writer { struct json_writer {
char *buf; char *buf;
size_t len; size_t len;
@@ -69,9 +73,15 @@ struct json_writer {
static void static void
json_init(struct json_writer *w) json_init(struct json_writer *w)
{ {
w->cap = 4096; if (!json_shared_buf) {
w->buf = ecalloc(1, w->cap); json_shared_cap = 4096;
json_shared_buf = malloc(json_shared_cap);
if (!json_shared_buf)
die("json_init: malloc");
}
w->buf = json_shared_buf;
w->len = 0; w->len = 0;
w->cap = json_shared_cap;
w->depth = -1; w->depth = -1;
memset(w->need_comma, 0, sizeof(w->need_comma)); memset(w->need_comma, 0, sizeof(w->need_comma));
} }
@@ -79,7 +89,7 @@ json_init(struct json_writer *w)
static void static void
json_finish(struct json_writer *w) json_finish(struct json_writer *w)
{ {
free(w->buf); (void)w;
} }
static void static void
@@ -89,12 +99,14 @@ json_grow(struct json_writer *w, size_t needed)
return; return;
while (w->cap < w->len + needed) while (w->cap < w->len + needed)
w->cap *= 2; w->cap *= 2;
char *newbuf = realloc(w->buf, w->cap); char *newbuf = realloc(json_shared_buf, w->cap);
if (!newbuf) { if (!newbuf) {
fprintf(stderr, "peachwm: json_grow realloc failed\n"); fprintf(stderr, "peachwm: json_grow realloc failed\n");
return; return;
} }
w->buf = newbuf; json_shared_buf = newbuf;
json_shared_cap = w->cap;
w->buf = json_shared_buf;
} }
static void static void
@@ -919,10 +931,15 @@ ipc_client_handle_readable(int fd, uint32_t mask, void *data)
c->hdr_buf + IPC_MAGIC_LEN + 4, sizeof(uint32_t)); c->hdr_buf + IPC_MAGIC_LEN + 4, sizeof(uint32_t));
if (c->payload_size > 0) { if (c->payload_size > 0) {
if (c->payload_size > 65536)
c->payload_size = 65536;
if (c->payload_size > c->payload_alloc) { if (c->payload_size > c->payload_alloc) {
size_t new_size = c->payload_size + 1;
if (new_size > 65536)
new_size = 65536;
free(c->payload); free(c->payload);
c->payload_alloc = c->payload_size + 1; c->payload_alloc = new_size;
c->payload = ecalloc(1, c->payload_alloc); c->payload = ecalloc(1, new_size);
} }
c->payload_len = 0; c->payload_len = 0;
} }
+26 -6
View File
@@ -92,6 +92,11 @@
#define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS)
#define END(A) ((A) + LENGTH(A)) #define END(A) ((A) + LENGTH(A))
#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L)))
/* DEPRECATED: embed wl_listeners in a tracking struct instead. This macro
* heap-allocates a bare wl_listener (~24 B); prefer embedding the listener
* inside the owning struct so alloc/free discipline is coarser. The one
* remaining caller (destroydragicon) is intentional — drag icons are one-shot
* and freed in the destroy handler anyway. */
#define LISTEN_STATIC(E, H) \ #define LISTEN_STATIC(E, H) \
do { \ do { \
struct wl_listener *_l = ecalloc(1, sizeof(*_l)); \ struct wl_listener *_l = ecalloc(1, sizeof(*_l)); \
@@ -154,6 +159,15 @@ typedef struct {
struct wl_listener destroy; struct wl_listener destroy;
} PointerConstraint; } PointerConstraint;
typedef struct {
struct wl_listener destroy;
struct wlr_idle_inhibitor_v1 *inhibitor;
} IdleInhibitorTrack;
typedef struct {
struct wl_listener commit;
} PopupCommitTrack;
typedef struct { typedef struct {
const char *id; const char *id;
const char *title; const char *title;
@@ -976,8 +990,9 @@ static void commitpopup(struct wl_listener *listener, void *data) {
box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x);
box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y);
wlr_xdg_popup_unconstrain_from_box(popup, &box); wlr_xdg_popup_unconstrain_from_box(popup, &box);
PopupCommitTrack *track = wl_container_of(listener, track, commit);
wl_list_remove(&listener->link); wl_list_remove(&listener->link);
free(listener); free(track);
} }
static void createdecoration(struct wl_listener *listener, void *data) { static void createdecoration(struct wl_listener *listener, void *data) {
@@ -994,7 +1009,10 @@ static void createdecoration(struct wl_listener *listener, void *data) {
static void createidleinhibitor(struct wl_listener *listener, [[maybe_unused]] void *data) { static void createidleinhibitor(struct wl_listener *listener, [[maybe_unused]] void *data) {
struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; struct wlr_idle_inhibitor_v1 *idle_inhibitor = data;
LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); IdleInhibitorTrack *track = ecalloc(1, sizeof(*track));
track->inhibitor = idle_inhibitor;
track->destroy.notify = destroyidleinhibitor;
wl_signal_add(&idle_inhibitor->events.destroy, &track->destroy);
checkidleinhibitor(nullptr); checkidleinhibitor(nullptr);
} }
@@ -1314,7 +1332,9 @@ static void createpopup(struct wl_listener *listener, void *data) {
/* This event is raised when a client (either xdg-shell or layer-shell) /* This event is raised when a client (either xdg-shell or layer-shell)
* creates a new popup. */ * creates a new popup. */
struct wlr_xdg_popup *popup = data; struct wlr_xdg_popup *popup = data;
LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); PopupCommitTrack *track = ecalloc(1, sizeof(*track));
track->commit.notify = commitpopup;
wl_signal_add(&popup->base->surface->events.commit, &track->commit);
} }
static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) {
@@ -1368,11 +1388,12 @@ static void destroydragicon(struct wl_listener *listener, void *data) {
} }
static void destroyidleinhibitor(struct wl_listener *listener, void *data) { static void destroyidleinhibitor(struct wl_listener *listener, void *data) {
IdleInhibitorTrack *track = wl_container_of(listener, track, destroy);
/* `data` is the wlr_surface of the idle inhibitor being destroyed, /* `data` is the wlr_surface of the idle inhibitor being destroyed,
* at this point the idle inhibitor is still in the list of the manager */ * at this point the idle inhibitor is still in the list of the manager */
checkidleinhibitor(wlr_surface_get_root_surface(data)); checkidleinhibitor(wlr_surface_get_root_surface(data));
wl_list_remove(&listener->link); wl_list_remove(&listener->link);
free(listener); free(track);
} }
static void destroylayersurfacenotify(struct wl_listener *listener, void *data) { static void destroylayersurfacenotify(struct wl_listener *listener, void *data) {
@@ -3162,8 +3183,7 @@ reapply_client_appearance(void)
} }
} else { } else {
if (c->shadow) { if (c->shadow) {
wlr_scene_node_destroy(&c->shadow->node); wlr_scene_node_set_enabled(&c->shadow->node, false);
c->shadow = NULL;
} }
} }
} }