[Spice-devel] [PATCH x11spice v3 3/3] Add cache for SHM segments
Frediano Ziglio
fziglio at redhat.com
Sat Aug 17 05:05:30 UTC 2019
>
> Add a cache to allow the reuse of SHM segments.
> Shared memory segments are added to the cache instead of being
> deallocated, and the cache is searched instead of/before allocating a
> new segment.
>
> Both the SHM segments and their attachment with the X server are cached.
>
> The cache currently has a fixed number of 10 entries, this provided a
> good cache hit rate while keeping memory usage under control.
> Building with DEBUG_SHM_CACHE defined and running with
> G_MESSAGES_DEBUG=all will periodically print out the SHM cache hit
> rate.
>
> On my Ubuntu 18.04 system running XFCE4 with a 2560x1440 screen, the
> cache hit rate starts around 72%. On-screen windows that update often
> and have consistently-sized damage rectangles are the best case. With
> several of those (scrolling terminal windows, web browser showing a
> WebGL demo), the hit rate slowly rises to around 92%.
> Operations that generate rapid damage reports (like resizing or moving
> windows) will lower the hit rate.
>
> Signed-off-by: Brendan Shanks <bshanks at codeweavers.com>
> ---
> src/display.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++----
> src/display.h | 11 +++-
> 2 files changed, 170 insertions(+), 16 deletions(-)
>
> diff --git a/src/display.c b/src/display.c
> index a2a18b8..55461a2 100644
> --- a/src/display.c
> +++ b/src/display.c
> @@ -213,11 +213,155 @@ static int register_for_events(display_t *d)
> return 0;
> }
>
> +static void shm_segment_destroy(display_t *d, shm_segment_t *segment)
> +{
> + if (segment->shmid == -1) {
> + return;
> + }
> +
> + xcb_shm_detach(d->c, segment->shmseg);
> + segment->shmseg = -1;
> +
> + shmdt(segment->shmaddr);
> + segment->shmaddr = NULL;
> +
> + shmctl(segment->shmid, IPC_RMID, NULL);
> + segment->shmid = -1;
> +}
> +
> +
> +static int shm_cache_get(display_t *d, size_t size, shm_segment_t *segment)
> +{
> + int i, ret;
> + shm_segment_t *bigger_entry = NULL;
> + shm_segment_t *entry_to_use = NULL;
> +
> +#if defined(DEBUG_SHM_CACHE)
> + static guint cache_hits = 0;
> + static guint cache_total = 0;
> +
> + cache_total++;
> +#endif
> +
> + g_mutex_lock(&d->shm_cache_mutex);
> +
> + /* Check the cache for a segment of size 'size' or bigger.
> + * Use an exact-size segment if found.
> + * If not, use the smallest entry that is big enough.
> + */
> + for (i = 0; i < G_N_ELEMENTS(d->shm_cache); i++) {
> + shm_segment_t *entry = &d->shm_cache[i];
> +
> + if (entry->shmid != -1) {
> + /* If a cache entry of the exact size being requested is found,
> use that */
> + if (size == entry->size) {
> + entry_to_use = entry;
> + break;
> + }
> +
> + /* Keep track of the next-biggest entry in case an exact-size
> match isn't found */
> + if (size < entry->size) {
> + if ((bigger_entry && entry->size < bigger_entry->size) ||
> !bigger_entry) {
This is equivalent to
if (!bigger_entry || entry->size < bigger_entry->size) {
> + bigger_entry = entry;
> + }
> + }
> + }
> + }
> +
> + /* An exact-size entry wasn't found, use the next biggest entry that was
> found */
> + if (!entry_to_use) {
> + entry_to_use = bigger_entry;
> + }
> +
> + if (entry_to_use) {
> + *segment = *entry_to_use;
> + entry_to_use->shmid = -1;
> +
> + ret = 1;
> +
> +#if defined(DEBUG_SHM_CACHE)
> + cache_hits++;
> + if ((cache_hits % 100 == 0)) {
> + g_debug("SHM cache hitrate: %u/%u (%.2f%%)", cache_hits,
> cache_total,
> + (float) ((float) cache_hits / (float) cache_total) *
> 100);
> + }
> +#endif
> + } else {
> + /* No usable entry found in the cache */
> + ret = 0;
> + }
> +
> + g_mutex_unlock(&d->shm_cache_mutex);
> + return ret;
> +}
> +
> +static int shm_cache_add(display_t *d, shm_segment_t *segment)
> +{
> + int i, ret;
> + shm_segment_t *smallest_entry = NULL;
> + shm_segment_t *entry_to_use = NULL;
> +
> + g_mutex_lock(&d->shm_cache_mutex);
> +
> + /* 'segment' is now unused, try to add it to the cache.
> + * Use an empty slot in the cache if available.
> + * If not, evict the smallest entry (which also must be smaller than
> 'segment') from the cache.
> + */
> + for (i = 0; i < G_N_ELEMENTS(d->shm_cache); i++) {
> + shm_segment_t *entry = &d->shm_cache[i];
> +
> + if (entry->shmid == -1) {
> + /* Use an empty slot if found */
> + entry_to_use = entry;
> + break;
> + }
> +
> + /* Keep track of the smallest entry that's smaller than 'segment' */
> + if (entry->size < segment->size) {
> + if (smallest_entry && entry->size < smallest_entry->size) {
> + smallest_entry = entry;
> + } else if (!smallest_entry) {
Similar here (single if),
if (!smallest_entry || entry->size < smallest_entry->size) {
> + smallest_entry = entry;
> + }
> + }
> + }
> +
> + /* If no empty entries were found, evict 'smallest_entry' and reuse it
> */
> + if (!entry_to_use && smallest_entry) {
> + shm_segment_destroy(d, smallest_entry);
> + entry_to_use = smallest_entry;
> + }
> +
> + if (entry_to_use) {
> + *entry_to_use = *segment;
> + ret = 1;
> + } else {
> + /* Cache is full, and contained no entries smaller than the one
> being added */
> + ret = 0;
> + }
> +
> + g_mutex_unlock(&d->shm_cache_mutex);
> + return ret;
> +}
> +
> +static void shm_cache_destroy(display_t *d)
> +{
> + int i;
> +
> + g_mutex_lock(&d->shm_cache_mutex);
> + for (i = 0; i < G_N_ELEMENTS(d->shm_cache); i++) {
> + shm_segment_t *entry = &d->shm_cache[i];
> +
> + shm_segment_destroy(d, entry);
> + }
> + g_mutex_unlock(&d->shm_cache_mutex);
> +}
>
> int display_open(display_t *d, session_t *session)
> {
> int scr;
> int rc;
> + int i;
> xcb_damage_query_version_cookie_t dcookie;
> xcb_damage_query_version_reply_t *damage_version;
> xcb_xkb_use_extension_cookie_t use_cookie;
> @@ -314,6 +458,11 @@ int display_open(display_t *d, session_t *session)
> if (rc)
> return rc;
>
> + g_mutex_init(&d->shm_cache_mutex);
> + for (i = 0; i < G_N_ELEMENTS(d->shm_cache); i++) {
> + d->shm_cache[i].shmid = -1;
> + }
> +
> rc = display_create_screen_images(d);
>
> g_message("Display %s opened", session->options.display ?
> session->options.display : "");
> @@ -321,17 +470,6 @@ int display_open(display_t *d, session_t *session)
> return rc;
> }
>
> -/*
> - TODO: Implement a cache for shared memory handles
> - That is, instead of doing the shmget/shmat for every read,
> - we should cache the shmid, and reuse if we're doing a later
> - read of a similar size.
> -
> - We would likely see a 40% improvement in raw read time. Note
> - that a callgrind run suggested that would be on the order of 5%
> - overall.
> -*/
> -
> shm_image_t *create_shm_image(display_t *d, unsigned int w, unsigned int h)
> {
> shm_image_t *shmi;
> @@ -349,6 +487,11 @@ shm_image_t *create_shm_image(display_t *d, unsigned int
> w, unsigned int h)
> shmi->bytes_per_line = (bits_per_pixel(d) / 8) * shmi->w;
> imgsize = shmi->bytes_per_line * shmi->h;
>
> + if (shm_cache_get(d, imgsize, &shmi->segment)) {
> + return shmi;
> + }
> +
> + /* No usable shared memory segment found in cache, allocate a new one */
> shmi->segment.shmid = shmget(IPC_PRIVATE, imgsize, IPC_CREAT | 0700);
> if (shmi->segment.shmid != -1)
> shmi->segment.shmaddr = shmat(shmi->segment.shmid, 0, 0);
> @@ -360,6 +503,7 @@ shm_image_t *create_shm_image(display_t *d, unsigned int
> w, unsigned int h)
> /* We tell shmctl to detach now; that prevents us from holding this
> shared memory segment forever in case of abnormal process exit. */
> shmctl(shmi->segment.shmid, IPC_RMID, NULL);
> + shmi->segment.size = imgsize;
>
> shmi->segment.shmseg = xcb_generate_id(d->c);
> cookie = xcb_shm_attach_checked(d->c, shmi->segment.shmseg,
> shmi->segment.shmid, 0);
> @@ -451,9 +595,10 @@ void display_copy_image_into_fullscreen(display_t *d,
> shm_image_t *shmi, int x,
>
> void destroy_shm_image(display_t *d, shm_image_t *shmi)
> {
> - xcb_shm_detach(d->c, shmi->segment.shmseg);
> - shmdt(shmi->segment.shmaddr);
> - shmctl(shmi->segment.shmid, IPC_RMID, NULL);
> + if (!shm_cache_add(d, &shmi->segment)) {
> + /* Could not add to cache, destroy this segment */
> + shm_segment_destroy(d, &shmi->segment);
> + }
> if (shmi->drawable_ptr)
> free(shmi->drawable_ptr);
> free(shmi);
> @@ -521,6 +666,8 @@ void display_stop_event_thread(display_t *d)
>
> void display_close(display_t *d)
> {
> + shm_cache_destroy(d);
> + g_mutex_clear(&d->shm_cache_mutex);
> xcb_damage_destroy(d->c, d->damage);
> display_destroy_screen_images(d);
> xcb_disconnect(d->c);
> diff --git a/src/display.h b/src/display.h
> index da1b991..0aea348 100644
> --- a/src/display.h
> +++ b/src/display.h
> @@ -21,18 +21,19 @@
> #ifndef DISPLAY_H_
> #define DISPLAY_H_
>
> +#include <glib.h>
> #include <xcb/xcb.h>
> #include <xcb/damage.h>
> #include <xcb/shm.h>
>
> -
> struct session_struct;
>
> /*----------------------------------------------------------------------------
> ** Structure definitions
> **--------------------------------------------------------------------------*/
> typedef struct {
> - int shmid;
> + int shmid; /* if shmid is -1: the shm_segment_t is "empty", other
> members are undefined */
> + size_t size;
> xcb_shm_seg_t shmseg;
> void *shmaddr;
> } shm_segment_t;
> @@ -63,6 +64,12 @@ typedef struct {
> shm_image_t *fullscreen;
> shm_image_t *scanline;
>
> + /* The SHM cache holds up to 10 segments, this provides a good cache
> + * hit rate while keeping memory usage reasonable.
> + */
> + shm_segment_t shm_cache[10];
> + GMutex shm_cache_mutex;
> +
> pthread_t event_thread;
> struct session_struct *session;
> } display_t;
It seems fine, last word to Jeremy
Frediano
More information about the Spice-devel
mailing list