yelp:cache implementation



This is the first rough pass at an implementation of a yelp:cache extension element.

Shaun and I discussed this on IRC and basically the premise is that if a template depends solely on a xsl:param element that is passed to it, then we can cache the results of that template and if it is called again with the same parameter, then we look up the result in a hash table instead of performing all the XSLT again.

A prime example of this is the db.number template.

<xsl:template name="db.number">
  <xsl:param name="node" select="."/>
    <xsl:apply-templates mode="db.number.mode" select="$node"/>
</xsl:template>

This template is responsible for calculating the numbers for a particular docbook section, and takes up quite a bit of processing time for docbook files with many sections and deep nesting. Since the same
node is passed to the function often, a considerable amount of time is
wasted recalculating something we've already done before.

In comes the <yelp:cache> extension element. Yelp's db2html.xsl template now has the following (overriding the original version of this template in db-label.xsl)

<xsl:template name="db.number">
  <xsl:param name="node" select="."/>
  <yelp:cache key="db.number" node="$node">
    <xsl:apply-templates mode="db.number.mode" select="$node"/>
  </yelp:cache>
</xsl:template>

You can see that two attributes are required for the <yelp:cache> element. The key is unique value per template and is used in generating the key for the hash table lookup. The other required attribute is node, which right now must be a nodeset with a single node. The xmlNodePtr to this single node makes up the other part of the hash key.

On the first call to the db.number template with a particular node, the extension element function will not find a value in the hash table. Therefore, it will apply the child elements of the yelp:cache element, and put the result in the hash table. On the second call with the same node, the extension element function finds the value in the hash table, and instantiates it in the result tree.

So enough blabbing, here are the results before and after for the Gnumeric manual, which takes the longest time to process (by far).

BEFORE:

smitten home:/extra/cvs/gnome2/yelp-head2$ YELP_DEBUG="enable-profiling" /opt/gnome2/bin/yelp
PROFILE [20:31:59]: entering xslt_pager_process
PROFILE [20:32:52]: leaving xslt_pager_process


AFTER:

smitten home:/extra/cvs/gnome2/yelp-head2$ YELP_DEBUG="enable-profiling" /opt/gnome2/bin/yelp
PROFILE [20:30:10]: entering xslt_pager_process
PROFILE [20:30:35]: leaving xslt_pager_process

Pretty dramatic!!

Shaun had some concerns about localization, but I don't think this should affect it since the extension element function caches the actual result of the db.number.mode template. That is unless the result of db.number.mode is dependent on the depth of numbering as well as the $node parameter...

Let me know your thoughts!

--
Brent Smith <gnome nextreality net>
IRC: smitten
? data/info.xml
? m4/intltool.m4
? po/stamp-it
? src/client-bindings.h
? src/server-bindings.h
? src/stylesheet
Index: src/yelp-debug.c
===================================================================
RCS file: /cvs/gnome/yelp/src/yelp-debug.c,v
retrieving revision 1.2
diff -u -p -r1.2 yelp-debug.c
--- src/yelp-debug.c	12 Jun 2006 04:39:59 -0000	1.2
+++ src/yelp-debug.c	30 Jun 2006 02:12:19 -0000
@@ -23,6 +23,7 @@
 #include <glib.h>
 #include <glib/gprintf.h>
 #include <unistd.h>
+#include <time.h>
 
 #include "yelp-debug.h"
 
@@ -160,7 +161,16 @@ void yelp_debug (const gchar *file,     
 	}
 
 	if (flags & DB_PROFILE) {
+		time_t t;
+		struct tm *tmp;
+		gchar timestamp[20];
+
+		t = time (NULL);
+		tmp = localtime(&t);
+
+		strftime (timestamp, 20, "%T", tmp);
 		formatted = g_strdup_vprintf (format, args);
+		g_fprintf (stdout, "PROFILE [%s]: %s\n", timestamp, formatted);
 		str = g_strdup_printf ("MARK: %s: %s", g_get_prgname(), formatted);
 		access (str, F_OK);
 		g_free (formatted);
Index: src/yelp-xslt-pager.c
===================================================================
RCS file: /cvs/gnome/yelp/src/yelp-xslt-pager.c,v
retrieving revision 1.17
diff -u -p -r1.17 yelp-xslt-pager.c
--- src/yelp-xslt-pager.c	12 Jun 2006 04:39:59 -0000	1.17
+++ src/yelp-xslt-pager.c	30 Jun 2006 02:12:20 -0000
@@ -162,6 +162,7 @@ xslt_pager_process (YelpPager *pager)
     GError *error = NULL;
 
     debug_print (DB_FUNCTION, "entering\n");
+    debug_print (DB_PROFILE, "entering %s", __FUNCTION__);
 
     g_return_val_if_fail (pager != NULL, FALSE);
     g_return_val_if_fail (YELP_IS_XSLT_PAGER (pager), FALSE);
@@ -276,6 +277,8 @@ xslt_pager_process (YelpPager *pager)
     }
 
     g_object_unref (pager);
+    
+    debug_print (DB_PROFILE, "leaving %s", __FUNCTION__);
 
     return FALSE;
 }
@@ -459,9 +462,113 @@ xslt_yelp_cache (xsltTransformContextPtr
 		 xmlNodePtr              inst,
 		 xsltStylePreCompPtr     comp)
 {
+    static GHashTable *keyhash; /* hash table of hash tables :-) */
+    xmlXPathObjectPtr nodeexpr;
+    xsltStylesheetPtr style = NULL;
+    xmlNodePtr  nodeptr;
+    xmlChar    *keyprop;
+    xmlChar    *nodeprop;
+    const char *old_outfile;
+    xmlDocPtr   old_output;
+    xmlNodePtr  old_insert;
+    xmlDocPtr   new_doc = NULL;
+    xmlChar    *page_buf;
+    gint        buf_size;
+    gchar      *key;
+    xmlNodePtr  tmpnode;
+    xmlNodePtr  tmpnode2;
+    
+    if (!ctxt || !node || !inst || !comp)
+	return;
+
+    keyprop = xmlGetProp (inst, BAD_CAST "key");
+    if (!keyprop)
+	return;
+    
+    nodeprop = xmlGetProp (inst, BAD_CAST "node");
+    if (!nodeprop) {
+	xmlFree (keyprop);
+        return;
+    }
+
+    nodeexpr = xmlXPathEvalExpression (nodeprop, ctxt->xpathCtxt);
+    if (!nodeexpr)
+	goto done;
+    
+    /* if we haven't initialize the hash yet, do so */
+    if (!keyhash)
+	keyhash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, xmlFree);
+
+    if (nodeexpr->type != XPATH_NODESET) {
+	debug_print (DB_WARN, "node attribute did not evaluate to a nodeset\n");
+	goto done;
+    }
+
+    nodeptr = nodeexpr->nodesetval->nodeTab[0];
+    /*g_print ("key=%s node=%s type=%d ptr=%p\n", (gchar *)keyprop, (gchar *)nodeprop, nodeexpr->type, nodeptr);*/
+
+    key = g_strdup_printf ("%s%p", keyprop, nodeptr);
+    tmpnode = g_hash_table_lookup (keyhash, key);
+    
+    if (tmpnode) {
+/*	g_print ("found cached result\n");*/
+        tmpnode2 = xmlDocCopyNode (tmpnode, tmpnode->doc, 1); 
+	xmlAddChild (ctxt->insert, tmpnode2);
+	g_free (key);
+	goto done;
+    }
+
+    old_outfile = ctxt->outputFile;
+    old_output  = ctxt->output;
+    old_insert  = ctxt->insert;
+    ctxt->outputFile = "test";
+    
+    style = xsltNewStylesheet ();
+    if (style == NULL) {
+	xsltTransformError (ctxt, NULL, inst, _("Out of memory"));
+    }
+
+    style->omitXmlDeclaration = TRUE;
+
+    new_doc = xmlNewDoc (BAD_CAST "1.0");
+    new_doc->charset = XML_CHAR_ENCODING_UTF8;
+    new_doc->dict = ctxt->dict;
+    xmlDictReference (new_doc->dict);
+
+    ctxt->output = new_doc;
+    ctxt->insert = (xmlNodePtr) new_doc;
+
     xsltApplyOneTemplate (ctxt, node, inst->children, NULL, NULL);
+    
+    /*xsltSaveResultToString (&page_buf, &buf_size, new_doc, style);
+
+    g_print ("page_buf = %s\n", page_buf);*/
+
+    ctxt->outputFile = old_outfile;
+    ctxt->output     = old_output;
+    ctxt->insert     = old_insert;
+
+    /*for (tmpnode = new_doc->children; tmpnode != NULL; tmpnode = tmpnode->next) {
+	g_print ("type=%d name=%s\n", tmpnode->type, tmpnode->name);
+    }*/
+    
+    tmpnode = xmlCopyNode (new_doc->children, 1); 
+    g_hash_table_insert (keyhash, key, tmpnode);
+
+    tmpnode2 = xmlDocCopyNode (tmpnode, tmpnode->doc, 1); 
+    xmlAddChild (ctxt->insert, tmpnode2);
+
+    /*g_print ("cached result\n");*/
 
     while (gtk_events_pending ())
 	gtk_main_iteration ();
     /* FIXME : check for cancel */
+
+done:
+    xmlFree (keyprop);
+    xmlFree (nodeprop);
+    if (new_doc)
+	xmlFreeDoc (new_doc);
+    if (style)
+	xsltFreeStylesheet (style);
 }
Index: stylesheets/db2html.xsl.in
===================================================================
RCS file: /cvs/gnome/yelp/stylesheets/db2html.xsl.in,v
retrieving revision 1.19
diff -u -p -r1.19 db2html.xsl.in
--- stylesheets/db2html.xsl.in	19 May 2005 22:41:16 -0000	1.19
+++ stylesheets/db2html.xsl.in	30 Jun 2006 02:12:20 -0000
@@ -58,6 +58,13 @@
 <xsl:param name="db.chunk.index_basename" select="'__yelp_index'"/>
 <xsl:param name="db.chunk.toc_basename"   select="'__yelp_toc'"/>
 
+<xsl:template name="db.number">
+  <xsl:param name="node" select="."/>
+  <yelp:cache key="db.number" node="$node">
+    <xsl:apply-templates mode="db.number.mode" select="$node"/>
+  </yelp:cache>
+</xsl:template>
+
 <!-- == db.chunk == -->
 <xsl:template name="db.chunk">
   <xsl:param name="node" select="."/>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]