m4-patches
[Top][All Lists]
Advanced

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

memory waste in macro recursion


From: Eric Blake
Subject: memory waste in macro recursion
Date: Tue, 13 Nov 2007 19:04:26 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

I discovered a quasi-memory leak during my efforts on $@ references.  It is not 
a true memory leak, because the memory lived on an obstack, and was eventually 
freed once recursion ends; however, it was rather wasteful of resources to keep 
that much memory live when it will never be referenced again.  Patching the 
leak gave such impressive results, with so little code, that I'm installing 
this patch now on the branch, and a similar one on the head.  It dramatically 
cuts the peak memory usage of examples/loop.m4, with negligible impact to 
speed, thereby allowing much higher iteration limits before running into 
machine limits.  Tested with 'm4 -Iexamples -Dlimit=n loop.m4':

            n=1000          n=1500          n=2000
pre-patch:  17.8Mb 1.575s   40.7Mb 3.732s   73.0Mb 6.763s
post-patch:  2.0Mb 1.607s    2.0Mb 3.732s    2.0Mb 6.747s

For this particular patch, loop.m4 still exhibits quadratic timing growth, but 
the memory usage dropped from O(n^2) to O(1)!

Progress on my $@ patch series: I've finally resolved my memory allocation 
issues (I spent the better part of my last two weeks of hacking time figuring 
out how to correctly lengthen the lifetime of references without hanging on to 
memory too long).  I have also gotten foreachq3.m4 reduced from quadratic down 
to logarithmic scaling in time; but am still trying to get similar results for 
foreachq2.m4 (as well as see if I can get closer to linear instead of log 
scaling).  Would anyone be interested if I pushed my progress onto a public git 
branch?

From: Eric Blake <address@hidden>
Date: Tue, 13 Nov 2007 11:59:43 -0700
Subject: [PATCH] Fix memory leak in tail recursion.

* src/input.c (push_string_init): Let go of memory earlier.
(next_char_1): Make end of string detection reliable.
(match_input): Simplify use of push_string_init.
* NEWS: Document this fix.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog   |    8 ++++++++
 NEWS        |    1 +
 src/input.c |   53 +++++++++++++++++++++++++++++++++++++++++------------
 3 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 86d8ad4..b52f3e0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2007-11-13  Eric Blake  <address@hidden>
+
+       Fix memory leak in tail recursion.
+       * src/input.c (push_string_init): Let go of memory earlier.
+       (next_char_1): Make end of string detection reliable.
+       (match_input): Simplify use of push_string_init.
+       * NEWS: Document this fix.
+
 2007-11-07  Eric Blake  <address@hidden>
 
        * doc/m4.texinfo (Pseudo Arguments): Test more corner cases.
diff --git a/NEWS b/NEWS
index 6d667f0..b92630b 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,7 @@ Version 1.4.11 - ?? ??? 2007, by ????  (git version 1.4.10a-*)
   possible to concatenate a builtin macro with anything else.
 * Several improvements in `index', `regexp', and `patsubst' builtins to
   speed up typical Autoconf usage.
+* Memory usage is greatly reduced in recursive macros.
 * A number of portability improvements inherited from gnulib.
 
 Version 1.4.10 - 09 Jul 2007, by Eric Blake  (CVS version 1.4.9c)
diff --git a/src/input.c b/src/input.c
index 23903f3..3ef7b83 100644
--- a/src/input.c
+++ b/src/input.c
@@ -241,7 +241,37 @@ push_macro (builtin_func *func)
 struct obstack *
 push_string_init (void)
 {
+  /* Free any memory occupied by completely parsed strings.  */
+  bool pruning = true;
   assert (next == NULL);
+  while (isp && pruning)
+    {
+      switch (isp->type)
+       {
+       case INPUT_STRING:
+         if (*isp->u.u_s.string)
+           pruning = false;
+         break;
+
+       case INPUT_FILE:
+       case INPUT_MACRO:
+         pruning = false;
+         break;
+
+       default:
+         assert (!"push_string_init");
+         abort ();
+       }
+      if (pruning)
+       {
+         next = isp;
+         isp = isp->prev;
+       }
+    }
+  if (next)
+    obstack_free (current_input, next);
+
+  /* Reserve the next location on the obstack.  */
   next = (input_block *) obstack_alloc (current_input,
                                        sizeof (struct input_block));
   next->type = INPUT_STRING;
@@ -497,9 +527,12 @@ next_char_1 (void)
       switch (isp->type)
        {
        case INPUT_STRING:
-         ch = to_uchar (*isp->u.u_s.string++);
+         ch = to_uchar (*isp->u.u_s.string);
          if (ch != '\0')
-           return ch;
+           {
+             *isp->u.u_s.string++;
+             return ch;
+           }
          break;
 
        case INPUT_FILE:
@@ -536,10 +569,10 @@ next_char_1 (void)
     }
 }
 
-/*------------------------------------------------------------------------.
-| skip_line () simply discards all immediately following characters, upto |
-| the first newline.  It is only used from m4_dnl ().                    |
-`------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------.
+| skip_line () simply discards all immediately following characters, |
+| up to the first newline.  It is only used from m4_dnl ().          |
+`-------------------------------------------------------------------*/
 
 void
 skip_line (void)
@@ -607,12 +640,8 @@ match_input (const char *s, bool consume)
     }
 
   /* Failed or shouldn't consume, push back input.  */
-  {
-    struct obstack *h = push_string_init ();
-
-    /* `obstack_grow' may be macro evaluating its arg 1 several times. */
-    obstack_grow (h, t, n);
-  }
+  push_string_init ();
+  obstack_grow (current_input, t, n);
   push_string_finish ();
   return result;
 }
-- 
1.5.3.2







reply via email to

[Prev in Thread] Current Thread [Next in Thread]