Perf improvement for some scenarios by caching SplashPath.

PDF from https://bugs.freedesktop.org/show_bug.cgi?id=11849
shows a high rate of creation/destruction of SplashPath
objects. At the same time, only a few exists at the same
time. This patch implements a small cache to recycle
SplashPath objects. In my test rendering time for the above
PDF dropped ~4%, mostly thanks to decent reduction in
malloc/free calls.
diff --git a/poppler/SplashOutputDev.cc b/poppler/SplashOutputDev.cc
index d8ae539..dd211f4 100644
--- a/poppler/SplashOutputDev.cc
+++ b/poppler/SplashOutputDev.cc
@@ -1170,7 +1170,7 @@
   }
   path = convertPath(state, state->getPath());
   splash->stroke(path);
-  delete path;
+  SplashPath::destroy(path);
 }
 
 void SplashOutputDev::fill(GfxState *state) {
@@ -1181,7 +1181,7 @@
   }
   path = convertPath(state, state->getPath());
   splash->fill(path, gFalse);
-  delete path;
+  SplashPath::destroy(path);
 }
 
 void SplashOutputDev::eoFill(GfxState *state) {
@@ -1192,7 +1192,7 @@
   }
   path = convertPath(state, state->getPath());
   splash->fill(path, gTrue);
-  delete path;
+  SplashPath::destroy(path);
 }
 
 void SplashOutputDev::clip(GfxState *state) {
@@ -1200,7 +1200,7 @@
 
   path = convertPath(state, state->getPath());
   splash->clipToPath(path, gFalse);
-  delete path;
+  SplashPath::destroy(path);
 }
 
 void SplashOutputDev::eoClip(GfxState *state) {
@@ -1208,7 +1208,7 @@
 
   path = convertPath(state, state->getPath());
   splash->clipToPath(path, gTrue);
-  delete path;
+  SplashPath::destroy(path);
 }
 
 void SplashOutputDev::clipToStrokePath(GfxState *state) {
@@ -1216,9 +1216,9 @@
 
   path = convertPath(state, state->getPath());
   path2 = splash->makeStrokePath(path);
-  delete path;
+  SplashPath::destroy(path);
   splash->clipToPath(path2, gFalse);
-  delete path2;
+  SplashPath::destroy(path2);
 }
 
 SplashPath *SplashOutputDev::convertPath(GfxState * /*state*/, GfxPath *path) {
@@ -1226,7 +1226,7 @@
   GfxSubpath *subpath;
   int i, j;
 
-  sPath = new SplashPath();
+  sPath = SplashPath::create();
   for (i = 0; i < path->getNumSubpaths(); ++i) {
     subpath = path->getSubpath(i);
     if (subpath->getNumPoints() > 0) {
@@ -1291,9 +1291,9 @@
   if ((render & 3) == 1 || (render & 3) == 2) {
     if (!state->getStrokeColorSpace()->isNonMarking()) {
       if ((path = font->getGlyphPath(code))) {
-	path->offset((SplashCoord)x, (SplashCoord)y);
-	splash->stroke(path);
-	delete path;
+        path->offset((SplashCoord)x, (SplashCoord)y);
+        splash->stroke(path);
+        SplashPath::destroy(path);
       }
     }
   }
@@ -1303,10 +1303,10 @@
     if ((path = font->getGlyphPath(code))) {
       path->offset((SplashCoord)x, (SplashCoord)y);
       if (textClipPath) {
-	textClipPath->append(path);
-	delete path;
+        textClipPath->append(path);
+        SplashPath::destroy(path);
       } else {
-	textClipPath = path;
+        textClipPath = path;
       }
     }
   }
@@ -1585,7 +1585,7 @@
 void SplashOutputDev::endTextObject(GfxState *state) {
   if (textClipPath) {
     splash->clipToPath(textClipPath, gFalse);
-    delete textClipPath;
+    SplashPath::destroy(textClipPath);
     textClipPath = NULL;
   }
 }
diff --git a/splash/Splash.cc b/splash/Splash.cc
index 45eb6fe..84c3300 100644
--- a/splash/Splash.cc
+++ b/splash/Splash.cc
@@ -1184,7 +1184,7 @@
   path2 = flattenPath(path, state->matrix, state->flatness);
   if (state->lineDashLength > 0) {
     dPath = makeDashedPath(path2);
-    delete path2;
+    SplashPath::destroy(path2);
     path2 = dPath;
   }
   if (state->lineWidth == 0) {
@@ -1192,7 +1192,7 @@
   } else {
     strokeWide(path2);
   }
-  delete path2;
+  SplashPath::destroy(path2);
   return splashOk;
 }
 
@@ -1310,7 +1310,7 @@
 
   path2 = makeStrokePath(path, gFalse);
   fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha);
-  delete path2;
+  SplashPath::destroy(path2);
 }
 
 SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix,
@@ -1320,7 +1320,7 @@
   Guchar flag;
   int i;
 
-  fPath = new SplashPath();
+  fPath = SplashPath::create();
   flatness2 = flatness * flatness;
   i = 0;
   while (i < path->length) {
@@ -1451,7 +1451,7 @@
     ++lineDashStartIdx;
   }
 
-  dPath = new SplashPath();
+  dPath = SplashPath::create();
 
   // process each subpath
   i = 0;
@@ -3219,7 +3219,7 @@
     pathIn = flattenPath(path, state->matrix, state->flatness);
     if (state->lineDashLength > 0) {
       pathOut = makeDashedPath(pathIn);
-      delete pathIn;
+      SplashPath::destroy(pathIn);
       pathIn = pathOut;
     }
   } else {
@@ -3231,7 +3231,7 @@
   left0 = left1 = right0 = right1 = join0 = join1 = 0; // make gcc happy
   leftFirst = rightFirst = firstPt = 0; // make gcc happy
 
-  pathOut = new SplashPath();
+  pathOut = SplashPath::create();
   w = state->lineWidth;
 
   for (i = 0; i < pathIn->length - 1; ++i) {
@@ -3491,7 +3491,7 @@
   }
 
   if (pathIn != path) {
-    delete pathIn;
+    SplashPath::destroy(pathIn);
   }
 
   return pathOut;
diff --git a/splash/SplashFTFont.cc b/splash/SplashFTFont.cc
index 963d42d..dfda251 100644
--- a/splash/SplashFTFont.cc
+++ b/splash/SplashFTFont.cc
@@ -268,7 +268,7 @@
   if (FT_Get_Glyph(slot, &glyph)) {
     return NULL;
   }
-  path.path = new SplashPath();
+  path.path = SplashPath::create();
   path.textScale = textScale;
   path.needClose = gFalse;
   FT_Outline_Decompose(&((FT_OutlineGlyph)glyph)->outline,
diff --git a/splash/SplashPath.cc b/splash/SplashPath.cc
index 261f778..29c5a2d 100644
--- a/splash/SplashPath.cc
+++ b/splash/SplashPath.cc
@@ -15,6 +15,42 @@
 #include "SplashErrorCodes.h"
 #include "SplashPath.h"
 
+// According to my profiling, number of SplahPath objects created at the same
+// time rarely exceeds 4, so this is a good number for the size of the cache
+#define CACHE_SIZE 4
+
+static int cachedCount = 0;
+static SplashPath* splashPathCache[CACHE_SIZE] = { NULL };
+
+SplashPath* SplashPath::create()
+{
+  SplashPath* result = NULL;
+  if (cachedCount > 0) {
+    result = splashPathCache[--cachedCount];
+  } else {
+    result = new SplashPath();
+  }
+  return result;
+}
+
+void SplashPath::destroy(SplashPath* path)
+{
+  if (cachedCount < CACHE_SIZE) {
+    path->Reset();
+    splashPathCache[cachedCount++] = path;
+  }
+  else
+    delete path;
+}
+
+void SplashPath::emptyCache()
+{
+  for (int i=0; i < cachedCount; i++) {
+    delete splashPathCache[i];
+  }
+  cachedCount = 0;    
+}
+
 //------------------------------------------------------------------------
 // SplashPath
 //------------------------------------------------------------------------
@@ -182,3 +218,12 @@
   *y = pts[length - 1].y;
   return gTrue;
 }
+
+void SplashPath::Reset()
+{
+  // TODO: possibly free data if size is above some threshold to avoid
+  // cache eating too much memory
+  length = 0;
+  hintsLength = 0;
+}
+
diff --git a/splash/SplashPath.h b/splash/SplashPath.h
index ea58af0..36e9b4e 100644
--- a/splash/SplashPath.h
+++ b/splash/SplashPath.h
@@ -54,14 +54,13 @@
 class SplashPath {
 public:
 
-  // Create an empty path.
-  SplashPath();
+  static SplashPath* create();
+  static void destroy(SplashPath* path);
+  static void emptyCache();
 
   // Copy a path.
   SplashPath *copy() { return new SplashPath(this); }
 
-  ~SplashPath();
-
   // Append <path> to <this>.
   void append(SplashPath *path);
 
@@ -96,9 +95,15 @@
   // Get the current point.
   GBool getCurPt(SplashCoord *x, SplashCoord *y);
 
+  void Reset();
 private:
 
+  // Create an empty path.
+  SplashPath();
   SplashPath(SplashPath *path);
+
+  ~SplashPath();
+
   void grow(int nPts);
   GBool noCurrentPoint() { return curSubpath == length; }
   GBool onePointSubpath() { return curSubpath == length - 1; }
diff --git a/splash/SplashT1Font.cc b/splash/SplashT1Font.cc
index b30c0fc..fb2b7f0 100644
--- a/splash/SplashT1Font.cc
+++ b/splash/SplashT1Font.cc
@@ -238,7 +238,7 @@
     T1_TransformFont(outlineID, &matrix);
   }
 
-  path = new SplashPath();
+  path = SplashPath::create();
   if ((outline = T1_GetCharOutline(outlineID, c, outlineSize, NULL))) {
     x = 0;
     y = 0;