Commit cb18fe86 authored by Marwan Tanager's avatar Marwan Tanager Committed by Sebastian Ramacher
Browse files

Replace the periodic page reclaiming code with a LRU caching algorithm.



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's avatarSebastian Ramacher <sebastian+dev@ramacher.at>
parent cd3314b4
...@@ -95,6 +95,8 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi ...@@ -95,6 +95,8 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi
if (gdk_rectangle_intersect(&view_rect, &page_rect, NULL) == TRUE) { if (gdk_rectangle_intersect(&view_rect, &page_rect, NULL) == TRUE) {
zathura_page_set_visibility(page, 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 if (zathura->global.update_page_number == true && updated == false
&& gdk_rectangle_intersect(&center, &page_rect, NULL) == TRUE) { && gdk_rectangle_intersect(&center, &page_rect, NULL) == TRUE) {
zathura_document_set_current_page_number(zathura->document, page_id); zathura_document_set_current_page_number(zathura->document, page_id);
...@@ -103,7 +105,6 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi ...@@ -103,7 +105,6 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi
} else { } else {
zathura_page_set_visibility(page, false); zathura_page_set_visibility(page, false);
} }
zathura_page_widget_update_view_time(ZATHURA_PAGE(page_widget));
} }
statusbar_page_number_update(zathura); statusbar_page_number_update(zathura);
......
...@@ -155,9 +155,8 @@ config_load_default(zathura_t* zathura) ...@@ -155,9 +155,8 @@ config_load_default(zathura_t* zathura)
girara_setting_add(gsession, "zoom-min", &int_value, INT, false, _("Zoom minimum"), NULL, NULL); girara_setting_add(gsession, "zoom-min", &int_value, INT, false, _("Zoom minimum"), NULL, NULL);
int_value = 1000; int_value = 1000;
girara_setting_add(gsession, "zoom-max", &int_value, INT, false, _("Zoom maximum"), NULL, NULL); girara_setting_add(gsession, "zoom-max", &int_value, INT, false, _("Zoom maximum"), NULL, NULL);
int_value = 20; int_value = ZATHURA_PAGE_CACHE_DEFAULT_SIZE;
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-cache-size", &int_value, INT, true, _("Maximum number of pages to keep in the cache"), NULL, NULL);
girara_setting_add(gsession, "page-store-interval", &int_value, INT, true, _("Amount of seconds between each cache purge"), NULL, NULL);
int_value = 20; 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); girara_setting_add(gsession, "jumplist-size", &int_value, INT, false, _("Number of positions to remember in the jumplist"), cb_jumplist_change, NULL);
......
...@@ -860,19 +860,3 @@ zathura_page_widget_update_view_time(ZathuraPage* widget) ...@@ -860,19 +860,3 @@ zathura_page_widget_update_view_time(ZathuraPage* widget)
priv->last_view = g_get_real_time(); 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);
}
}
...@@ -88,12 +88,4 @@ zathura_link_t* zathura_page_widget_link_get(ZathuraPage* widget, unsigned int i ...@@ -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); 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 #endif
...@@ -33,9 +33,9 @@ render_job(void* data, void* user_data) ...@@ -33,9 +33,9 @@ render_job(void* data, void* user_data)
return; 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) { 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);
} }
} }
......
...@@ -53,7 +53,10 @@ typedef struct position_set_delayed_s { ...@@ -53,7 +53,10 @@ typedef struct position_set_delayed_s {
} position_set_delayed_t; } position_set_delayed_t;
static gboolean document_info_open(gpointer data); 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 */ /* function implementation */
zathura_t* zathura_t*
...@@ -264,11 +267,6 @@ zathura_init(zathura_t* zathura) ...@@ -264,11 +267,6 @@ zathura_init(zathura_t* zathura)
zathura->bookmarks.bookmarks = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare, zathura->bookmarks.bookmarks = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare,
(girara_free_function_t) zathura_bookmark_free); (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 */ /* jumplist */
zathura->jumplist.max_size = 20; zathura->jumplist.max_size = 20;
...@@ -279,6 +277,14 @@ zathura_init(zathura_t* zathura) ...@@ -279,6 +277,14 @@ zathura_init(zathura_t* zathura)
zathura->jumplist.cur = NULL; zathura->jumplist.cur = NULL;
zathura_jumplist_append_jump(zathura); zathura_jumplist_append_jump(zathura);
zathura->jumplist.cur = girara_list_iterator(zathura->jumplist.list); 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; return true;
error_free: error_free:
...@@ -353,6 +359,8 @@ zathura_free(zathura_t* zathura) ...@@ -353,6 +359,8 @@ zathura_free(zathura_t* zathura)
girara_list_iterator_free(zathura->jumplist.cur); girara_list_iterator_free(zathura->jumplist.cur);
} }
g_free(zathura->page_cache.cache);
g_free(zathura); g_free(zathura);
} }
...@@ -775,6 +783,9 @@ document_open(zathura_t* zathura, const char* path, const char* password, ...@@ -775,6 +783,9 @@ document_open(zathura_t* zathura, const char* path, const char* password,
cb_view_vadjustment_value_changed(NULL, zathura); cb_view_vadjustment_value_changed(NULL, zathura);
} }
/* Invalidate all current entries in the page cache */
zathura_page_cache_invalidate_all(zathura);
return true; return true;
error_free: error_free:
...@@ -1066,30 +1077,6 @@ page_widget_set_mode(zathura_t* zathura, unsigned int pages_per_row, unsigned in ...@@ -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); 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 static gboolean
position_set_delayed_impl(gpointer data) position_set_delayed_impl(gpointer data)
{ {
...@@ -1215,3 +1202,117 @@ zathura_jumplist_save(zathura_t* zathura) ...@@ -1215,3 +1202,117 @@ zathura_jumplist_save(zathura_t* zathura)
cur->y = gtk_adjustment_get_value(view_vadjustment) / zathura_document_get_scale(zathura->document);; 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;
}
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
#include <gtk/gtkx.h> #include <gtk/gtkx.h>
#endif #endif
#define ZATHURA_PAGE_CACHE_DEFAULT_SIZE 15
enum { NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, HIGHLIGHT, enum { NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, HIGHLIGHT,
DELETE_LAST_WORD, DELETE_LAST_CHAR, DEFAULT, ERROR, WARNING, NEXT_GROUP, DELETE_LAST_WORD, DELETE_LAST_CHAR, DEFAULT, ERROR, WARNING, NEXT_GROUP,
PREVIOUS_GROUP, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, ZOOM_SPECIFIC, FORWARD, PREVIOUS_GROUP, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, ZOOM_SPECIFIC, FORWARD,
...@@ -152,6 +154,15 @@ struct zathura_s ...@@ -152,6 +154,15 @@ struct zathura_s
gchar* file_path; /**< Save file path */ gchar* file_path; /**< Save file path */
gchar* password; /**< Save password */ gchar* password; /**< Save password */
} file_monitor; } 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); ...@@ -366,5 +377,12 @@ void zathura_jumplist_add(zathura_t* zathura);
*/ */
void zathura_jumplist_append_jump(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 #endif // ZATHURA_H
...@@ -530,20 +530,16 @@ The page padding defines the gap in pixels between each rendered page. ...@@ -530,20 +530,16 @@ The page padding defines the gap in pixels between each rendered page.
* Value type: Integer * Value type: Integer
* Default value: 1 * Default value: 1
page-store-threshold page-cache-size
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
Pages that are not visible get unloaded after some time. Every page that has not Defines the maximum number of pages that could be kept in the page cache. When
been visible for page-store-treshold seconds will be unloaded. 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.
* Value type: Integer Large values for this variable are NOT recommended, because this will lead to
* Default value: 30 consuming a significant portion of the system memory.
page-store-interval
^^^^^^^^^^^^^^^^^^^
Defines the amount of seconds between the check to unload invisible pages.
* Value type: Integer * Value type: Integer
* Default value: 30 * Default value: 15
pages-per-row pages-per-row
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment