[gs-cvs] rev 8493 - trunk/gs/src
leonardo at ghostscript.com
leonardo at ghostscript.com
Sun Jan 20 01:21:16 PST 2008
Author: leonardo
Date: 2008-01-20 01:21:15 -0800 (Sun, 20 Jan 2008)
New Revision: 8493
Modified:
trunk/gs/src/gsalloc.c
trunk/gs/src/gxalloc.h
trunk/gs/src/isave.c
Log:
Fix (save/restore) : Compact the changes list against big memory leak.
DETAILS :
Bug 689536 "Memory leak reading PostScript file (PMR 53877).".
The old implementation includes an optimization agains a big CPU time expense
in 'save' and 'restore' for sedtting and resetting l_mark flags.
Normally, when executing 'save', the memory manager needs
to reset l_new marks for objects modified or allocated at the last save level,
so that their further modification on the next save level to be saved
in the changes list. When executing 'restore' these flags to be set
to prevent redundant save.
When the last save level includes many allocations,
the number of scanned objects is big and it causes a significant
time expense. A long ago Ghostscript implements an optimization,
which shorten the scanned list against the big time expense.
However the shortening causes redundant saving.
The redundant saving itself isn't harmful because
it happens seldon and spends a small memory.
However if a redundant save element points to a reference to
a newly allocated array or dictionary, the array or dictionary
can't be garbage collected. When a client executes such allocation and
same reference modification multiple times, all allocated arrays
and dictionaries persist in memory, causing a significant memory leak.
This patch compacts the saved change list whem a significant memory
allocation is accummulated since last save or since last compacting.
The new field gs_ref_memory_s::total_scanned_after_compacting
works for counting allocated elements. The new function
drop_redundant_changes performs the compacting. See comments in its code.
The threshold for starting drop_redundant_changes is choosen arbitrary.
It must not be too small, because the CPU time expense
optimization wouldn't be effective. Also it can't be too big,
because the memory leak recovery would not be effective.
This patch sets it to about 1600000 objects,
which is an experimental trade-of.
Note that this patch reduces the memory leak but doesn't
eliminate it to zero, because more data may be allocated
after the last filtering.
We could implement a filtering within the garbager rather
than withis save/restore. Actually it wouldn't change
the behavior to much, because the threshold would be still needed
against the CPU time expense, It happens because the scanning
deals with array or dictioanr elements rather than with
whole aggregates. Also we don't want to complicate the garbager.
Note that the arbitrary threshold is bigger than neccessary
for the simplified test case included into the bug report.
Due ti that the leak elimination does not happen with this test.
For the compacting to take place the sequence "NEWJOB Z"
to be repeated 90 times rather than 40 in the supplied test.
The customer's test does demonstrate the leak reducing.
EXPECTED DIFFERENCES :
None.
Modified: trunk/gs/src/gsalloc.c
===================================================================
--- trunk/gs/src/gsalloc.c 2008-01-18 21:50:38 UTC (rev 8492)
+++ trunk/gs/src/gsalloc.c 2008-01-20 09:21:15 UTC (rev 8493)
@@ -326,6 +326,7 @@
mem->changes = 0;
mem->scan_limit = 0;
mem->total_scanned = 0;
+ mem->total_scanned_after_compacting = 0;
ialloc_reset_free(mem);
}
Modified: trunk/gs/src/gxalloc.h
===================================================================
--- trunk/gs/src/gxalloc.h 2008-01-18 21:50:38 UTC (rev 8492)
+++ trunk/gs/src/gxalloc.h 2008-01-20 09:21:15 UTC (rev 8493)
@@ -383,6 +383,7 @@
struct alloc_change_s *scan_limit;
struct alloc_save_s *saved;
long total_scanned;
+ long total_scanned_after_compacting;
struct alloc_save_s *reloc_saved; /* for GC */
gs_memory_status_t previous_status; /* total allocated & used */
/* in outer save levels */
Modified: trunk/gs/src/isave.c
===================================================================
--- trunk/gs/src/isave.c 2008-01-18 21:50:38 UTC (rev 8492)
+++ trunk/gs/src/isave.c 2008-01-20 09:21:15 UTC (rev 8493)
@@ -459,6 +459,7 @@
mem->space, (ulong) mem->streams);
mem->streams = 0;
mem->total_scanned = 0;
+ mem->total_scanned_after_compacting = 0;
if (sid)
mem->save_level++;
return save;
@@ -1299,15 +1300,69 @@
return 0;
}
+/* Drop redundant elements from the changes list and set l_new. */
+static void
+drop_redundant_changes(gs_ref_memory_t * mem)
+{
+ register alloc_change_t *chp = mem->changes, *chp_back = NULL, *chp_forth;
+
+ /* First reverse the list and set all. */
+ for (; chp; chp = chp_forth) {
+ chp_forth = chp->next;
+ if (chp->offset != AC_OFFSET_ALLOCATED) {
+ ref_packed *prp = chp->where;
+
+ if (!r_is_packed(prp)) {
+ ref *const rp = (ref *)prp;
+
+ rp->tas.type_attrs |= l_new;
+ }
+ }
+ chp->next = chp_back;
+ chp_back = chp;
+ }
+ mem->changes = chp_back;
+ chp_back = NULL;
+ /* Then filter, reset and reverse again. */
+ for (chp = mem->changes; chp; chp = chp_forth) {
+ chp_forth = chp->next;
+ if (chp->offset != AC_OFFSET_ALLOCATED) {
+ ref_packed *prp = chp->where;
+
+ if (!r_is_packed(prp)) {
+ ref *const rp = (ref *)prp;
+
+ if ((rp->tas.type_attrs & l_new) == 0) {
+ if (mem->scan_limit == chp)
+ mem->scan_limit = chp_back;
+ if (mem->changes == chp)
+ mem->changes = chp_back;
+ gs_free_object((gs_memory_t *)mem, chp, "alloc_save_remove");
+ continue;
+ } else
+ rp->tas.type_attrs &= ~l_new;
+ }
+ }
+ chp->next = chp_back;
+ chp_back = chp;
+ }
+ mem->changes = chp_back;
+}
+
+
/* Set or reset the l_new attribute on the changes chain. */
static int
save_set_new_changes(gs_ref_memory_t * mem, bool to_new, bool set_limit)
{
- register alloc_change_t *chp = mem->changes;
+ register alloc_change_t *chp;
register uint new = (to_new ? l_new : 0);
- ulong scanned = mem->total_scanned;
+ ulong scanned = 0;
- for (; chp; chp = chp->next) {
+ if (!to_new && mem->total_scanned_after_compacting > max_repeated_scan * 16) {
+ mem->total_scanned_after_compacting = 0;
+ drop_redundant_changes(mem);
+ }
+ for (chp = mem->changes; chp; chp = chp->next) {
if (chp->offset == AC_OFFSET_ALLOCATED) {
if (chp->where != 0) {
uint size;
@@ -1333,11 +1388,12 @@
break;
}
if (set_limit) {
- if (scanned >= max_repeated_scan) {
+ mem->total_scanned_after_compacting += scanned;
+ if (scanned + mem->total_scanned >= max_repeated_scan) {
mem->scan_limit = mem->changes;
mem->total_scanned = 0;
} else
- mem->total_scanned = scanned;
+ mem->total_scanned += scanned;
}
return 0;
}
More information about the gs-cvs
mailing list