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

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
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(&center, &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);
......
......@@ -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);
......
......@@ -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);
}
}
......@@ -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
......@@ -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);
}
}
......
......@@ -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;
}
......@@ -13,6 +13,8 @@
#include <gtk/gtkx.h>
#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
......@@ -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.
* Value type: Integer
* Default value: 30
page-store-interval
^^^^^^^^^^^^^^^^^^^
Defines the amount of seconds between the check to unload invisible pages.
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
* Default value: 15
pages-per-row
^^^^^^^^^^^^^
......
Markdown is supported
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