From cb18fe860305048012d840512914a12cda9f152c Mon Sep 17 00:00:00 2001 From: Marwan Tanager Date: Tue, 26 Mar 2013 05:50:22 +0200 Subject: [PATCH] Replace the periodic page reclaiming code with a LRU caching algorithm. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch implements a page cache that is invalidated in a LRU fashion. Pages are added to the cache as soon as they become visible. When the cache is full and a new page that isn't in the cache becomes visible, the least recently viewed page in the cache is evicted from memory and the new one takes it's place. The cache size is configurable using the page-cache-size configuration variable, with a default value of 15 pages. Very large values for the cache size are not recommended, though, as it will stress the system memory out. The old periodic page reclaiming code is no longer necessary with this patch, so I removed it. Special thanks to Ignas Anikevičius, and Sebastian Ramacher for the inspirations. Signed-off-by: Sebastian Ramacher --- callbacks.c | 3 +- config.c | 5 +- page-widget.c | 16 ----- page-widget.h | 8 --- render.c | 4 +- zathura.c | 161 +++++++++++++++++++++++++++++++++++++++--------- zathura.h | 18 ++++++ zathurarc.5.rst | 20 +++--- 8 files changed, 163 insertions(+), 72 deletions(-) diff --git a/callbacks.c b/callbacks.c index 2c63945..b4242d6 100644 --- a/callbacks.c +++ b/callbacks.c @@ -95,6 +95,8 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi if (gdk_rectangle_intersect(&view_rect, &page_rect, NULL) == TRUE) { zathura_page_set_visibility(page, true); + zathura_page_widget_update_view_time(ZATHURA_PAGE(page_widget)); + zathura_page_cache_add(zathura, zathura_page_get_index(page)); if (zathura->global.update_page_number == true && updated == false && gdk_rectangle_intersect(¢er, &page_rect, NULL) == TRUE) { zathura_document_set_current_page_number(zathura->document, page_id); @@ -103,7 +105,6 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi } else { zathura_page_set_visibility(page, false); } - zathura_page_widget_update_view_time(ZATHURA_PAGE(page_widget)); } statusbar_page_number_update(zathura); diff --git a/config.c b/config.c index 2dd64df..3f49034 100644 --- a/config.c +++ b/config.c @@ -155,9 +155,8 @@ config_load_default(zathura_t* zathura) girara_setting_add(gsession, "zoom-min", &int_value, INT, false, _("Zoom minimum"), NULL, NULL); int_value = 1000; girara_setting_add(gsession, "zoom-max", &int_value, INT, false, _("Zoom maximum"), NULL, NULL); - int_value = 20; - girara_setting_add(gsession, "page-store-threshold", &int_value, INT, false, _("Life time (in seconds) of a hidden page"), NULL, NULL); - girara_setting_add(gsession, "page-store-interval", &int_value, INT, true, _("Amount of seconds between each cache purge"), NULL, NULL); + int_value = ZATHURA_PAGE_CACHE_DEFAULT_SIZE; + girara_setting_add(gsession, "page-cache-size", &int_value, INT, true, _("Maximum number of pages to keep in the cache"), NULL, NULL); int_value = 20; girara_setting_add(gsession, "jumplist-size", &int_value, INT, false, _("Number of positions to remember in the jumplist"), cb_jumplist_change, NULL); diff --git a/page-widget.c b/page-widget.c index 8000ccb..e2b1542 100644 --- a/page-widget.c +++ b/page-widget.c @@ -860,19 +860,3 @@ zathura_page_widget_update_view_time(ZathuraPage* widget) priv->last_view = g_get_real_time(); } } - -void -zathura_page_widget_purge_unused(ZathuraPage* widget, gint64 threshold) -{ - g_return_if_fail(ZATHURA_IS_PAGE(widget) == TRUE); - zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); - if (zathura_page_get_visibility(priv->page) == true || priv->surface == NULL || threshold <= 0) { - return; - } - - const gint64 now = g_get_real_time(); - if (now - priv->last_view >= threshold * G_USEC_PER_SEC) { - girara_debug("purge page %d from cache (unseen for %f seconds)", zathura_page_get_index(priv->page), ((double)now - priv->last_view) / G_USEC_PER_SEC); - zathura_page_widget_update_surface(widget, NULL); - } -} diff --git a/page-widget.h b/page-widget.h index fe0b67a..5609e78 100644 --- a/page-widget.h +++ b/page-widget.h @@ -88,12 +88,4 @@ zathura_link_t* zathura_page_widget_link_get(ZathuraPage* widget, unsigned int i */ void zathura_page_widget_update_view_time(ZathuraPage* widget); -/** - * If the page has not been viewed for some time, purge the surface. - * - * @param widget the widget - * @param threshold the threshold (in seconds) - */ -void zathura_page_widget_purge_unused(ZathuraPage* widget, gint64 threshold); - #endif diff --git a/render.c b/render.c index 05b12eb..faf5f79 100644 --- a/render.c +++ b/render.c @@ -33,9 +33,9 @@ render_job(void* data, void* user_data) return; } - girara_debug("rendering page %d ...", zathura_page_get_index(page)); + girara_debug("rendering page %d ...", zathura_page_get_index(page) + 1); if (render(zathura, page) != true) { - girara_error("Rendering failed (page %d)\n", zathura_page_get_index(page)); + girara_error("Rendering failed (page %d)\n", zathura_page_get_index(page) + 1); } } diff --git a/zathura.c b/zathura.c index 8e8c4da..5a97755 100644 --- a/zathura.c +++ b/zathura.c @@ -53,7 +53,10 @@ typedef struct position_set_delayed_s { } position_set_delayed_t; static gboolean document_info_open(gpointer data); -static gboolean purge_pages(gpointer data); +static bool zathura_page_cache_is_cached(zathura_t* zathura, unsigned int page_index); +static ssize_t zathura_page_cache_lru_invalidate(zathura_t* zathura); +static void zathura_page_cache_invalidate_all(zathura_t* zathura); +static bool zathura_page_cache_is_full(zathura_t* zathura, bool* result); /* function implementation */ zathura_t* @@ -264,11 +267,6 @@ zathura_init(zathura_t* zathura) zathura->bookmarks.bookmarks = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare, (girara_free_function_t) zathura_bookmark_free); - /* add even to purge old pages */ - int interval = 30; - girara_setting_get(zathura->ui.session, "page-store-interval", &interval); - g_timeout_add_seconds(interval, purge_pages, zathura); - /* jumplist */ zathura->jumplist.max_size = 20; @@ -279,6 +277,14 @@ zathura_init(zathura_t* zathura) zathura->jumplist.cur = NULL; zathura_jumplist_append_jump(zathura); zathura->jumplist.cur = girara_list_iterator(zathura->jumplist.list); + + /* page cache */ + + zathura->page_cache.size = ZATHURA_PAGE_CACHE_DEFAULT_SIZE; + girara_setting_get(zathura->ui.session, "page-cache-size", &zathura->page_cache.size); + zathura->page_cache.cache = g_malloc(zathura->page_cache.size * sizeof(int)); + zathura_page_cache_invalidate_all(zathura); + return true; error_free: @@ -353,6 +359,8 @@ zathura_free(zathura_t* zathura) girara_list_iterator_free(zathura->jumplist.cur); } + g_free(zathura->page_cache.cache); + g_free(zathura); } @@ -775,6 +783,9 @@ document_open(zathura_t* zathura, const char* path, const char* password, cb_view_vadjustment_value_changed(NULL, zathura); } + /* Invalidate all current entries in the page cache */ + zathura_page_cache_invalidate_all(zathura); + return true; error_free: @@ -1066,30 +1077,6 @@ page_widget_set_mode(zathura_t* zathura, unsigned int pages_per_row, unsigned in gtk_widget_show_all(zathura->ui.page_widget); } -static -gboolean purge_pages(gpointer data) -{ - zathura_t* zathura = data; - if (zathura == NULL || zathura->document == NULL) { - return TRUE; - } - - int threshold = 0; - girara_setting_get(zathura->ui.session, "page-store-threshold", &threshold); - if (threshold <= 0) { - return TRUE; - } - - girara_debug("purging pages ..."); - unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document); - for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) { - zathura_page_t* page = zathura_document_get_page(zathura->document, page_id); - GtkWidget* page_widget = zathura_page_get_widget(zathura, page); - zathura_page_widget_purge_unused(ZATHURA_PAGE(page_widget), threshold); - } - return TRUE; -} - static gboolean position_set_delayed_impl(gpointer data) { @@ -1215,3 +1202,117 @@ zathura_jumplist_save(zathura_t* zathura) cur->y = gtk_adjustment_get_value(view_vadjustment) / zathura_document_get_scale(zathura->document);; } } + +static bool +zathura_page_cache_is_cached(zathura_t* zathura, unsigned int page_index) +{ + g_return_val_if_fail(zathura != NULL, false); + + unsigned int i; + + if (zathura->page_cache.num_cached_pages != 0) { + for (i = 0; i < zathura->page_cache.size; ++i) { + if (zathura->page_cache.cache[i] >= 0 && page_index == (unsigned int)zathura->page_cache.cache[i]) { + girara_debug("Page %d is a cache hit", page_index + 1); + return true; + } + } + } + + girara_debug("Page %d is a cache miss", page_index + 1); + return false; +} + +static ssize_t +zathura_page_cache_lru_invalidate(zathura_t* zathura) +{ + g_return_val_if_fail(zathura != NULL, -1); + + ssize_t lru_index = 0; + guint64 view_time = 0; + guint64 lru_view_time = G_MAXUINT64; + GtkWidget* page_widget; + + for (unsigned int i = 0; i < zathura->page_cache.size; ++i) { + page_widget = zathura_page_get_widget(zathura, zathura_document_get_page(zathura->document, zathura->page_cache.cache[i])); + g_return_val_if_fail(page_widget != NULL, -1); + g_object_get(G_OBJECT(page_widget), "last-view", &view_time, NULL); + + if (view_time < lru_view_time) { + lru_view_time = view_time; + lru_index = i; + } + } + + zathura_page_t* page = zathura_document_get_page(zathura->document, zathura->page_cache.cache[lru_index]); + g_return_val_if_fail(page != NULL, -1); + + page_widget = zathura_page_get_widget(zathura, page); + g_return_val_if_fail(page_widget != NULL, -1); + + zathura_page_widget_update_surface(ZATHURA_PAGE(page_widget), NULL); + girara_debug("Invalidated page %d at cache index %ld", zathura->page_cache.cache[lru_index] + 1, lru_index); + zathura->page_cache.cache[lru_index] = -1; + --zathura->page_cache.num_cached_pages; + + return lru_index; +} + +static bool +zathura_page_cache_is_full(zathura_t* zathura, bool* result) +{ + g_return_val_if_fail(zathura != NULL, false); + + *result = zathura->page_cache.num_cached_pages == zathura->page_cache.size; + + return true; +} + +void +zathura_page_cache_invalidate_all(zathura_t* zathura) +{ + g_return_if_fail(zathura != NULL); + + unsigned int i; + + for (i = 0; i < zathura->page_cache.size; ++i) { + zathura->page_cache.cache[i] = -1; + } + + zathura->page_cache.num_cached_pages = 0; +} + +void +zathura_page_cache_add(zathura_t* zathura, unsigned int page_index) +{ + g_return_if_fail(zathura != NULL); + + zathura_page_t* page = zathura_document_get_page(zathura->document, page_index); + + g_return_if_fail(page != NULL); + + if (zathura_page_cache_is_cached(zathura, page_index)) { + return; + } + + bool full; + + if (zathura_page_cache_is_full(zathura, &full) == false) { + return; + } else if (full == true) { + ssize_t idx = zathura_page_cache_lru_invalidate(zathura); + + if (idx == -1) { + return; + } + + zathura->page_cache.cache[idx] = page_index; + ++zathura->page_cache.num_cached_pages; + girara_debug("Page %d is cached at cache index %ld", page_index + 1, idx); + return; + } + + zathura->page_cache.cache[zathura->page_cache.num_cached_pages++] = page_index; + girara_debug("Page %d is cached at cache index %d", page_index + 1, zathura->page_cache.num_cached_pages - 1); + return; +} diff --git a/zathura.h b/zathura.h index 1520d78..e75a7e2 100644 --- a/zathura.h +++ b/zathura.h @@ -13,6 +13,8 @@ #include #endif +#define ZATHURA_PAGE_CACHE_DEFAULT_SIZE 15 + enum { NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, HIGHLIGHT, DELETE_LAST_WORD, DELETE_LAST_CHAR, DEFAULT, ERROR, WARNING, NEXT_GROUP, PREVIOUS_GROUP, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, ZOOM_SPECIFIC, FORWARD, @@ -152,6 +154,15 @@ struct zathura_s gchar* file_path; /**< Save file path */ gchar* password; /**< Save password */ } file_monitor; + + /** + * The page cache + */ + struct { + int* cache; + unsigned int size; + unsigned int num_cached_pages; + } page_cache; }; /** @@ -366,5 +377,12 @@ void zathura_jumplist_add(zathura_t* zathura); */ void zathura_jumplist_append_jump(zathura_t* zathura); +/** + * Add a page to the page cache + * + * @param zathura The zathura session + * @param page_index The index of the page to be cached + */ +void zathura_page_cache_add(zathura_t* zathura, unsigned int page_index); #endif // ZATHURA_H diff --git a/zathurarc.5.rst b/zathurarc.5.rst index f317aa6..09854b6 100644 --- a/zathurarc.5.rst +++ b/zathurarc.5.rst @@ -530,20 +530,16 @@ The page padding defines the gap in pixels between each rendered page. * Value type: Integer * Default value: 1 -page-store-threshold -^^^^^^^^^^^^^^^^^^^^ -Pages that are not visible get unloaded after some time. Every page that has not -been visible for page-store-treshold seconds will be unloaded. +page-cache-size +^^^^^^^^^^^^^^^ +Defines the maximum number of pages that could be kept in the page cache. When +the cache is full and a new page that isn't cached becomes visible, the least +recently viewed page in the cache will be evicted to make room for the new one. +Large values for this variable are NOT recommended, because this will lead to +consuming a significant portion of the system memory. * Value type: Integer -* Default value: 30 - -page-store-interval -^^^^^^^^^^^^^^^^^^^ -Defines the amount of seconds between the check to unload invisible pages. - -* Value type: Integer -* Default value: 30 +* Default value: 15 pages-per-row ^^^^^^^^^^^^^