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:
+24
-7
@@ -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
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user