Use GCache for a global string cache
Tyson Dowd
trd@cs.mu.OZ.AU
Sun, 5 Nov 2000 15:16:35 +1100
Hi,
I was looking around the source code and noticed this might be a nice
improvement, plus I wanted to make sure I actually understood how things
worked.
So here's a patch -- feel free to take it or leave it.
Below is the proposed CVS log message, and below that the patch.
===================================================================
Use GCache to optimize the allocation of strings a little.
A GCache is a glib data structure that will cache previous entries and
do reference counting. We use it to store strings -- each unique string
is stored only once, and is only removed when all references to it are
gone.
Previously, all transactions and splits allocated their own copy
of char * fields
memo, action, description, num
mainly so that they could reliably de-allocate them when removed.
Also, every key-value pair frame (kvp_frame) copied the strings used as
keys in the database.
In all of these cases, it's quite likely that the strings in question
will be re-used over and over again. Descriptions will be
auto-completed from previous transactions, nums will be re-used in
different accounts, etc, etc. Also, the empty string is the default
value in all the fields, and we were allocating a new empty string for
each new transaction or split.
Finally, for kvp databases, it's extremely likely the same keys (e.g.
"notes") will be used again and again. Currently this doesn't save much
because account notes seems to be the only thing kvp is used for, but it
still seems to be a good idea.
On a medium sized sample I have (1 year of transactions for a small
business), gnucash previously used 2.4 Mb to load the session.
This change shaves 100kb off that figure.
engine/Transaction.c:
Use GCache to allocate and deallocate the string fields of
transactions and splits.
engine/gnc-engine.c:
engine/gnc-engine.h:
Provide the global variable gnc_string_cache and make sure it is
initialized.
engine/kvp_frame.c:
Use gnc_string_cache to allocate keys.
(we could also allocate string values using the cache, but this
doesn't seem like a clear win -- the cache is only worthwhile if
there is a good chance of duplicates).
Index: engine/Transaction.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/Transaction.c,v
retrieving revision 1.122
diff -u -r1.122 Transaction.c
--- engine/Transaction.c 2000/11/04 01:05:32 1.122
+++ engine/Transaction.c 2000/11/05 03:35:37
@@ -42,6 +42,7 @@
#include "date.h"
#include "gnc-commodity.h"
#include "gnc-engine-util.h"
+#include "gnc-engine.h"
/*
* The "force_double_entry" flag determines how
@@ -91,8 +92,8 @@
split->acc = NULL;
split->parent = NULL;
- split->action = g_strdup("");
- split->memo = g_strdup("");
+ split->action = g_cache_insert(gnc_string_cache, "");
+ split->memo = g_cache_insert(gnc_string_cache, "");
split->reconciled = NREC;
split->damount = gnc_numeric_zero();
split->value = gnc_numeric_zero();
@@ -140,8 +141,8 @@
split->acc = s->acc;
split->parent = s->parent;
- split->action = g_strdup(s->action);
- split->memo = g_strdup(s->memo);
+ split->action = g_cache_insert(gnc_string_cache, s->action);
+ split->memo = g_cache_insert(gnc_string_cache, s->memo);
split->reconciled = s->reconciled;
split->damount = s->damount;
split->value = s->value;
@@ -173,8 +174,8 @@
{
if (!split) return;
- g_free (split->memo);
- g_free (split->action);
+ g_cache_remove(gnc_string_cache, split->memo);
+ g_cache_remove(gnc_string_cache, split->action);
/* just in case someone looks up freed memory ... */
split->memo = NULL;
@@ -207,8 +208,9 @@
if(!guid_equal(&(sa->guid), &(sb->guid))) return FALSE;
}
- if(safe_strcmp(sa->memo, sb->memo) != 0) return FALSE;
- if(safe_strcmp(sa->action, sb->action) != 0) return FALSE;
+ /* Since these strings are cached we can just use pointer equality */
+ if(sa->memo != sb->memo) return FALSE;
+ if(sa->action != sb->action) return FALSE;
if(kvp_frame_compare(sa->kvp_data, sb->kvp_data) != 0) return FALSE;
@@ -557,8 +559,8 @@
xaccInitTransaction (Transaction * trans)
{
/* Fill in some sane defaults */
- trans->num = g_strdup("");
- trans->description = g_strdup("");
+ trans->num = g_cache_insert(gnc_string_cache, "");
+ trans->description = g_cache_insert(gnc_string_cache, "");
trans->splits = NULL;
@@ -605,8 +607,8 @@
trans = g_new(Transaction, 1);
- trans->num = g_strdup(t->num);
- trans->description = g_strdup(t->description);
+ trans->num = g_cache_insert(gnc_string_cache, t->num);
+ trans->description = g_cache_insert(gnc_string_cache, t->description);
trans->splits = g_list_copy (t->splits);
for (node = trans->splits; node; node = node->next)
@@ -648,8 +650,8 @@
trans->splits = NULL;
/* free up transaction strings */
- g_free (trans->num);
- g_free (trans->description);
+ g_cache_remove(gnc_string_cache, trans->num);
+ g_cache_remove(gnc_string_cache, trans->description);
/* just in case someone looks up freed memory ... */
trans->num = NULL;
@@ -698,8 +700,11 @@
if(!timespec_equal(&(ta->date_entered), &(tb->date_entered))) return FALSE;
if(!timespec_equal(&(ta->date_posted), &(tb->date_posted))) return FALSE;
- if(safe_strcmp(ta->num, tb->num) != 0) return FALSE;
- if(safe_strcmp(ta->description, tb->description) != 0) return FALSE;
+ /* Since we use cached strings, we can just compare pointer
+ * equality for num and description
+ */
+ if(ta->num != tb->num) return FALSE;
+ if(ta->description != tb->description) return FALSE;
if(kvp_frame_compare(ta->kvp_data, tb->kvp_data) != 0) return FALSE;
@@ -1219,14 +1224,14 @@
xaccTransAppendSplit (trans, s);
xaccAccountInsertSplit (split->acc, s);
- g_free (s->memo);
- g_free (s->action);
+ g_cache_remove(gnc_string_cache, s->memo);
+ g_cache_remove(gnc_string_cache, s->action);
xaccSplitSetValue(s, gnc_numeric_neg(split->value));
xaccSplitSetShareAmount(s, gnc_numeric_neg(split->value));
- s->memo = g_strdup (split->memo);
- s->action = g_strdup (split->action);
+ s->memo = g_cache_insert(gnc_string_cache, split->memo);
+ s->action = g_cache_insert(gnc_string_cache, split->action);
}
}
}
@@ -1425,9 +1430,11 @@
#define PUT_BACK(val) { g_free(trans->val); \
trans->val=orig->val; orig->val=NULL; }
+#define PUT_BACK_CACHE(val) { g_cache_remove(gnc_string_cache, trans->val); \
+ trans->val=orig->val; orig->val=NULL; }
PUT_BACK (num);
- PUT_BACK (description);
+ PUT_BACK_CACHE (description);
trans->date_entered.tv_sec = orig->date_entered.tv_sec;
trans->date_entered.tv_nsec = orig->date_entered.tv_nsec;
@@ -1903,8 +1910,8 @@
if (!trans || !xnum) return;
CHECK_OPEN (trans);
- tmp = g_strdup (xnum);
- g_free (trans->num);
+ tmp = g_cache_insert(gnc_string_cache, xnum);
+ g_cache_remove(gnc_string_cache, trans->num);
trans->num = tmp;
MarkChanged (trans);
}
@@ -1916,8 +1923,8 @@
if (!trans || !desc) return;
CHECK_OPEN (trans);
- tmp = g_strdup (desc);
- g_free (trans->description);
+ tmp = g_cache_insert(gnc_string_cache, desc);
+ g_cache_remove(gnc_string_cache, trans->description);
trans->description = tmp;
MarkChanged (trans);
}
@@ -1991,8 +1998,9 @@
{
char * tmp;
if (!split || !memo) return;
- tmp = g_strdup (memo);
- g_free (split->memo);
+
+ tmp = g_cache_insert(gnc_string_cache, memo);
+ g_cache_remove(gnc_string_cache, split->memo);
split->memo = tmp;
MARK_SPLIT (split);
}
@@ -2002,8 +2010,9 @@
{
char * tmp;
if (!split || !actn) return;
- tmp = g_strdup (actn);
- g_free (split->action);
+
+ tmp = g_cache_insert(gnc_string_cache, actn);
+ g_cache_remove(gnc_string_cache, split->action);
split->action = tmp;
MARK_SPLIT (split);
}
Index: engine/gnc-engine.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-engine.c,v
retrieving revision 1.2
diff -u -r1.2 gnc-engine.c
--- engine/gnc-engine.c 2000/10/23 09:40:51 1.2
+++ engine/gnc-engine.c 2000/11/05 03:35:38
@@ -29,8 +29,8 @@
static GList * engine_init_hooks = NULL;
static gnc_commodity_table * known_commodities = NULL;
static int engine_is_initialized = 0;
+GCache * gnc_string_cache = NULL;
-
/********************************************************************
* gnc_engine_init
* initialize backend, load any necessary databases, etc.
@@ -42,6 +42,11 @@
GList * cur;
engine_is_initialized = 1;
+
+ /* initialize the string cache */
+ gnc_string_cache = g_cache_new( (GCacheNewFunc) g_strdup,
+ g_free, (GCacheDupFunc) g_strdup, g_free, g_str_hash,
+ g_str_hash, g_str_equal);
/* initialize the commodity table (it starts empty) */
known_commodities = gnc_commodity_table_new();
Index: engine/gnc-engine.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-engine.h,v
retrieving revision 1.2
diff -u -r1.2 gnc-engine.h
--- engine/gnc-engine.h 2000/10/23 09:40:51 1.2
+++ engine/gnc-engine.h 2000/11/05 03:35:38
@@ -41,5 +41,24 @@
/* this is a global table of known commodity types. */
gnc_commodity_table * gnc_engine_commodities(void);
+/* Many strings used throughout the engine are likely to be duplicated.
+ * So we provide a reference counted cache system for the strings, which
+ * shares strings whenever possible.
+ *
+ * Use g_cache_insert to insert a string into the cache (it will return a
+ * pointer to the cached string).
+ * Basically you should use this instead of g_strdup.
+ *
+ * Use g_cache_remove (giving it a pointer to a cached string) if the string
+ * is unused. If this is the last reference to the string it will be
+ * removed from the cache, otherwise it will just decrement the
+ * reference count.
+ * Basically you should use this instead of g_free.
+ *
+ * Note that all the work is done when inserting or removing. Once
+ * cached the strings are just plain C strings.
+ */
+extern GCache *gnc_string_cache;
+
#endif
Index: engine/kvp_frame.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/kvp_frame.c,v
retrieving revision 1.6
diff -u -r1.6 kvp_frame.c
--- engine/kvp_frame.c 2000/10/23 09:40:54 1.6
+++ engine/kvp_frame.c 2000/11/05 03:35:39
@@ -23,11 +23,15 @@
#include "kvp_frame.h"
#include "guid.h"
+#include "gnc-engine.h"
#include <string.h>
#include <stdio.h>
#include <glib.h>
+ /* Note that we keep the keys for this hash table in a GCache
+ * (gnc_string_cache), as it is likely we will see the same keys
+ * over and over again */
struct _kvp_frame {
GHashTable * hash;
};
@@ -84,7 +88,7 @@
static void
kvp_frame_delete_worker(gpointer key, gpointer value, gpointer user_data) {
- g_free(key);
+ g_cache_remove(gnc_string_cache, key);
kvp_value_delete((kvp_value *)value);
}
@@ -107,7 +111,7 @@
kvp_frame * dest = (kvp_frame *)user_data;
g_hash_table_freeze(dest->hash);
g_hash_table_insert(dest->hash,
- (gpointer)g_strdup(key),
+ (gpointer)g_cache_insert(gnc_string_cache, key),
(gpointer)kvp_value_copy(value));
g_hash_table_thaw(dest->hash);
}
@@ -142,11 +146,14 @@
& orig_key, & orig_value);
if(key_exists) {
g_hash_table_remove(frame->hash, slot);
- g_free(orig_key);
+ g_cache_remove(gnc_string_cache, orig_key);
kvp_value_delete(orig_value);
}
- if(new_value) g_hash_table_insert(frame->hash, g_strdup(slot), new_value);
+ if(new_value) {
+ g_hash_table_insert(frame->hash, g_cache_insert(gnc_string_cache, slot),
+ new_value);
+ }
g_hash_table_thaw(frame->hash);
}
--
Tyson Dowd #
# Surreal humour isn't everyone's cup of fur.
trd@cs.mu.oz.au #
http://www.cs.mu.oz.au/~trd #