[gs-cvs] rev 8721 - trunk/gs/src
ray at ghostscript.com
ray at ghostscript.com
Thu May 8 19:18:15 PDT 2008
Author: ray
Date: 2008-05-08 19:18:14 -0700 (Thu, 08 May 2008)
New Revision: 8721
Added:
trunk/gs/src/gsmchunk.c
trunk/gs/src/gsmchunk.h
trunk/gs/src/gxclthrd.c
trunk/gs/src/gxclthrd.h
Modified:
trunk/gs/src/Makefile.in
trunk/gs/src/configure.ac
trunk/gs/src/gdevprn.c
trunk/gs/src/gdevprn.h
trunk/gs/src/gsmalloc.c
trunk/gs/src/gsmalloc.h
trunk/gs/src/gsmemlok.c
trunk/gs/src/gxclist.c
trunk/gs/src/gxclist.h
trunk/gs/src/gxclmem.c
trunk/gs/src/gxclmem.h
trunk/gs/src/gxclread.c
trunk/gs/src/lib.mak
Log:
This is the "final" merge of the mtrender (multi-threaded clist rendering)
branch into the trunk. The default behavior is still the same, i.e., the
clist rendering is done in the same thread as the parsing (main thread).
The 'gsmalloc' memory allocator now ALWAYS uses a mutex to lock accesses
in case it is used by a multi-threaded client. This was determined to
result is less than 1% performance hit on a single threaded client.
Refer to the log messages on the mtrender branch for details on the
design of the multi-threaded clist rendering, but the summary is that
-dNumRenderingThreads=# (default 0) determines the number of background
threads REQUESTED for rendering bands. If the platform doesn't support
threads, or if there is an error starting the threads from the clist_
get_bits_rectangle_mt hook, single threaded rendering will be used.
The number of threads may be less than the requested number if the
number of bands is less than the request, or if there is an error
setting up threads (as many as can be created, up to the request will
be used). The -Z: debug switch emits status messages indicating how
many threads are requested (-dNumRenderingThreads) and the number that
is actually used.
Many files show little if any improvement with multi-threaded clist
rendering since they are dominated by the clist writing time, or by
the time required to write the output. No files seen to date show a
performance hit greater than aobut 3%. Setting NumRenderingThreads
to a count higher than the number of CPU cores available does not
seem to help or hurt much, but there is overhead (per page) with
starting threads and allocating band buffers, so a very large count
is not productive.
The best performance seen on an Intel Core 2 Duo system, on a
particular file is about a 75% performance improvement (completing
the page in 57% of the single threaded time). This time did not
include writing a file (output to /dev/null).
In order to prevent memory mutex (locking) contention from slowing
down multi-threaded clist rendering, each thread uses a 'chunk'
wrapper on NON-GC the non-gc memory allocator. Each chunk allocator
is thread-safe/instantiated, so the locking only occurs on chunk
allocations to the wrapped (target) memory allocator.
The 'BAND_LIST_STORAGE=mmeory' option has been supplemented to allow
multiple concurrent threads to read the clist 'memfile'.
The "autoconf" files (configure.ac and Makefile.in) have been improved
to hook the 'posix' pthreads if available, so linux and Mac OS/X will
be able to support the multi-threaded clist rendering.
Modified: trunk/gs/src/Makefile.in
===================================================================
--- trunk/gs/src/Makefile.in 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/Makefile.in 2008-05-09 02:18:14 UTC (rev 8721)
@@ -304,11 +304,8 @@
# All reasonable platforms require -lm, but Rhapsody and perhaps one or
# two others fold libm into libc and don't require any additional library.
-#STDLIBS=-lpthread -lm
+STDLIBS=@PTHREAD_LIBS@ -lm
-# Since the default build is for nosync, don't include pthread lib
-STDLIBS=-lm
-
# Define the include switch(es) for the X11 header files.
# This can be null if handled in some other way (e.g., the files are
# in /usr/include, or the directory is supplied by an environment variable)
@@ -339,12 +336,11 @@
# primitives for this platform.
# If POSIX sync primitives are used, also change the STDLIBS to include
-# the pthread library.
+# the pthread library. Otherwise use SYNC=nosync
#SYNC=posync
+#SYNC=nosync
+SYNC=@SYNC@
-# Default is No sync primitives since some platforms don't have it (HP-UX)
-SYNC=nosync
-
# programs we use
RM=rm -f
Modified: trunk/gs/src/configure.ac
===================================================================
--- trunk/gs/src/configure.ac 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/configure.ac 2008-05-09 02:18:14 UTC (rev 8721)
@@ -247,8 +247,16 @@
dnl --------------------------------------------------
AC_CHECK_LIB(m, cos)
-dnl AC_CHECK_LIB(pthread, pthread_create)
+SYNC="nosync"
+PTHREAD_LIBS=""
+AC_CHECK_LIB(pthread, pthread_create, [
+ SYNC=posync;
+ PTHREAD_LIBS="-lpthread"
+])
+AC_SUBST(SYNC)
+AC_SUBST(PTHREAD_LIBS)
+
dnl Tests for iconv (Needed for OpenPrinting Vector, "opvp" output device)
AC_ARG_WITH(libiconv,
[AC_HELP_STRING([--with-libiconv=@<:@no/gnu/native@:>@],
Modified: trunk/gs/src/gdevprn.c
===================================================================
--- trunk/gs/src/gdevprn.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gdevprn.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -358,6 +358,11 @@
if ( code >= 0 || (reallocate && pass > 1) )
ppdev->procs = gs_clist_device_procs;
+ /*
+ * Now the device is a clist device, we enable multi-threaded rendering.
+ * It will remain enabled, but that doesn't really cause any problems.
+ */
+ clist_enable_multi_thread_render(pdev);
} else {
/* Render entirely in memory. */
gx_device *bdev = (gx_device *)pmemdev;
@@ -497,6 +502,7 @@
(code = param_write_int(plist, "BandWidth", &ppdev->space_params.band.BandWidth)) < 0 ||
(code = param_write_int(plist, "BandHeight", &ppdev->space_params.band.BandHeight)) < 0 ||
(code = param_write_long(plist, "BandBufferSpace", &ppdev->space_params.band.BandBufferSpace)) < 0 ||
+ (code = param_write_int(plist, "NumRenderingThreads", &ppdev->num_render_threads_requested)) < 0 ||
(code = param_write_bool(plist, "OpenOutputFile", &ppdev->OpenOutputFile)) < 0 ||
(code = param_write_bool(plist, "ReopenPerPage", &ppdev->ReopenPerPage)) < 0 ||
(code = param_write_bool(plist, "PageUsesTransparency",
@@ -542,6 +548,7 @@
int duplex_set = -1;
int width = pdev->width;
int height = pdev->height;
+ int nthreads = ppdev->num_render_threads_requested;
gdev_prn_space_params sp, save_sp;
gs_param_string ofs;
gs_param_dict mdict;
@@ -665,6 +672,16 @@
read_media("InputAttributes");
read_media("OutputAttributes");
+ switch (code = param_read_int(plist, (param_name = "NumRenderingThreads"), &nthreads)) {
+ case 0:
+ break;
+ default:
+ ecode = code;
+ param_signal_error(plist, param_name, ecode);
+ case 1:
+ ;
+ }
+
if (ecode < 0)
return ecode;
/* Prevent gx_default_put_params from closing the printer. */
@@ -682,6 +699,7 @@
ppdev->Duplex_set = duplex_set;
}
ppdev->space_params = sp;
+ ppdev->num_render_threads_requested = nthreads;
/* If necessary, free and reallocate the printer memory. */
/* Formerly, would not reallocate if device is not open: */
Modified: trunk/gs/src/gdevprn.h
===================================================================
--- trunk/gs/src/gdevprn.h 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gdevprn.h 2008-05-09 02:18:14 UTC (rev 8721)
@@ -246,6 +246,7 @@
gx_device_printer *async_renderer; /* in async writer, pointer to async renderer */\
uint clist_disable_mask; /* mask of clist options to disable */\
/* ---- End async rendering support --- */\
+ int num_render_threads_requested; /* for multiple band rendering threads */\
gx_device_procs orig_procs /* original (std_)procs */
/* The device descriptor */
@@ -383,6 +384,7 @@
0/*false*/, -1, /* Duplex[_set] */\
0/*false*/, 0, 0, 0, /* file_is_new ... buf */\
0, 0, 0, 0, 0/*false*/, 0, 0, /* buffer_memory ... clist_dis'_mask */\
+ 0, /* num_render_threads_requested */\
{ 0 } /* ... orig_procs */
#define prn_device_body_rest_(print_page)\
prn_device_body_rest2_(print_page, gx_default_print_page_copies)
Modified: trunk/gs/src/gsmalloc.c
===================================================================
--- trunk/gs/src/gsmalloc.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gsmalloc.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -22,7 +22,6 @@
#include "gsmdebug.h"
#include "gsstruct.h" /* for st_bytes */
#include "gsmalloc.h"
-#include "gsmemlok.h" /* locking (multithreading) wrapper */
#include "gsmemret.h" /* retrying wrapper */
@@ -118,6 +117,9 @@
mem->max_used = 0;
mem->gs_lib_ctx = 0;
mem->non_gc_memory = (gs_memory_t *)mem;
+ /* Allocate a monitor to serialize access to structures within */
+ mem->monitor = NULL; /* prevent use during initial allocation */
+ mem->monitor = gx_monitor_alloc((gs_memory_t *)mem);
return mem;
}
@@ -163,6 +165,9 @@
# define set_msg(str) DO_NOTHING
#endif
+ /* Exclusive acces so our decisions and changes are 'atomic' */
+ if (mmem->monitor)
+ gx_monitor_enter(mmem->monitor);
if (size > mmem->limit - sizeof(gs_malloc_block_t)) {
/* Definitely too large to allocate; also avoids overflow. */
set_msg("exceeded limit");
@@ -192,12 +197,16 @@
bp->cname = cname;
mmem->allocated = bp;
ptr = (byte *) (bp + 1);
- gs_alloc_fill(ptr, gs_alloc_fill_alloc, size);
mmem->used += size + sizeof(gs_malloc_block_t);
if (mmem->used > mmem->max_used)
mmem->max_used = mmem->used;
}
}
+ if (mmem->monitor)
+ gx_monitor_leave(mmem->monitor); /* Done with exclusive access */
+ /* We don't want to 'fill' under mutex to keep the window smaller */
+ if (ptr)
+ gs_alloc_fill(ptr, gs_alloc_fill_alloc, size);
#ifdef DEBUG
if (gs_debug_c('a') || msg != ok_msg)
dlprintf4("[a+]gs_malloc(%s)(%u) = 0x%lx: %s\n",
@@ -256,6 +265,8 @@
if (new_size == old_size)
return obj;
+ if (mmem->monitor)
+ gx_monitor_enter(mmem->monitor); /* Exclusive access */
new_ptr = (gs_malloc_block_t *) gs_realloc(ptr, old_size, new_size);
if (new_ptr == 0)
return 0;
@@ -268,6 +279,8 @@
new_ptr->size = new_size - sizeof(gs_malloc_block_t);
mmem->used -= old_size;
mmem->used += new_size;
+ if (mmem->monitor)
+ gx_monitor_leave(mmem->monitor); /* Done with exclusive access */
if (new_size > old_size)
gs_alloc_fill((byte *) new_ptr + old_size,
gs_alloc_fill_alloc, new_size - old_size);
@@ -304,6 +317,8 @@
(ulong) ptr, client_name_string(cname));
(*finalize) (ptr);
}
+ if (mmem->monitor)
+ gx_monitor_enter(mmem->monitor); /* Exclusive access */
bp = mmem->allocated; /* If 'finalize' releases a memory,
this function could be called recursively and
change mmem->allocated. */
@@ -313,6 +328,8 @@
if (mmem->allocated)
mmem->allocated->prev = 0;
+ if (mmem->monitor)
+ gx_monitor_leave(mmem->monitor); /* Done with exclusive access */
gs_alloc_fill(bp, gs_alloc_fill_free,
bp->size + sizeof(gs_malloc_block_t));
free(bp);
@@ -330,6 +347,8 @@
if (np->next)
np->next->prev = bp;
mmem->used -= np->size + sizeof(gs_malloc_block_t);
+ if (mmem->monitor)
+ gx_monitor_leave(mmem->monitor); /* Done with exclusive access */
gs_alloc_fill(np, gs_alloc_fill_free,
np->size + sizeof(gs_malloc_block_t));
free(np);
@@ -337,6 +356,8 @@
}
}
}
+ if (mmem->monitor)
+ gx_monitor_leave(mmem->monitor); /* Done with exclusive access */
lprintf2("%s: free 0x%lx not found!\n",
client_name_string(cname), (ulong) ptr);
free((char *)((gs_malloc_block_t *) ptr - 1));
@@ -379,6 +400,12 @@
{
return mem; /* heap memory is stable */
}
+
+/*
+ * NB: In a multi-threaded application, this is only a 'snapshot'
+ * since other threads may change the heap_status. The heap_available()
+ * probe is just an approximation anyway.
+ */
static void
gs_heap_status(gs_memory_t * mem, gs_memory_status_t * pstat)
{
@@ -403,7 +430,16 @@
gs_heap_free_all(gs_memory_t * mem, uint free_mask, client_name_t cname)
{
gs_malloc_memory_t *const mmem = (gs_malloc_memory_t *) mem;
+ gx_monitor_t *mon = mmem->monitor;
+ /*
+ * We don't perform locking during this process since the 'monitor'
+ * is contained in this allocator, and will get freed along the way.
+ * It is only called at exit, and there better not be any threads
+ * accessing this allocator.
+ */
+ mmem->monitor = NULL; /* delete reference to this monitor */
+ gx_monitor_free(mon); /* free the monitor */
if (free_mask & FREE_ALL_DATA) {
gs_malloc_block_t *bp = mmem->allocated;
gs_malloc_block_t *np;
@@ -427,41 +463,34 @@
int
gs_malloc_wrap(gs_memory_t **wrapped, gs_malloc_memory_t *contents)
{
-#ifdef USE_RETRY_AND_LOCKING_MEMORY_WRAPPERS
- gs_memory_t *cmem = (gs_memory_t *)contents;
- gs_memory_locked_t *lmem = (gs_memory_locked_t *)
- gs_alloc_bytes_immovable(cmem, sizeof(gs_memory_locked_t),
- "gs_malloc_wrap(locked)");
- gs_memory_retrying_t *rmem;
- int code;
+# ifdef USE_RETRY_MEMORY_WRAPPER
+ /*
+ * This is deprecated since 'retry' for clist reversion/cycling
+ * will ONLY work for monochrome, simple PS or PCL, not for a
+ * color device and not for PDF or XPS with transparency
+ */
+ {
+ gs_memory_retrying_t *rmem;
+ rmem = (gs_memory_retrying_t *)
+ gs_alloc_bytes_immovable((gs_memory_t *)lmem,
+ sizeof(gs_memory_retrying_t),
+ "gs_malloc_wrap(retrying)");
+ if (rmem == 0) {
+ gs_memory_locked_release(lmem);
+ gs_free_object(cmem, lmem, "gs_malloc_wrap(locked)");
+ return_error(gs_error_VMerror);
+ }
+ code = gs_memory_retrying_init(rmem, (gs_memory_t *)lmem);
+ if (code < 0) {
+ gs_free_object((gs_memory_t *)lmem, rmem, "gs_malloc_wrap(retrying)");
+ gs_memory_locked_release(lmem);
+ gs_free_object(cmem, lmem, "gs_malloc_wrap(locked)");
+ return code;
+ }
- if (lmem == 0)
- return_error(gs_error_VMerror);
- code = gs_memory_locked_init(lmem, cmem);
- if (code < 0) {
- gs_free_object(cmem, lmem, "gs_malloc_wrap(locked)");
- return code;
+ *wrapped = (gs_memory_t *)rmem;
}
-
- rmem = (gs_memory_retrying_t *)
- gs_alloc_bytes_immovable((gs_memory_t *)lmem,
- sizeof(gs_memory_retrying_t),
- "gs_malloc_wrap(retrying)");
- if (rmem == 0) {
- gs_memory_locked_release(lmem);
- gs_free_object(cmem, lmem, "gs_malloc_wrap(locked)");
- return_error(gs_error_VMerror);
- }
- code = gs_memory_retrying_init(rmem, (gs_memory_t *)lmem);
- if (code < 0) {
- gs_free_object((gs_memory_t *)lmem, rmem, "gs_malloc_wrap(retrying)");
- gs_memory_locked_release(lmem);
- gs_free_object(cmem, lmem, "gs_malloc_wrap(locked)");
- return code;
- }
-
- *wrapped = (gs_memory_t *)rmem;
-#endif
+# endif /* retrying */
return 0;
}
@@ -469,35 +498,28 @@
gs_malloc_memory_t *
gs_malloc_wrapped_contents(gs_memory_t *wrapped)
{
-#ifdef USE_RETRY_AND_LOCKING_MEMORY_WRAPPERS
+#ifdef USE_RETRY_MEMORY_WRAPPER
gs_memory_retrying_t *rmem = (gs_memory_retrying_t *)wrapped;
- gs_memory_locked_t *lmem =
- (gs_memory_locked_t *)gs_memory_retrying_target(rmem);
- if (lmem)
- return (gs_malloc_memory_t *)gs_memory_locked_target(lmem);
- return (gs_malloc_memory_t *) wrapped;
-#else
+
+ return (gs_malloc_memory_t *)gs_memory_retrying_target(rmem);
+#else /* retrying */
return (gs_malloc_memory_t *)wrapped;
-#endif
+#endif /* retrying */
}
/* Free the wrapper, and return the wrapped contents. */
gs_malloc_memory_t *
gs_malloc_unwrap(gs_memory_t *wrapped)
{
-#ifdef USE_RETRY_AND_LOCKING_MEMORY_WRAPPERS
+#ifdef USE_RETRY_MEMORY_WRAPPER
gs_memory_retrying_t *rmem = (gs_memory_retrying_t *)wrapped;
- gs_memory_locked_t *lmem =
- (gs_memory_locked_t *)gs_memory_retrying_target(rmem);
- gs_memory_t *contents = gs_memory_locked_target(lmem);
+ gs_memory_t *contents = gs_memory_retrying_target(rmem);
- gs_free_object((gs_memory_t *)lmem, rmem, "gs_malloc_unwrap(retrying)");
- gs_memory_locked_release(lmem);
- gs_free_object(contents, lmem, "gs_malloc_unwrap(locked)");
+ gs_free_object(wrapped rmem, "gs_malloc_unwrap(retrying)");
return (gs_malloc_memory_t *)contents;
#else
return (gs_malloc_memory_t *)wrapped;
-#endif
+#endif
}
@@ -513,7 +535,7 @@
else
gs_lib_ctx_init((gs_memory_t *)malloc_memory_default);
-#ifdef USE_RETRY_AND_LOCKING_MEMORY_WRAPPERS
+#if defined(USE_RETRY_MEMORY_WRAPPER)
gs_malloc_wrap(&memory_t_default, malloc_memory_default);
#else
memory_t_default = (gs_memory_t *)malloc_memory_default;
@@ -526,7 +548,7 @@
void
gs_malloc_release(gs_memory_t *mem)
{
-#ifdef USE_RETRY_AND_LOCKING_MEMORY_WRAPPERS
+#ifdef USE_RETRY_MEMORY_WRAPPER
gs_malloc_memory_t * malloc_memory_default = gs_malloc_unwrap(mem);
#else
gs_malloc_memory_t * malloc_memory_default = (gs_malloc_memory_t *)mem;
Modified: trunk/gs/src/gsmalloc.h
===================================================================
--- trunk/gs/src/gsmalloc.h 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gsmalloc.h 2008-05-09 02:18:14 UTC (rev 8721)
@@ -18,6 +18,8 @@
#ifndef gsmalloc_INCLUDED
# define gsmalloc_INCLUDED
+#include "gxsync.h"
+
/* Define a memory manager that allocates directly from the C heap. */
typedef struct gs_malloc_block_s gs_malloc_block_t;
typedef struct gs_malloc_memory_s {
@@ -26,6 +28,7 @@
long limit;
long used;
long max_used;
+ gx_monitor_t *monitor; /* monitor to serialize access to functions */
} gs_malloc_memory_t;
/* Allocate and initialize a malloc memory manager. */
Added: trunk/gs/src/gsmchunk.c
===================================================================
--- trunk/gs/src/gsmchunk.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gsmchunk.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -0,0 +1,729 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id:$ */
+/* chunk consolidating wrapper on a base memory allocator */
+
+#include "memory_.h"
+#include "gx.h"
+#include "gsstype.h"
+#include "gserrors.h"
+#include "gsmchunk.h"
+
+/* Raw memory procedures */
+static gs_memory_proc_alloc_bytes(chunk_alloc_bytes_immovable);
+static gs_memory_proc_resize_object(chunk_resize_object);
+static gs_memory_proc_free_object(chunk_free_object);
+static gs_memory_proc_stable(chunk_stable);
+static gs_memory_proc_status(chunk_status);
+static gs_memory_proc_free_all(chunk_free_all);
+static gs_memory_proc_consolidate_free(chunk_consolidate_free);
+
+/* Object memory procedures */
+static gs_memory_proc_alloc_bytes(chunk_alloc_bytes);
+static gs_memory_proc_alloc_struct(chunk_alloc_struct);
+static gs_memory_proc_alloc_struct(chunk_alloc_struct_immovable);
+static gs_memory_proc_alloc_byte_array(chunk_alloc_byte_array);
+static gs_memory_proc_alloc_byte_array(chunk_alloc_byte_array_immovable);
+static gs_memory_proc_alloc_struct_array(chunk_alloc_struct_array);
+static gs_memory_proc_alloc_struct_array(chunk_alloc_struct_array_immovable);
+static gs_memory_proc_object_size(chunk_object_size);
+static gs_memory_proc_object_type(chunk_object_type);
+static gs_memory_proc_alloc_string(chunk_alloc_string);
+static gs_memory_proc_alloc_string(chunk_alloc_string_immovable);
+static gs_memory_proc_resize_string(chunk_resize_string);
+static gs_memory_proc_free_string(chunk_free_string);
+static gs_memory_proc_register_root(chunk_register_root);
+static gs_memory_proc_unregister_root(chunk_unregister_root);
+static gs_memory_proc_enable_free(chunk_enable_free);
+static const gs_memory_procs_t chunk_procs =
+{
+ /* Raw memory procedures */
+ chunk_alloc_bytes_immovable,
+ chunk_resize_object,
+ chunk_free_object,
+ chunk_stable,
+ chunk_status,
+ chunk_free_all,
+ chunk_consolidate_free,
+ /* Object memory procedures */
+ chunk_alloc_bytes,
+ chunk_alloc_struct,
+ chunk_alloc_struct_immovable,
+ chunk_alloc_byte_array,
+ chunk_alloc_byte_array_immovable,
+ chunk_alloc_struct_array,
+ chunk_alloc_struct_array_immovable,
+ chunk_object_size,
+ chunk_object_type,
+ chunk_alloc_string,
+ chunk_alloc_string_immovable,
+ chunk_resize_string,
+ chunk_free_string,
+ chunk_register_root,
+ chunk_unregister_root,
+ chunk_enable_free
+};
+
+typedef struct chunk_obj_node_s {
+ struct chunk_obj_node_s *next;
+ uint size; /* objlist: client size */
+ /* if freelist: size of block (obj header and client area must fit in block) */
+ gs_memory_type_ptr_t type;
+#ifdef DEBUG
+ unsigned long sequence;
+#endif
+} chunk_obj_node_t;
+
+/*
+ * Note: All objects within a chunk are 'aligned' since we round_up_to_align
+ * the free list pointer when removing part of a free area.
+ */
+typedef struct chunk_mem_node_s {
+ uint size;
+ uint largest_free; /* quick check when allocating */
+ struct chunk_mem_node_s *next;
+ chunk_obj_node_t *objlist; /* head of objects in this chunk (no order) */
+ chunk_obj_node_t *freelist; /* free list (ordered) */
+ /* chunk data follows immediately */
+} chunk_mem_node_t;
+
+typedef struct gs_memory_chunk_s {
+ gs_memory_common; /* interface outside world sees */
+ gs_memory_t *target; /* base allocator */
+ chunk_mem_node_t *head_chunk;
+#ifdef DEBUG
+ unsigned long sequence_counter;
+#endif
+} gs_memory_chunk_t;
+
+/* ---------- Public constructors/destructors ---------- */
+
+/* Initialize a gs_memory_chunk_t */
+int /* -ve error code or 0 */
+gs_memory_chunk_wrap( gs_memory_t **wrapped, /* chunk allocator init */
+ gs_memory_t * target ) /* base allocator */
+{
+ gs_memory_chunk_t *cmem = (gs_memory_chunk_t *)
+ gs_alloc_bytes_immovable(target, sizeof(gs_memory_chunk_t),
+ "gs_malloc_wrap(chunk)");
+ *wrapped = NULL; /* don't leave garbage in case we fail */
+ if (cmem == 0)
+ return_error(gs_error_VMerror);
+ cmem->stable_memory = (gs_memory_t *)cmem; /* we are stable */
+ cmem->procs = chunk_procs;
+ cmem->gs_lib_ctx = target->gs_lib_ctx;
+ cmem->non_gc_memory = (gs_memory_t *)cmem; /* and are not subject to GC */
+ cmem->target = target;
+ cmem->head_chunk = NULL;
+#ifdef DEBUG
+ cmem->sequence_counter = 0;
+#endif
+
+ /* Init the chunk management values */
+
+ *wrapped = (gs_memory_t *)cmem;
+ return 0;
+}
+
+/* Release a chunk memory manager. */
+/* Note that this has no effect on the target. */
+void
+gs_memory_chunk_release(gs_memory_t *mem)
+{
+ gs_memory_free_all((gs_memory_t *)mem, FREE_ALL_EVERYTHING,
+ "gs_memory_chunk_release");
+}
+
+/* ---------- Accessors ------------- */
+
+/* Retrieve this allocator's target */
+gs_memory_t *
+gs_memory_chunk_target(const gs_memory_t *mem)
+{
+ gs_memory_chunk_t *cmem = (gs_memory_chunk_t *)mem;
+ return cmem->target;
+}
+
+#ifdef DEBUG
+void
+gs_memory_chunk_dump_memory(const gs_memory_t *mem)
+{
+ gs_memory_chunk_t *cmem = (gs_memory_chunk_t *)mem;
+ chunk_mem_node_t *head = cmem->head_chunk;
+ chunk_mem_node_t *current;
+ chunk_mem_node_t *next;
+
+ current = head;
+ while ( current != NULL ) {
+ if (current->objlist != NULL) {
+ chunk_obj_node_t *obj;
+
+ for (obj= current->objlist; obj != NULL; obj=obj->next)
+ dprintf4("chunk_mem leak, obj=0x%lx, size=%d, type=0x%lx, sequence#=%ld\n",
+ (ulong)obj, obj->size, (ulong)(obj->type), obj->sequence);
+ }
+ next = current->next;
+ current = next;
+ }
+}
+#endif
+
+/* -------- Private members --------- */
+
+/* Note that all of the data is 'immovable' and is opaque to the base allocator */
+/* thus even if it is a GC type of allocator, no GC functions will be applied */
+/* All allocations are done in the non_gc_memory of the base */
+
+/* Procedures */
+
+static void
+chunk_mem_node_free_all_remaining(gs_memory_chunk_t *cmem)
+{
+ chunk_mem_node_t *head = cmem->head_chunk;
+ gs_memory_t * const target = cmem->target;
+ chunk_mem_node_t *current;
+ chunk_mem_node_t *next;
+
+ current = head;
+ while ( current != NULL ) {
+ next = current->next;
+ gs_free_object(target, current, "chunk_mem_node_remove");
+ current = next;
+ }
+ cmem->head_chunk = NULL;
+}
+
+static void
+chunk_free_all(gs_memory_t * mem, uint free_mask, client_name_t cname)
+{
+ gs_memory_chunk_t * const cmem = (gs_memory_chunk_t *)mem;
+ gs_memory_t * const target = cmem->target;
+
+ /* Only free the structures and the allocator itself. */
+ if (mem->stable_memory) {
+ if (mem->stable_memory != mem)
+ gs_memory_free_all(mem->stable_memory, free_mask, cname);
+ if (free_mask & FREE_ALL_ALLOCATOR)
+ mem->stable_memory = 0;
+ }
+ if (free_mask & FREE_ALL_DATA) {
+ chunk_mem_node_free_all_remaining(cmem);
+ }
+ if (free_mask & FREE_ALL_STRUCTURES) {
+ cmem->target = 0;
+ }
+ if (free_mask & FREE_ALL_ALLOCATOR)
+ gs_free_object(target, cmem, cname);
+}
+
+extern const gs_memory_struct_type_t st_bytes;
+
+/* round up objects to make sure we have room for a header left */
+inline static uint
+round_up_to_align(uint size)
+{
+ uint num_node_headers = (size + sizeof(chunk_obj_node_t) - 1) / sizeof(chunk_obj_node_t);
+
+ return num_node_headers * sizeof(chunk_obj_node_t);
+}
+
+#define IS_SINGLE_OBJ_SIZE(chunk_size) \
+ (chunk_size > (CHUNK_SIZE>>1))
+#define MULTIPLE_OBJ_CHUNK_SIZE \
+ (sizeof(chunk_mem_node_t) + round_up_to_align(CHUNK_SIZE))
+
+
+/* return -1 on error, 0 on success */
+static int
+chunk_mem_node_add(gs_memory_chunk_t *cmem, uint size_needed, chunk_mem_node_t **newchunk)
+{
+ chunk_mem_node_t *node, *prev_node;
+ gs_memory_t *target = cmem->target;
+ /* Allocate enough for the chunk header, and the size_needed */
+ /* The size needed already includes the object header from caller */
+ /* and is already rounded up to the obj_node_t sized elements */
+ uint chunk_size = size_needed + sizeof(chunk_mem_node_t);
+ bool is_multiple_object_node = false;
+
+ /* Objects > half the default chunk size get their own chunk */
+ if ( ! IS_SINGLE_OBJ_SIZE(chunk_size)) {
+ chunk_size = MULTIPLE_OBJ_CHUNK_SIZE; /* the size for collections of objects */
+ is_multiple_object_node = true;
+ }
+
+ *newchunk = NULL;
+ node = (chunk_mem_node_t *)gs_alloc_bytes_immovable(target, chunk_size, "chunk_mem_node_add");
+ if ( node == NULL )
+ return -1;
+ node->size = chunk_size; /* how much we allocated */
+ node->largest_free = chunk_size - sizeof(chunk_mem_node_t);
+ node->objlist = NULL;
+ node->freelist = (chunk_obj_node_t *)((byte *)(node) + sizeof(chunk_mem_node_t));
+ node->freelist->next = NULL;
+ node->freelist->size = node->largest_free;
+
+ prev_node = NULL;
+ if (!is_multiple_object_node) {
+ chunk_mem_node_t *scan_node;
+
+ /* Scan past chunks that are collections of smaller chunks */
+ /* This allows the most frequently accessed chunks to be near the head */
+ for (scan_node = cmem->head_chunk; scan_node != NULL; scan_node = scan_node->next) {
+ if (scan_node->size != MULTIPLE_OBJ_CHUNK_SIZE)
+ break;
+ prev_node = scan_node;
+ }
+ }
+ if (prev_node == NULL) {
+ if (cmem->head_chunk == NULL) {
+ cmem->head_chunk = node;
+ node->next = NULL;
+ } else {
+ node->next = cmem->head_chunk;
+ cmem->head_chunk = node;
+ }
+ } else {
+ node->next = prev_node->next;
+ prev_node->next = node;
+ }
+
+ *newchunk = node; /* return the chunk we just allocated */
+ return 0;
+}
+
+static int
+chunk_mem_node_remove(gs_memory_chunk_t *cmem, chunk_mem_node_t *addr)
+{
+ chunk_mem_node_t *head = cmem->head_chunk;
+ gs_memory_t * const target = cmem->target;
+
+ /* check the head first */
+ if (head == NULL) {
+ dprintf("FAIL - no nodes to be removed\n" );
+ return -1;
+ }
+ if (head == addr) {
+ cmem->head_chunk = head->next;
+ gs_free_object(target, head, "chunk_mem_node_remove");
+ } else {
+ chunk_mem_node_t *current;
+ bool found = false;
+
+ /* scan the list, stopping in front of element */
+ for (current = head; current != NULL; current = current->next) {
+ if ( current->next && (current->next == addr) ) {
+ current->next = current->next->next; /* de-link it */
+ gs_free_object(target, addr, "chunk_mem_node_remove");
+ found = true;
+ break;
+ }
+ }
+ if ( !found ) {
+ dprintf1("FAIL freeing wild pointer freed address 0x%lx not found\n", (ulong)addr );
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* all of the allocation routines reduce to the this function */
+static byte *
+chunk_obj_alloc(gs_memory_t *mem, uint size, gs_memory_type_ptr_t type, client_name_t cname)
+{
+ gs_memory_chunk_t *cmem = (gs_memory_chunk_t *)mem;
+ chunk_mem_node_t *head = cmem->head_chunk;
+ uint newsize, free_size;
+ chunk_obj_node_t *newobj = NULL;
+ chunk_obj_node_t *free_obj, *prev_free, *new_free;
+ chunk_mem_node_t *current;
+ bool rescan_free_list;
+
+ newsize = round_up_to_align(size + sizeof(chunk_obj_node_t)); /* space we will need */
+
+ /* Search the chunks for one with a large enough free area */
+ for (current = head; current != NULL; current = current->next) {
+ if ( current->largest_free >= newsize)
+ break;
+ }
+ if (current == NULL) {
+ /* No chunks with enough space, allocate one */
+ if (chunk_mem_node_add(cmem, newsize, ¤t) < 0)
+ return NULL;
+ }
+ /* Find the first free area in the current chunk that is big enough */
+ /* LATER: might be better to find the 'best fit' */
+ prev_free = NULL; /* NULL means chunk */
+ for (free_obj = current->freelist; free_obj != NULL; free_obj=free_obj->next) {
+ if (free_obj->size >= newsize)
+ break;
+ prev_free = free_obj; /* keep track so we can update link */
+ }
+
+ if (free_obj == NULL) {
+ dprintf2("largest_free value = %d is too large, cannot find room for size = %d\n",
+ current->largest_free, newsize);
+ return NULL;
+ }
+
+ /* If this free object's size == largest_free, we'll have to re-scan */
+ rescan_free_list = free_obj->size == current->largest_free;
+
+ /* Make an object in the free_obj we found above, reducing it's size */
+ /* and adjusting the free list preserving alignment */
+ newobj = free_obj;
+ free_size = free_obj->size - newsize; /* amount remaining */
+ new_free = (chunk_obj_node_t *)((byte *)(free_obj) + newsize); /* start of remaining free area */
+ if (free_size >= sizeof(chunk_obj_node_t)) {
+ if (prev_free != NULL)
+ prev_free->next = new_free;
+ else
+ current->freelist = new_free;
+ new_free->next = free_obj->next;
+ new_free->size = free_size;
+ } else {
+ /* Not enough space remaining, just skip around it */
+ if (prev_free != NULL)
+ prev_free->next = free_obj->next;
+ else
+ current->freelist = free_obj->next;
+ }
+
+#ifdef DEBUG
+ memset((byte *)(newobj) + sizeof(chunk_obj_node_t), 0xa1, newsize - sizeof(chunk_obj_node_t));
+ memset((byte *)(newobj) + sizeof(chunk_obj_node_t), 0xac, size);
+ newobj->sequence = cmem->sequence_counter++;
+#endif
+
+ newobj->next = current->objlist; /* link to start of list */
+ current->objlist = newobj;
+ newobj->size = size; /* client requested size */
+ newobj->type = type; /* and client desired type */
+
+ /* If we flagged for re-scan to find the new largest_free, do it now */
+ if (rescan_free_list) {
+ current->largest_free = 0;
+ for (free_obj = current->freelist; free_obj != NULL; free_obj=free_obj->next)
+ if (free_obj->size > current->largest_free)
+ current->largest_free = free_obj->size;
+ }
+
+ /* return the client area of the object we allocated */
+ return (byte *)(newobj) + sizeof(chunk_obj_node_t);
+}
+
+static byte *
+chunk_alloc_bytes_immovable(gs_memory_t * mem, uint size, client_name_t cname)
+{
+ return chunk_obj_alloc(mem, size, &st_bytes, cname);
+}
+
+static byte *
+chunk_alloc_bytes(gs_memory_t * mem, uint size, client_name_t cname)
+{
+ return chunk_obj_alloc(mem, size, &st_bytes, cname);
+}
+
+static void *
+chunk_alloc_struct_immovable(gs_memory_t * mem, gs_memory_type_ptr_t pstype,
+ client_name_t cname)
+{
+ return chunk_obj_alloc(mem, pstype->ssize, pstype, cname);
+}
+
+static void *
+chunk_alloc_struct(gs_memory_t * mem, gs_memory_type_ptr_t pstype,
+ client_name_t cname)
+{
+ return chunk_obj_alloc(mem, pstype->ssize, pstype, cname);
+}
+
+static byte *
+chunk_alloc_byte_array_immovable(gs_memory_t * mem, uint num_elements,
+ uint elt_size, client_name_t cname)
+{
+ return chunk_alloc_bytes(mem, num_elements * elt_size, cname);
+}
+
+static byte *
+chunk_alloc_byte_array(gs_memory_t * mem, uint num_elements, uint elt_size,
+ client_name_t cname)
+{
+ return chunk_alloc_bytes(mem, num_elements * elt_size, cname);
+}
+
+static void *
+chunk_alloc_struct_array_immovable(gs_memory_t * mem, uint num_elements,
+ gs_memory_type_ptr_t pstype, client_name_t cname)
+{
+ return chunk_obj_alloc(mem, num_elements * pstype->ssize, pstype, cname);
+}
+
+static void *
+chunk_alloc_struct_array(gs_memory_t * mem, uint num_elements,
+ gs_memory_type_ptr_t pstype, client_name_t cname)
+{
+ return chunk_obj_alloc(mem, num_elements * pstype->ssize, pstype, cname);
+}
+
+
+static void *
+chunk_resize_object(gs_memory_t * mem, void *ptr, uint new_num_elements, client_name_t cname)
+{
+ /* get the type from the old object */
+ chunk_obj_node_t *obj = ((chunk_obj_node_t *)ptr) - 1;
+ uint new_size = (obj->type->ssize * new_num_elements);
+
+ /* This isn't particularly efficient, but it is rarely used */
+ chunk_free_object(mem, ptr, cname);
+ return chunk_obj_alloc(mem, new_size, obj->type, cname);
+}
+
+static void
+chunk_free_object(gs_memory_t * mem, void *ptr, client_name_t cname)
+{
+ if (ptr == NULL )
+ return;
+ {
+ /* back up to obj header */
+ chunk_obj_node_t *obj = ((chunk_obj_node_t *)ptr) - 1;
+ void (*finalize)(void *ptr) = obj->type->finalize;
+ gs_memory_chunk_t * const cmem = (gs_memory_chunk_t *)mem;
+ chunk_mem_node_t *current;
+ chunk_obj_node_t *free_obj, *prev_free;
+ chunk_obj_node_t *scan_obj, *prev_obj;
+ /* space we will free */
+ uint freed_size = round_up_to_align(obj->size + sizeof(chunk_obj_node_t));
+
+ if ( finalize != NULL )
+ finalize(ptr);
+
+ /* Find the chunk containing this object */
+ for (current = cmem->head_chunk; current != NULL; current = current->next) {
+ if (((byte *)obj > (byte *)current) && ((byte *)obj < (byte *)(current) + current->size))
+ break;
+ }
+ if (current == NULL) {
+ /* Object not found in any chunk */
+ dprintf1("chunk_free_obj failed, object 0x%lx not in any chunk\n", ((ulong)obj));
+ return;
+ }
+
+ /* Scan obj list to find this element */
+ prev_obj = NULL; /* object is head, linked to mem node */
+ for (scan_obj = current->objlist; scan_obj != NULL; scan_obj = scan_obj->next) {
+ if (scan_obj == obj)
+ break;
+ prev_obj = scan_obj;
+ }
+ if (scan_obj == NULL) {
+ /* Object not found in expected chunk */
+ dprintf3("chunk_free_obj failed, object 0x%lx not in chunk at 0x%lx, size = %d\n",
+ ((ulong)obj), ((ulong)current), current->size);
+ return;
+ }
+ /* link around the object being freed */
+ if (prev_obj == NULL)
+ current->objlist = obj->next;
+ else
+ prev_obj->next = obj->next;
+
+ /* Add this object's space (including the header) to the free list */
+
+ /* Scan free list to find where this element goes */
+ obj->size = freed_size; /* adjust size to include chunk_obj_node and pad */
+
+ prev_free = NULL;
+ for (free_obj = current->freelist; free_obj != NULL; free_obj = free_obj->next) {
+ if (obj < free_obj)
+ break;
+ prev_free = free_obj;
+ }
+ if (prev_free == NULL) {
+ /* this object is before any other free objects */
+ obj->next = current->freelist;
+ current->freelist = obj;
+ } else {
+ obj->next = free_obj;
+ prev_free->next = obj;
+ }
+ /* If the end of this object is adjacent to the next free space,
+ * merge the two. Next we'll merge with predecessor (prev_free)
+ */
+ if (free_obj != NULL) {
+ byte *after_obj = (byte*)(obj) + freed_size;
+
+ if (free_obj <= (chunk_obj_node_t *)after_obj) {
+ /* Object is adjacent to following free space block -- merge it */
+ obj->next = free_obj->next; /* link around the one being absorbed */
+ obj->size = (byte *)(free_obj) - (byte *)(obj) + free_obj->size;
+ }
+ }
+ /* the prev_free object precedes this object that is now free,
+ * it _may_ be adjacent
+ */
+ if (prev_free != NULL) {
+ byte *after_free = (byte*)(prev_free) + prev_free->size;
+
+ if (obj <= (chunk_obj_node_t *)after_free) {
+ /* Object is adjacent to prior free space block -- merge it */
+ /* NB: this is the common case with LIFO alloc-free patterns */
+ /* (LIFO: Last-allocated, first freed) */
+ prev_free->size = (byte *)(obj) - (byte *)(prev_free) + obj->size;
+ prev_free->next = obj->next; /* link around 'obj' area */
+ obj = prev_free;
+ }
+ }
+#ifdef DEBUG
+memset((byte *)(obj) + sizeof(chunk_obj_node_t), 0xf1, obj->size - sizeof(chunk_obj_node_t));
+#endif
+ if (current->largest_free < obj->size)
+ current->largest_free = obj->size;
+
+ /* If this chunk is now totally empty, free it */
+ if (current->objlist == NULL) {
+ if (current->size != current->freelist->size + sizeof(chunk_mem_node_t))
+ dprintf2("chunk freelist size not correct, is: %d, should be: %d\n",
+ round_up_to_align(current->freelist->size + sizeof(chunk_mem_node_t)), current->size);
+ chunk_mem_node_remove(cmem, current);
+ }
+ }
+}
+
+static byte *
+chunk_alloc_string_immovable(gs_memory_t * mem, uint nbytes, client_name_t cname)
+{
+ /* we just alloc bytes here */
+ return chunk_alloc_bytes(mem, nbytes, cname);
+}
+
+static byte *
+chunk_alloc_string(gs_memory_t * mem, uint nbytes, client_name_t cname)
+{
+ /* we just alloc bytes here */
+ return chunk_alloc_bytes(mem, nbytes, cname);
+}
+
+static byte *
+chunk_resize_string(gs_memory_t * mem, byte * data, uint old_num, uint new_num,
+ client_name_t cname)
+{
+ /* just resize object - ignores old_num */
+ return chunk_resize_object(mem, data, new_num, cname);
+}
+
+static void
+chunk_free_string(gs_memory_t * mem, byte * data, uint nbytes,
+ client_name_t cname)
+{
+ chunk_free_object(mem, data, cname);
+}
+
+static void
+chunk_status(gs_memory_t * mem, gs_memory_status_t * pstat)
+{
+}
+
+static gs_memory_t *
+chunk_stable(gs_memory_t * mem)
+{
+ return mem;
+}
+
+static void
+chunk_enable_free(gs_memory_t * mem, bool enable)
+{
+}
+
+static void
+chunk_consolidate_free(gs_memory_t *mem)
+{
+}
+
+/* aceesors to get size and type given the pointer returned to the client */
+static uint
+chunk_object_size(gs_memory_t * mem, const void *ptr)
+{
+ chunk_obj_node_t *obj = ((chunk_obj_node_t *)ptr) - 1;
+
+ return obj->size;
+}
+
+static gs_memory_type_ptr_t
+chunk_object_type(const gs_memory_t * mem, const void *ptr)
+{
+ chunk_obj_node_t *obj = ((chunk_obj_node_t *)ptr) - 1;
+ return obj->type;
+}
+
+static int
+chunk_register_root(gs_memory_t * mem, gs_gc_root_t * rp, gs_ptr_type_t ptype,
+ void **up, client_name_t cname)
+{
+ return 0;
+}
+
+static void
+chunk_unregister_root(gs_memory_t * mem, gs_gc_root_t * rp, client_name_t cname)
+{
+}
+
+#ifdef DEBUG
+
+#define A(obj, size) \
+ if ((obj = gs_alloc_bytes(cmem, size, "chunk_alloc_unit_test")) == NULL) { \
+ dprintf("chunk alloc failed\n"); \
+ return_error(gs_error_VMerror); \
+ }
+
+#define F(obj) \
+ gs_free_object(cmem, obj, "chunk_alloc_unit_test");
+
+int
+chunk_allocator_unit_test(gs_memory_t *mem)
+{
+ int code;
+ gs_memory_t *cmem;
+ byte *obj1, *obj2, *obj3, *obj4, *obj5, *obj6, *obj7;
+
+ if ((code = gs_memory_chunk_wrap(&cmem, mem )) < 0) {
+ dprintf1("chunk_wrap returned error code: %d\n", code);
+ return code;
+ }
+
+ /* Allocate a large object */
+ A(obj1, 80000);
+ F(obj1);
+ A(obj1, 80000);
+
+ A(obj2, 3);
+ A(obj3, 7);
+ A(obj4, 15);
+ A(obj5, 16);
+ A(obj6, 16);
+ A(obj7, 16);
+
+ F(obj2);
+ F(obj1);
+ F(obj5);
+ F(obj4);
+ F(obj6);
+ F(obj7);
+ F(obj3);
+
+ /* cleanup */
+ gs_memory_chunk_release(cmem);
+ return 0;
+}
+
+#endif /* DEBUG */
Added: trunk/gs/src/gsmchunk.h
===================================================================
--- trunk/gs/src/gsmchunk.h 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gsmchunk.h 2008-05-09 02:18:14 UTC (rev 8721)
@@ -0,0 +1,38 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id:$ */
+/* chunk consolidating wrapper on a base memory allocator */
+
+#define CHUNK_SIZE 65536
+
+/* ---------- Public constructors/destructors ---------- */
+
+/* Initialize a gs_memory_chunk_t */
+ /* -ve error code or 0 */
+int gs_memory_chunk_wrap(gs_memory_t **wrapped, /* chunk allocator init */
+ gs_memory_t * target ); /* base allocator */
+
+/* Release a chunk memory manager and all of the memory it held */
+void gs_memory_chunk_release(gs_memory_t *cmem);
+
+/* ---------- Accessors ------------- */
+
+/* Retrieve this allocator's target */
+gs_memory_t *gs_memory_chunk_target(const gs_memory_t *cmem);
+
+#ifdef DEBUG
+void gs_memory_chunk_dump_memory(const gs_memory_t *mem);
+
+int chunk_allocator_unit_test(gs_memory_t *mem);
+#endif /* DEBUG */
Property changes on: trunk/gs/src/gsmchunk.h
___________________________________________________________________
Name: svn:executable
+ *
Modified: trunk/gs/src/gsmemlok.c
===================================================================
--- trunk/gs/src/gsmemlok.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gsmemlok.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -89,9 +89,9 @@
{
lmem->stable_memory = 0;
lmem->procs = locked_procs;
-
lmem->target = target;
lmem->gs_lib_ctx = target->gs_lib_ctx;
+ lmem->non_gc_memory = (gs_memory_t *)lmem;
/* Allocate a monitor to serialize access to structures within */
lmem->monitor = gx_monitor_alloc(target);
@@ -105,6 +105,7 @@
{
gs_memory_free_all((gs_memory_t *)lmem, FREE_ALL_STRUCTURES,
"gs_memory_locked_release");
+ gx_monitor_free(lmem->monitor);
}
/* ---------- Accessors ------------- */
Modified: trunk/gs/src/gxclist.c
===================================================================
--- trunk/gs/src/gxclist.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclist.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -668,9 +668,12 @@
/* If this is a reader clist, which is about to be reset to a writer,
* free any band_complexity_array memory used by same.
+ * since we have been rendering, shut down threads
*/
- if (!CLIST_IS_WRITER((gx_device_clist *)dev))
- gx_clist_reader_free_band_complexity_array( (gx_device_clist *)dev );
+ if (!CLIST_IS_WRITER((gx_device_clist *)dev)) {
+ gx_clist_reader_free_band_complexity_array( (gx_device_clist *)dev );
+ clist_teardown_render_threads(dev);
+ }
if (flush) {
if (cdev->page_cfile != 0)
@@ -1007,7 +1010,6 @@
{
const gx_band_page_info_t *pinfo = &cdev->common.page_info;
clist_file_ptr pfile = (!select ? pinfo->bfile : pinfo->cfile);
- const char *fname = (!select ? pinfo->bfname : pinfo->cfname);
int code;
code = pinfo->io_procs->ftell(pfile);
Modified: trunk/gs/src/gxclist.h
===================================================================
--- trunk/gs/src/gxclist.h 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclist.h 2008-05-09 02:18:14 UTC (rev 8721)
@@ -213,6 +213,7 @@
clist_writer_cropping_buffer_t, "clist_writer_transparency_buffer",\
clist_writer_cropping_buffer_enum_ptrs, clist_writer_cropping_buffer_reloc_ptrs, next)
+
/* Define the state of a band list when writing. */
typedef struct clist_color_space_s {
byte byte1; /* see cmd_opv_set_color_space in gxclpath.h */
@@ -287,6 +288,11 @@
#define clist_disable_pass_thru_params (1 << 5) /* disable EXCEPT at top of page */
#define clist_disable_copy_alpha (1 << 6) /* target does not support copy_alpha */
+#ifndef clist_render_thread_control_t_DEFINED
+# define clist_render_thread_control_t_DEFINED
+typedef struct clist_render_thread_control_s clist_render_thread_control_t;
+#endif
+
/* Define the state of a band list when reading. */
/* For normal rasterizing, pages and num_pages are both 0. */
typedef struct gx_device_clist_reader_s {
@@ -297,6 +303,11 @@
int num_pages;
gx_band_complexity_t *band_complexity_array; /* num_bands elements */
void *offset_map; /* Just against collecting the map as garbage. */
+ int num_render_threads; /* number of threads being used */
+ clist_render_thread_control_t *render_threads; /* array of threads */
+ byte *main_thread_data; /* saved data pointer of main thread */
+ int curr_render_thread; /* index into array */
+ int thread_lookahead_direction; /* +1 or -1 */
} gx_device_clist_reader;
union gx_device_clist_s {
@@ -414,6 +425,26 @@
/* Put command list data. */
int clist_put_data(const gx_device_clist *cdev, int select, int offset, const byte *buf, int length);
+/* Exports from gxclread used by the multi-threading logic */
+
+/* Initialize for reading. */
+int clist_render_init(gx_device_clist *dev);
+
+int
+clist_close_writer_and_init_reader(gx_device_clist *cldev);
+
+void
+clist_select_render_plane(gx_device *dev, int y, int height,
+ gx_render_plane_t *render_plane, int index);
+
+/* Enable multi threaded rendering. Returns > 0 if supported, < 0 if single threaded */
+int
+clist_enable_multi_thread_render(gx_device *dev);
+
+/* Shutdown render threads and free up the related memory */
+void
+clist_teardown_render_threads(gx_device *dev);
+
#ifdef DEBUG
#define clist_debug_rect clist_debug_rect_imp
void clist_debug_rect_imp(int x, int y, int width, int height);
Modified: trunk/gs/src/gxclmem.c
===================================================================
--- trunk/gs/src/gxclmem.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclmem.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -131,14 +131,14 @@
least as large as the fixed overhead of the compressor plus the
decompressor, plus the expected compressed size of a block that size.
*/
-static const long COMPRESSION_THRESHOLD = 32000000;
+static const long COMPRESSION_THRESHOLD = 500000000; /* 0.5 Gb for host machines */
#define NEED_TO_COMPRESS(f)\
((f)->ok_to_compress && (f)->total_space > COMPRESSION_THRESHOLD)
- /* FOR NOW ALLOCATE 1 raw buffer for every 32 blocks (at least 8) */
-#define GET_NUM_RAW_BUFFERS( f ) \
- max(f->log_length/MEMFILE_DATA_SIZE/32, 8)
+ /* FOR NOW ALLOCATE 1 raw buffer for every 32 blocks (at least 8, no more than 64) */
+#define GET_NUM_RAW_BUFFERS( f ) \
+ min(64, max(f->log_length/MEMFILE_DATA_SIZE/32, 8))
#define MALLOC(f, siz, cname)\
(void *)gs_alloc_bytes((f)->data_memory, siz, cname)
@@ -154,6 +154,7 @@
static int memfile_init_empty(MEMFILE * f);
static int memfile_set_memory_warning(clist_file_ptr cf, int bytes_left);
static int memfile_fclose(clist_file_ptr cf, const char *fname, bool delete);
+static int memfile_get_pdata(MEMFILE * f);
/************************************************/
/* #define DEBUG /- force statistics -/ */
@@ -225,20 +226,103 @@
clist_file_ptr /*MEMFILE * */ * pf,
gs_memory_t *mem, gs_memory_t *data_mem, bool ok_to_compress)
{
- MEMFILE *f = 0;
+ MEMFILE *f = NULL;
int code = 0;
- /* We don't implement reopening an existing file. */
- if (fname[0] != 0 || fmode[0] != 'w') {
- code = gs_note_error(gs_error_invalidfileaccess);
- goto finish;
- }
+ *pf = NULL; /* in case we have an error */
- /* There is no need to set fname in this implementation, */
- /* but we do it anyway. */
- fname[0] = (ok_to_compress ? 'a' : 'b');
- fname[1] = 0;
+ /* fname[0] == 0 if this is not reopening */
+ /* memfile file names begin with a flag byte == 0xff */
+ if (fname[0] == '\377' || fmode[0] == 'r') {
+ MEMFILE *base_f = NULL;
+ /* reopening an existing file. */
+ code = sscanf(fname+1, "0x%x", &base_f);
+ if (code != 1) {
+ gs_note_error(gs_error_ioerror);
+ goto finish;
+ }
+ if (fmode[0] == 'w') {
+ /* Reopen an existing file for 'write' */
+ /* Check first to make sure that we have exclusive access */
+ if (base_f->openlist != NULL) {
+ code = gs_note_error(gs_error_ioerror);
+ goto finish;
+ }
+ f = base_f; /* use the file */
+ goto finish;
+ } else {
+ /* Reopen an existing file for 'read' */
+ /* We need to 'clone' this memfile so that each reader instance */
+ /* will be able to maintain it's own 'state' */
+ f = gs_alloc_struct(mem, MEMFILE, &st_MEMFILE,
+ "memfile_fopen_instance(MEMFILE)");
+ if (f == NULL) {
+ eprintf1("memfile_open_scratch(%s): gs_alloc_struct failed\n", fname);
+ code = gs_note_error(gs_error_VMerror);
+ goto finish;
+ }
+ memcpy(f, base_f, sizeof(MEMFILE));
+ f->memory = mem;
+ f->data_memory = data_mem;
+ f->compress_state = 0; /* Not used by reader instance */
+ f->decompress_state = 0; /* make clean for GC, or alloc'n failure */
+ f->reservePhysBlockChain = NULL;
+ f->reservePhysBlockCount = 0;
+ f->reserveLogBlockChain = NULL;
+ f->reserveLogBlockCount = 0;
+ f->openlist = base_f->openlist;
+ base_f->openlist = f; /* link this one in to the base memfile */
+ f->base_memfile = base_f;
+ f->log_curr_pos = 0;
+ f->raw_head = NULL;
+ f->error_code = 0;
+
+ if (f->log_head->phys_blk->data_limit != NULL) {
+ /* The file is compressed, so we need to copy the logical block */
+ /* list so that it is unique to this instance, and initialize */
+ /* the decompressor. */
+ LOG_MEMFILE_BLK *log_block, *new_log_block;
+ int i;
+ int num_log_blocks = (f->log_length + MEMFILE_DATA_SIZE - 1) / MEMFILE_DATA_SIZE;
+ const stream_state *decompress_proto = clist_decompressor_state(NULL);
+ const stream_template *decompress_template = decompress_proto->template;
+
+ new_log_block = MALLOC(f, num_log_blocks * sizeof(LOG_MEMFILE_BLK), "memfile_fopen" );
+ if (new_log_block == NULL)
+ code = gs_note_error(gs_error_VMerror);
+
+ /* copy the logical blocks to the new list just allocated */
+ for (log_block=f->log_head, i=0; log_block != NULL; log_block=log_block->link, i++) {
+ new_log_block[i].phys_blk = log_block->phys_blk;
+ new_log_block[i].phys_pdata = log_block->phys_pdata;
+ new_log_block[i].raw_block = NULL;
+ new_log_block[i].link = log_block->link == NULL ? NULL : new_log_block + i + 1;
+ }
+ f->log_head = new_log_block;
+
+ /* NB: don't need compress_state for reading */
+ f->decompress_state =
+ gs_alloc_struct(mem, stream_state, decompress_template->stype,
+ "memfile_open_scratch(decompress_state)");
+ if (f->decompress_state == 0) {
+ eprintf1("memfile_open_scratch(%s): gs_alloc_struct failed\n", fname);
+ code = gs_note_error(gs_error_VMerror);
+ goto finish;
+ }
+ memcpy(f->decompress_state, decompress_proto,
+ gs_struct_type_size(decompress_template->stype));
+ f->decompress_state->memory = mem;
+ if (decompress_template->set_defaults)
+ (*decompress_template->set_defaults) (f->decompress_state);
+ }
+ f->log_curr_blk = f->log_head;
+ memfile_get_pdata(f); /* set up the initial block */
+
+ goto finish;
+ }
+ }
+ fname[0] = 0; /* no file name yet */
f = gs_alloc_struct(mem, MEMFILE, &st_MEMFILE,
"memfile_open_scratch(MEMFILE)");
if (f == NULL) {
@@ -251,6 +335,8 @@
/* init an empty file, BEFORE allocating de/compress state */
f->compress_state = 0; /* make clean for GC, or alloc'n failure */
f->decompress_state = 0;
+ f->openlist = NULL;
+ f->base_memfile = NULL;
f->total_space = 0;
f->reservePhysBlockChain = NULL;
f->reservePhysBlockCount = 0;
@@ -298,16 +384,16 @@
}
f->total_space = 0;
+ /* Return the address of this memfile as a string for use in future clist_fopen calls */
+ fname[0] = 0xff; /* a flag that this is a memfile name */
+ sprintf(fname+1, "0x%0x", f);
+
#ifdef DEBUG
- /* If this is the start, init some statistics. */
- /* Hack: we know the 'a' file is opened first. */
- if (*fname == 'a') {
tot_compressed = 0;
tot_raw = 0;
tot_cache_miss = 0;
tot_cache_hits = 0;
tot_swap_out = 0;
- }
#endif
finish:
if (code < 0) {
@@ -326,9 +412,64 @@
{
MEMFILE *const f = (MEMFILE *)cf;
- /* We don't implement closing without deletion. */
- if (!delete)
- return_error(gs_error_invalidfileaccess);
+ if (!delete) {
+ if (f->base_memfile) {
+ MEMFILE *prev_f;
+
+ /* Here we need to delete this instance from the 'openlist' */
+ /* in case this file was opened for 'read' on a previously */
+ /* written file (base_memfile != NULL) */
+ for (prev_f = f->base_memfile; prev_f != NULL; prev_f = prev_f->openlist)
+ if (prev_f->openlist == f)
+ break;
+ if (prev_f == NULL) {
+ eprintf1("Could not find 0x%x on memfile openlist\n", ((unsigned int)f));
+ return_error(gs_error_invalidfileaccess);
+ }
+ prev_f->openlist = f->openlist; /* link around the one being fclosed */
+ /* Now delete this MEMFILE reader instance */
+ /* NB: we don't delete 'base' instances until we delete */
+ /* If the file is compressed, free the logical blocks, but not */
+ /* the phys_blk info (that is still used by the base memfile */
+ if (f->log_head->phys_blk->data_limit != NULL) {
+ LOG_MEMFILE_BLK *tmpbp, *bp = f->log_head;
+
+ while (bp != NULL) {
+ tmpbp = bp->link;
+ FREE(f, bp, "memfile_free_mem(log_blk)");
+ bp = tmpbp;
+ }
+ f->log_head = NULL;
+
+ /* Free any internal compressor state. */
+ if (f->compressor_initialized) {
+ if (f->decompress_state->template->release != 0)
+ (*f->decompress_state->template->release) (f->decompress_state);
+ if (f->compress_state->template->release != 0)
+ (*f->compress_state->template->release) (f->compress_state);
+ f->compressor_initialized = false;
+ }
+ /* free the raw buffers */
+ while (f->raw_head != NULL) {
+ RAW_BUFFER *tmpraw = f->raw_head->fwd;
+
+ FREE(f, f->raw_head, "memfile_free_mem(raw)");
+ f->raw_head = tmpraw;
+ }
+ }
+ /* deallocate the memfile object proper */
+ gs_free_object(f->memory, f, "memfile_close_and_unlink(MEMFILE)");
+ }
+ return 0;
+ }
+
+ /* If there are open read memfile structures, set them so that */
+ /* future accesses will return errors rather than causing SEGV */
+ if (f->openlist) {
+ /* TODO: do the cleanup rather than just giving an error */
+ eprintf1("Attempt to delete a memfile still open for read: 0x%0x\n", ((unsigned int)f));
+ return_error(gs_error_invalidfileaccess);
+ }
memfile_free_mem(f);
/* Free reserve blocks; don't do it in memfile_free_mem because */
@@ -360,11 +501,14 @@
static int
memfile_unlink(const char *fname)
{
- /*
- * Since we have no way to represent a memfile other than by the
- * pointer, we don't (can't) implement unlinking.
- */
- return_error(gs_error_invalidfileaccess);
+ int code;
+ MEMFILE *f;
+
+ /* memfile file names begin with a flag byte == 0xff */
+ if (fname[0] == '\377' && (code = sscanf(fname+1, "0x%x", &f) == 1)) {
+ return memfile_fclose((clist_file_ptr)f, fname, true);
+ } else
+ return_error(gs_error_invalidfileaccess);
}
/* ---------------- Writing ---------------- */
@@ -546,11 +690,6 @@
code = (*f->compress_state->template->init) (f->compress_state);
if (code < 0)
return_error(gs_error_VMerror); /****** BOGUS ******/
- if (f->decompress_state->template->init != 0)
- code = (*f->decompress_state->template->init)
- (f->decompress_state);
- if (code < 0)
- return_error(gs_error_VMerror); /****** BOGUS ******/
f->compressor_initialized = true;
}
/* Write into the new physical block we just allocated, */
@@ -672,7 +811,7 @@
static int
memfile_get_pdata(MEMFILE * f)
{
- int i, num_raw_buffers, status;
+ int code, i, num_raw_buffers, status;
LOG_MEMFILE_BLK *bp = f->log_curr_blk;
if (bp->phys_blk->data_limit == NULL) {
@@ -685,8 +824,10 @@
else
f->pdata_end = f->pdata + MEMFILE_DATA_SIZE;
} else {
+
/* data was compressed */
if (f->raw_head == NULL) {
+ code = 0;
/* need to allocate the raw buffer pool */
num_raw_buffers = GET_NUM_RAW_BUFFERS(f);
if (f->reservePhysBlockCount) {
@@ -699,8 +840,6 @@
f->reservePhysBlockChain = f->reservePhysBlockChain->link;
--f->reservePhysBlockCount;
} else {
- int code;
-
f->raw_head =
allocateWithReserve(f, sizeof(*f->raw_head), &code,
"memfile raw buffer",
@@ -726,6 +865,12 @@
num_raw_buffers = i + 1; /* if MALLOC failed, then OK */
if_debug1(':', "[:]Number of raw buffers allocated=%d\n",
num_raw_buffers);
+ if (f->decompress_state->template->init != 0)
+ code = (*f->decompress_state->template->init)
+ (f->decompress_state);
+ if (code < 0)
+ return_error(gs_error_VMerror);
+
} /* end allocating the raw buffer pool (first time only) */
if (bp->raw_block == NULL) {
#ifdef DEBUG
@@ -821,7 +966,7 @@
/* is always full size. */
} /* end else (when data was compressed) */
- return (0);
+ return 0;
}
/* ---------------- Reading ---------------- */
@@ -960,47 +1105,7 @@
/* Free up memory that was allocated for the memfile */
bp = f->log_head;
-/******************************************************************
- * The following was the original algorithm here. This algorithm has a bug:
- * the second loop references the physical blocks again after they have been
- * freed.
- ******************************************************************/
-
-#if 0 /**************** *************** */
-
if (bp != NULL) {
- /* Free the physical blocks that make up the compressed data */
- PHYS_MEMFILE_BLK *pphys = (f->log_head)->phys_blk;
-
- if (pphys->data_limit != NULL) {
- /* the data was compressed, free the chain of blocks */
- while (pphys != NULL) {
- PHYS_MEMFILE_BLK *tmpphys = pphys->link;
-
- FREE(f, pphys, "memfile_free_mem(pphys)");
- pphys = tmpphys;
- }
- }
- }
- /* free the logical blocks */
- while (bp != NULL) {
- /* if this logical block was not compressed, free the phys_blk */
- if (bp->phys_blk->data_limit == NULL) {
- FREE(f, bp->phys_blk, "memfile_free_mem(phys_blk)");
- }
- tmpbp = bp->link;
- FREE(f, bp, "memfile_free_mem(log_blk)");
- bp = tmpbp;
- }
-
-#else /**************** *************** */
-# if 1 /**************** *************** */
-
-/****************************************************************
- * This algorithm is correct (we think).
- ****************************************************************/
-
- if (bp != NULL) {
/* Null out phys_blk pointers to compressed data. */
PHYS_MEMFILE_BLK *pphys = bp->phys_blk;
@@ -1030,35 +1135,6 @@
bp = tmpbp;
}
-/***********************************************************************
- * This algorithm appears to be both simpler and free of the bug that
- * occasionally causes the older one to reference freed blocks; but in
- * fact it can miss blocks, because the very last compressed logical block
- * can have spill into a second physical block, which is not referenced by
- * any logical block.
- ***********************************************************************/
-
-# else /**************** *************** */
-
- {
- PHYS_MEMFILE_BLK *prev_phys = 0;
-
- while (bp != NULL) {
- PHYS_MEMFILE_BLK *phys = bp->phys_blk;
-
- if (phys != prev_phys) {
- FREE(f, phys, "memfile_free_mem(phys_blk)");
- prev_phys = phys;
- }
- tmpbp = bp->link;
- FREE(f, bp, "memfile_free_mem(log_blk)");
- bp = tmpbp;
- }
- }
-
-# endif /**************** *************** */
-#endif /**************** *************** */
-
f->log_head = NULL;
/* Free any internal compressor state. */
Modified: trunk/gs/src/gxclmem.h
===================================================================
--- trunk/gs/src/gxclmem.h 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclmem.h 2008-05-09 02:18:14 UTC (rev 8721)
@@ -49,7 +49,7 @@
typedef struct PHYS_MEMFILE_BLK {
struct PHYS_MEMFILE_BLK *link;
char *data_limit; /* end of data when compressed */
- /* NULL if not compressed */
+ /* data_limit is NULL if not compressed */
char data_spare[4]; /* used during de-compress */
char data[MEMFILE_DATA_SIZE];
} PHYS_MEMFILE_BLK;
@@ -65,6 +65,21 @@
gs_memory_t *memory; /* storage allocator */
gs_memory_t *data_memory; /* storage allocator for data */
bool ok_to_compress; /* if true, OK to compress this file */
+ /*
+ * We need to maintain a linked list of other structs that
+ * have beed opened on this 'file'. This allows for a writer
+ * to create a file, close it (but not unlink it), then other
+ * 'opens' of the same file, using the pseudo file-name that
+ * consists of a flag 0xff byte followed by the address as
+ * a 0x____ string.
+ *
+ * When the file is 'unlinked' it must check for other open
+ * 'readers' and set the 'READER INSTANCE' values of the
+ * cloned structures so that calls into 'read' and 'seek'
+ * etc., will be harmless.
+ */
+ struct MEMFILE_s *openlist;
+ struct MEMFILE_s *base_memfile; /* reader instances set this to the file that was written */
/*
* Reserve memory blocks: these are used to guarantee that a
* given-sized write (or sequence of writes) will always succeed.
@@ -87,21 +102,21 @@
int reserveLogBlockCount; /* count of entries on reserveLogBlockChain */
/* logical file properties */
LOG_MEMFILE_BLK *log_head;
- LOG_MEMFILE_BLK *log_curr_blk;
+ LOG_MEMFILE_BLK *log_curr_blk; /******* READER INSTANCE *******/
int64_t log_length; /* updated during write */
- int64_t log_curr_pos; /* updated during seek, close, read */
- char *pdata; /* raw data */
- char *pdata_end;
+ int64_t log_curr_pos; /* updated during seek, close, read */ /******* READER INSTANCE *******/
+ char *pdata; /* raw data */ /******* READER INSTANCE *******/
+ char *pdata_end; /******* READER INSTANCE *******/
/* physical file properties */
int64_t total_space; /* so we know when to start compress */
- PHYS_MEMFILE_BLK *phys_curr; /* NULL if not compressing */
- RAW_BUFFER *raw_head, *raw_tail;
- int error_code; /* used by CLIST_ferror */
- stream_cursor_read rd; /* use .ptr, .limit */
- stream_cursor_write wt; /* use .ptr, .limit */
+ PHYS_MEMFILE_BLK *phys_curr; /* NULL if not compressing */ /******* READER INSTANCE *******/
+ RAW_BUFFER *raw_head, *raw_tail ; /******* READER INSTANCE *******/
+ int error_code; /* used by CLIST_ferror */ /******* READER INSTANCE *******/
+ stream_cursor_read rd; /* use .ptr, .limit */ /******* READER INSTANCE *******/
+ stream_cursor_write wt; /* use .ptr, .limit */ /******* READER INSTANCE *******/
bool compressor_initialized;
stream_state *compress_state;
- stream_state *decompress_state;
+ stream_state *decompress_state; /******* READER INSTANCE *******/
};
#ifndef MEMFILE_DEFINED
#define MEMFILE_DEFINED
Modified: trunk/gs/src/gxclread.c
===================================================================
--- trunk/gs/src/gxclread.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclread.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -283,7 +283,6 @@
/* Forward references */
-static int clist_render_init(gx_device_clist *);
static int clist_rasterize_lines(gx_device *dev, int y, int lineCount,
gx_device *bdev,
const gx_render_plane_t *render_plane,
@@ -299,7 +298,7 @@
}
/* Select full-pixel rendering if required for RasterOp. */
-static void
+void
clist_select_render_plane(gx_device *dev, int y, int height,
gx_render_plane_t *render_plane, int index)
{
@@ -342,7 +341,7 @@
return code;
}
-static int
+int
clist_close_writer_and_init_reader(gx_device_clist *cldev)
{
gx_device_clist_reader * const crdev = &cldev->reader;
@@ -360,7 +359,7 @@
}
/* Initialize for reading. */
-static int
+int
clist_render_init(gx_device_clist *dev)
{
gx_device_clist_reader * const crdev = &dev->reader;
Added: trunk/gs/src/gxclthrd.c
===================================================================
--- trunk/gs/src/gxclthrd.c 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclthrd.c 2008-05-09 02:18:14 UTC (rev 8721)
@@ -0,0 +1,579 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id: gxclthrd1.c 8663 2008-04-25 19:24:16Z ray $ */
+/* Command list - Support for multiple rendering threads */
+#include "memory_.h"
+#include "gx.h"
+#include "gp.h"
+#include "gpcheck.h"
+#include "gxsync.h"
+#include "gserrors.h"
+#include "gxdevice.h"
+#include "gsdevice.h"
+#include "gscoord.h" /* requires gsmatrix.h */
+#include "gxdevmem.h" /* must precede gxcldev.h */
+#include "gdevprn.h" /* must precede gxcldev.h */
+#include "gxcldev.h"
+#include "gxgetbit.h"
+#include "gdevplnx.h"
+#include "gsmemory.h"
+#include "gsmchunk.h"
+#include "gxclthrd.h"
+
+/* Forward reference prototypes */
+static int clist_start_render_thread(gx_device *dev, int thread_index, int band);
+static void clist_render_thread(void *param);
+
+/* Set up and start the render threads */
+static int
+clist_setup_render_threads(gx_device *dev, int y)
+{
+ gx_device_printer *pdev = (gx_device_printer *)dev;
+ gx_device_clist *cldev = (gx_device_clist *)dev;
+ gx_device_clist_common *cdev = (gx_device_clist_common *)cldev;
+ gx_device_clist_reader *crdev = &cldev->reader;
+ gs_memory_t *mem = cdev->bandlist_memory;
+ gx_device *protodev;
+ gs_c_param_list paramlist;
+ int i, code, band;
+ int band_count = cdev->nbands;
+ char fmode[4];
+
+ crdev->num_render_threads = pdev->num_render_threads_requested;
+
+ if(gs_debug[':'] != 0)
+ dprintf1("%% %d rendering threads requested.\n", pdev->num_render_threads_requested);
+
+ if (crdev->num_render_threads > band_count)
+ crdev->num_render_threads = band_count; /* don't bother starting more threads than bands */
+
+ /* Allocate and initialize an array of thread control structures */
+ crdev->render_threads = (clist_render_thread_control_t *)
+ gs_alloc_byte_array(mem, crdev->num_render_threads,
+ sizeof(clist_render_thread_control_t), "clist_setup_render_threads" );
+ /* fallback to non-threaded if allocation fails */
+ if (crdev->render_threads == NULL) {
+ eprintf(" VMerror prevented threads from starting.\n");
+ return_error(gs_error_VMerror);
+ }
+
+
+ memset(crdev->render_threads, 0, crdev->num_render_threads *
+ sizeof(clist_render_thread_control_t));
+ crdev->main_thread_data = cdev->data; /* save data area */
+ /* Based on the line number requested, decide the order of band rendering */
+ if (y == 0) {
+ crdev->thread_lookahead_direction = 1;
+ band = 0;
+ } else {
+ crdev->thread_lookahead_direction = -1;
+ band = band_count;
+ }
+
+ /* Close the files so we can open them in multiple threads */
+ /* TODO: This doesn't work for memfile clist yet, so will fail */
+ if ((code = cdev->page_info.io_procs->fclose(cdev->page_cfile, cdev->page_cfname, false)) < 0 ||
+ (code = cdev->page_info.io_procs->fclose(cdev->page_bfile, cdev->page_bfname, false)) < 0) {
+ gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
+ crdev->render_threads = NULL;
+ eprintf("Closing clist files prevented threads from starting.\n");
+ return_error(gs_error_unknownerror); /* shouldn't happen */
+ }
+ cdev->page_cfile = cdev->page_bfile = NULL;
+ strcpy(fmode, "r"); /* read access for threads */
+ strcat(fmode, gp_fmode_binary_suffix);
+ /* Find the prototype for this device (needed so we can copy from it) */
+ for (i=0; (protodev = (gx_device *)gs_getdevice(i)) != NULL; i++)
+ if (strcmp(protodev->dname, dev->dname) == 0)
+ break;
+ if (protodev == NULL) {
+ eprintf("Could not find prototype device. Rendering threads not started.\n");
+ return gs_error_rangecheck;
+ }
+
+ gs_c_param_list_write(¶mlist, mem);
+ if ((code = gs_getdeviceparams(dev, (gs_param_list *)¶mlist)) < 0) {
+ eprintf1("Error getting device params, code=%d. Rendering threads not started.\n", code);
+ return code;
+ }
+
+ /* Loop creating the devices and semaphores for each thread, then start them */
+ for (i=0; i < crdev->num_render_threads; i++, band += crdev->thread_lookahead_direction) {
+ gx_device *ndev;
+ gx_device_clist *ncldev;
+ gx_device_clist_common *ncdev;
+ clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
+
+ /* Every thread will have a 'chunk allocator' to reduce the interaction
+ * with the 'base' allocator which has 'mutex' (locking) protection.
+ * This improves performance of the threads.
+ */
+ if ((code = gs_memory_chunk_wrap(&(thread->memory), mem )) < 0) {
+ eprintf1("chunk_wrap returned error code: %d\n", code);
+ break;
+ }
+
+ thread->band = -1; /* a value that won't match any valid band */
+ if ((code = gs_copydevice((gx_device **) &ndev, protodev, thread->memory)) < 0) {
+ code = 0; /* even though we failed, no cleanup needed */
+ break;
+ }
+ ncldev = (gx_device_clist *)ndev;
+ ncdev = (gx_device_clist_common *)ndev;
+ gx_device_fill_in_procs(ndev);
+ ((gx_device_printer *)ncdev)->buffer_memory = ncdev->memory =
+ ncdev->bandlist_memory = thread->memory;
+ gs_c_param_list_read(¶mlist);
+ ndev->PageCount = dev->PageCount; /* copy to prevent mismatch error */
+ if ((code = gs_putdeviceparams(ndev, (gs_param_list *)¶mlist)) < 0)
+ break;
+ ncdev->page_uses_transparency = cdev->page_uses_transparency;
+ /* gdev_prn_allocate_memory sets the clist for writing, creating new files.
+ * We need to unlink those files and open the main thread's files, then
+ * reset the clist state for reading/rendering
+ */
+ if ((code = gdev_prn_allocate_memory(ndev, NULL, 0, 0)) < 0)
+ break;
+ thread->cdev = ndev;
+ /* close and unlink the temp files just created */
+ cdev->page_info.io_procs->fclose(ncdev->page_cfile, ncdev->page_cfname, true);
+ cdev->page_info.io_procs->fclose(ncdev->page_bfile, ncdev->page_bfname, true);
+ /* open the main thread's files for this thread */
+ if ((code=cdev->page_info.io_procs->fopen(cdev->page_cfname, fmode, &ncdev->page_cfile,
+ thread->memory, thread->memory, true)) < 0 ||
+ (code=cdev->page_info.io_procs->fopen(cdev->page_bfname, fmode, &ncdev->page_bfile,
+ thread->memory, thread->memory, false)) < 0)
+ break;
+ clist_render_init(ncldev); /* Initialize clist device for reading */
+ ncdev->page_bfile_end_pos = cdev->page_bfile_end_pos;
+
+ /* create the buf device for this thread, and allocate the semaphores */
+ if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
+ &(thread->bdev), cdev->target,
+ band*crdev->page_band_height, NULL,
+ thread->memory, clist_get_band_complexity(dev,y)) < 0))
+ break;
+ if ((thread->sema_this = gx_semaphore_alloc(thread->memory)) == NULL ||
+ (thread->sema_group = gx_semaphore_alloc(thread->memory)) == NULL) {
+ code = gs_error_VMerror;
+ break;
+ }
+ /* Start thread 'i' to do band */
+ if ((code = clist_start_render_thread(dev, i, band)) < 0)
+ break;
+ }
+ gs_c_param_list_release(¶mlist);
+ /* If the code < 0, the last thread creation failed -- clean it up */
+ if (code < 0) {
+ /* the following relies on 'free' ignoring NULL pointers */
+ gx_semaphore_free(crdev->render_threads[i].sema_group);
+ gx_semaphore_free(crdev->render_threads[i].sema_this);
+ if (crdev->render_threads[i].bdev != NULL)
+ cdev->buf_procs.destroy_buf_device(crdev->render_threads[i].bdev);
+ if (crdev->render_threads[i].cdev != NULL) {
+ gx_device_clist_common *thread_cdev = (gx_device_clist_common *)crdev->render_threads[i].cdev;
+
+ /* Close the file handles, but don't delete (unlink) the files */
+ thread_cdev->page_info.io_procs->fclose(thread_cdev->page_bfile, thread_cdev->page_bfname, false);
+ thread_cdev->page_info.io_procs->fclose(thread_cdev->page_cfile, thread_cdev->page_cfname, false);
+ thread_cdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */
+
+ gdev_prn_free_memory((gx_device *)thread_cdev);
+ gs_free_object(crdev->render_threads[i].memory, thread_cdev,
+ "clist_setup_render_threads");
+ }
+ if (crdev->render_threads[i].memory != NULL)
+ gs_memory_chunk_release(crdev->render_threads[i].memory);
+ }
+ /* If we weren't able to create at least one thread, punt */
+ /* Although a single thread isn't any more efficient, the */
+ /* machinery still works, so that's OK. */
+ if (i == 0) {
+ if (crdev->render_threads[0].memory != NULL)
+ gs_memory_chunk_release(crdev->render_threads[0].memory);
+ gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
+ crdev->render_threads = NULL;
+ pdev->num_render_threads_requested = 0; /* shut down thread support */
+ /* restore the file pointers */
+ if (cdev->page_cfile == NULL) {
+ char fmode[4];
+
+ strcpy(fmode, "w+");
+ strcat(fmode, gp_fmode_binary_suffix);
+ cdev->page_info.io_procs->fopen(cdev->page_cfname, fmode, &cdev->page_cfile,
+ mem, cdev->bandlist_memory, true);
+ cdev->page_info.io_procs->fopen(cdev->page_bfname, fmode, &cdev->page_bfile,
+ mem, cdev->bandlist_memory, false);
+ }
+ eprintf1("Rendering threads not started, code=%d.\n", code);
+ return_error(code);
+ }
+ crdev->num_render_threads = i;
+ crdev->curr_render_thread = 0;
+
+ if(gs_debug[':'] != 0)
+ dprintf1("%% Using %d rendering threads\n", i);
+
+ return 0;
+}
+
+void
+clist_teardown_render_threads(gx_device *dev)
+{
+ gx_device_clist *cldev = (gx_device_clist *)dev;
+ gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
+ gx_device_clist_reader *crdev = &cldev->reader;
+ gs_memory_t *mem = cdev->bandlist_memory;
+ int i;
+
+ if (crdev->render_threads != NULL) {
+
+ /* Wait for each thread to finish then free its memory */
+ for (i=0; i < crdev->num_render_threads; i++) {
+ clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
+ gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
+
+ if (thread->status == RENDER_THREAD_BUSY)
+ gx_semaphore_wait(thread->sema_this);
+ /* Free control semaphores */
+ gx_semaphore_free(thread->sema_group);
+ gx_semaphore_free(thread->sema_this);
+ /* destroy the thread's buffer device */
+ thread_cdev->buf_procs.destroy_buf_device(thread->bdev);
+ /*
+ * Free the BufferSpace, close the band files
+ * Note that the BufferSpace is freed using 'ppdev->buf' so the 'data'
+ * pointer doesn't need to be the one that the thread started with
+ */
+ /* Close the file handles, but don't delete (unlink) the files */
+ thread_cdev->page_info.io_procs->fclose(thread_cdev->page_bfile, thread_cdev->page_bfname, false);
+ thread_cdev->page_info.io_procs->fclose(thread_cdev->page_cfile, thread_cdev->page_cfname, false);
+ thread_cdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */
+ gdev_prn_free_memory((gx_device *)thread_cdev);
+ /* Free the device copy this thread used */
+ gs_free_object(thread->memory, thread_cdev, "clist_teardown_render_threads");
+#ifdef DEBUG
+ if (gs_debug[':'])
+ dprintf2("%% Thread %d total usertime=%ld msec\n", i, thread->cputime);
+ dprintf1("\nthread: %d ending memory state...\n", i);
+ gs_memory_chunk_dump_memory(thread->memory);
+ dprintf(" memory dump done.\n");
+#endif
+
+ gs_memory_chunk_release(thread->memory);
+ }
+ cdev->data = crdev->main_thread_data; /* restore the pointer for writing */
+ gs_free_object(mem, crdev->render_threads, "clist_teardown_render_threads");
+ crdev->render_threads = NULL;
+
+ /* Now re-open the clist temp files so we can write to them */
+ if (cdev->page_cfile == NULL) {
+ char fmode[4];
+
+ strcpy(fmode, "w+");
+ strcat(fmode, gp_fmode_binary_suffix);
+ cdev->page_info.io_procs->fopen(cdev->page_cfname, fmode, &cdev->page_cfile,
+ mem, cdev->bandlist_memory, true);
+ cdev->page_info.io_procs->fopen(cdev->page_bfname, fmode, &cdev->page_bfile,
+ mem, cdev->bandlist_memory, false);
+ }
+ }
+}
+
+static int
+clist_start_render_thread(gx_device *dev, int thread_index, int band)
+{
+ gx_device_clist *cldev = (gx_device_clist *)dev;
+ gx_device_clist_reader *crdev = &cldev->reader;
+ int code;
+
+ crdev->render_threads[thread_index].band = band;
+ crdev->render_threads[thread_index].status = RENDER_THREAD_BUSY;
+
+ /* Finally, fire it up */
+ code = gp_create_thread(clist_render_thread, &(crdev->render_threads[thread_index]));
+
+ return code;
+}
+
+static void
+clist_render_thread(void *data)
+{
+ clist_render_thread_control_t *thread = (clist_render_thread_control_t *)data;
+ gx_device *dev = thread->cdev;
+ gx_device_clist *cldev = (gx_device_clist *)dev;
+ gx_device_clist_reader *crdev = &cldev->reader;
+ gx_device *bdev = thread->bdev;
+ gs_int_rect band_rect;
+ byte *mdata = crdev->data + crdev->page_tile_cache_size;
+ uint raster = bitmap_raster(dev->width * dev->color_info.depth);
+ int code;
+ int band_height = crdev->page_band_height;
+ int band = thread->band;
+ int band_begin_line = band * band_height;
+ int band_end_line = band_begin_line + band_height;
+ int band_num_lines;
+#ifdef DEBUG
+ long starttime[2], endtime[2];
+
+ gp_get_usertime(starttime); /* thread start time */
+#endif
+ if (band_end_line > dev->height)
+ band_end_line = dev->height;
+ band_num_lines = band_end_line - band_begin_line;
+
+ code = crdev->buf_procs.setup_buf_device
+ (bdev, mdata, raster, NULL, 0, band_num_lines, band_num_lines);
+ band_rect.p.x = 0;
+ band_rect.p.y = band_begin_line;
+ band_rect.q.x = dev->width;
+ band_rect.q.y = band_end_line;
+ if (code >= 0)
+ code = clist_render_rectangle(cldev, &band_rect, bdev, NULL, true);
+ /* Reset the band boundaries now */
+ crdev->ymin = band_begin_line;
+ crdev->ymax = band_end_line;
+ crdev->offset_map = NULL;
+ if (code < 0)
+ thread->status = code; /* shouldn't happen */
+ else
+ thread->status = RENDER_THREAD_DONE; /* OK */
+
+#ifdef DEBUG
+ gp_get_usertime(endtime);
+ thread->cputime += (endtime[0] - starttime[0]) * 1000 +
+ (endtime[1] - starttime[1]) / 1000000;
+#endif
+ /*
+ * Signal the semaphores. We signal the 'group' first since even if
+ * the waiter is released on the group, it still needs to check
+ * status on the thread
+ */
+ gx_semaphore_signal(thread->sema_group);
+ gx_semaphore_signal(thread->sema_this);
+}
+
+/*
+ * Copy the raster data from the completed thread to the caller's
+ * device (the main thread)
+ * Return 0 if OK, < 0 is the error code from the thread
+ *
+ * After swapping the pointers, start up the completed thread with the
+ * next band remaining to do (if any)
+ */
+static int
+clist_get_band_from_thread(gx_device *dev, int band)
+{
+ gx_device_clist *cldev = (gx_device_clist *)dev;
+ gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
+ gx_device_clist_reader *crdev = &cldev->reader;
+ int next_band, code = 0;
+ int thread_index = crdev->curr_render_thread;
+ clist_render_thread_control_t *thread = &(crdev->render_threads[thread_index]);
+ gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
+ int band_height = crdev->page_info.band_params.BandHeight;
+ int band_count = cdev->nbands;
+ byte *tmp; /* for swapping data areas */
+
+ /* We expect that the thread needed will be the 'current' thread */
+ if (thread->band != band) {
+ /*
+ *TODO: maybe we should search for it, and if not found wait for
+ * and idle thread and start that one
+ */
+ eprintf2("clist_get_band_from_thread: at band %d, needed band %d\n",
+ thread->band, band);
+ return_error(gs_error_rangecheck);
+ }
+ /* Wait for this thread */
+ gx_semaphore_wait(thread->sema_this);
+ if (thread->status < 0)
+ return thread->status; /* FAIL */
+
+ /* Swap the data areas to avoid the copy */
+ tmp = cdev->data;
+ cdev->data = thread_cdev->data;
+ thread_cdev->data = tmp;
+ thread->status = RENDER_THREAD_IDLE; /* the data is no longer valid */
+ thread->band = -1;
+ /* Update the bounds for this band */
+ cdev->ymin = band * band_height;
+ cdev->ymax = cdev->ymin + band_height;
+ if (cdev->ymax > dev->height)
+ cdev->ymax = dev->height;
+
+ /* If we are not at the final band, start up this thread with the next one to do */
+ next_band = band + (crdev->num_render_threads * crdev->thread_lookahead_direction);
+ if (next_band > 0 && next_band < band_count)
+ code = clist_start_render_thread(dev, thread_index, next_band);
+ /* bump the 'curr' to the next thread */
+ crdev->curr_render_thread = crdev->curr_render_thread == crdev->num_render_threads - 1 ?
+ 0 : crdev->curr_render_thread + 1;
+
+ return code;
+}
+
+/* Copy a rasterized rectangle to the client, rasterizing if needed. */
+/* The first invocation starts multiple threads to perform "look ahead" */
+/* rendering adjacent to the first band (forward or backward) */
+static int
+clist_get_bits_rect_mt(gx_device *dev, const gs_int_rect * prect,
+ gs_get_bits_params_t *params, gs_int_rect **unread)
+{
+ gx_device_printer *pdev = (gx_device_printer *)dev;
+ gx_device_clist *cldev = (gx_device_clist *)dev;
+ gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
+ gx_device_clist_reader *crdev = &cldev->reader;
+ gs_memory_t *mem = cdev->bandlist_memory;
+ gs_get_bits_options_t options = params->options;
+ int y = prect->p.y;
+ int end_y = prect->q.y;
+ int line_count = end_y - y;
+ int band_height = crdev->page_info.band_params.BandHeight;
+ int band = y / band_height;
+ gs_int_rect band_rect;
+ int lines_rasterized;
+ gx_device *bdev;
+ byte *mdata;
+ uint raster = bitmap_raster(dev->width * dev->color_info.depth);
+ int my;
+ int code = 0;
+
+ /* This page might not want multiple threads */
+ /* Also we don't support plane extraction using multiple threads */
+ if (pdev->num_render_threads_requested < 1 || (options & GB_SELECT_PLANES))
+ return clist_get_bits_rectangle(dev, prect, params, unread);
+
+ if (prect->p.x < 0 || prect->q.x > dev->width ||
+ y < 0 || end_y > dev->height
+ )
+ return_error(gs_error_rangecheck);
+ if (line_count <= 0 || prect->p.x >= prect->q.x)
+ return 0;
+
+ if((code = clist_close_writer_and_init_reader(cldev)) < 0)
+ return code;
+
+ if (crdev->render_threads == NULL) {
+ if ((code = clist_setup_render_threads(dev, y)) < 0) {
+ /* revert to the default single threaded rendering */
+ return clist_get_bits_rectangle(dev, prect, params, unread);
+ }
+ }
+ /* If we already have the band's data, just return it */
+ if (y < crdev->ymin || end_y > crdev->ymax)
+ code = clist_get_band_from_thread(dev, band);
+ if (code < 0)
+ goto free_thread_out;
+ mdata = crdev->data + crdev->page_tile_cache_size;
+ if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
+ &bdev, cdev->target, y, NULL,
+ mem, clist_get_band_complexity(dev,y))) < 0 ||
+ (code = crdev->buf_procs.setup_buf_device(bdev, mdata, raster, NULL,
+ y - crdev->ymin, line_count, crdev->ymax - crdev->ymin)) < 0)
+ goto free_thread_out;
+
+ lines_rasterized = min(band_height, line_count);
+ /* Return as much of the rectangle as falls within the rasterized lines. */
+ band_rect = *prect;
+ band_rect.p.y = 0;
+ band_rect.q.y = lines_rasterized;
+ code = dev_proc(bdev, get_bits_rectangle)
+ (bdev, &band_rect, params, unread);
+ cdev->buf_procs.destroy_buf_device(bdev);
+ if (code < 0)
+ goto free_thread_out;
+
+ /* Note that if called via 'get_bits', the line count will always be 1 */
+ if (lines_rasterized == line_count) {
+ return code;
+ }
+
+/***** TODO: Handle the below with data from the threads *****/
+ /*
+ * We'll have to return the rectangle in pieces. Force GB_RETURN_COPY
+ * rather than GB_RETURN_POINTER, and require all subsequent pieces to
+ * use the same values as the first piece for all of the other format
+ * options. If copying isn't allowed, or if there are any unread
+ * rectangles, punt.
+ */
+ if (!(options & GB_RETURN_COPY) || code > 0)
+ return gx_default_get_bits_rectangle(dev, prect, params, unread);
+ options = params->options;
+ if (!(options & GB_RETURN_COPY)) {
+ /* Redo the first piece with copying. */
+ params->options = options =
+ (params->options & ~GB_RETURN_ALL) | GB_RETURN_COPY;
+ lines_rasterized = 0;
+ }
+ {
+ gs_get_bits_params_t band_params;
+ uint raster = gx_device_raster(bdev, true);
+
+ code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
+ &bdev, cdev->target, y, NULL,
+ mem, clist_get_band_complexity(dev, y));
+ if (code < 0)
+ return code;
+ band_params = *params;
+ while ((y += lines_rasterized) < end_y) {
+ /* Increment data pointer by lines_rasterized. */
+ if (band_params.data)
+ band_params.data[0] += raster * lines_rasterized;
+ line_count = end_y - y;
+ // code = clist_rasterize_lines(dev, y, line_count, bdev, NULL, &my);
+ if (code < 0)
+ break;
+ lines_rasterized = min(code, line_count);
+ band_rect.p.y = my;
+ band_rect.q.y = my + lines_rasterized;
+ code = dev_proc(bdev, get_bits_rectangle)
+ (bdev, &band_rect, &band_params, unread);
+ if (code < 0)
+ break;
+ params->options = options = band_params.options;
+ if (lines_rasterized == line_count)
+ break;
+ }
+ cdev->buf_procs.destroy_buf_device(bdev);
+ }
+ return code;
+
+/* Free up thread stuff */
+free_thread_out:
+ clist_teardown_render_threads(dev);
+ return code;
+}
+
+static void
+test_threads(void *dummy)
+{
+}
+
+int
+clist_enable_multi_thread_render(gx_device *dev)
+{
+ int code = -1;
+
+ /* We need to test gp_create_thread since we may be on a platform */
+ /* built without working threads, i.e., using gp_nsync.c dummy */
+ /* routines. The nosync gp_create_thread returns a -ve error code */
+ if ((code = gp_create_thread(test_threads, NULL)) < 0 ) {
+ /* TODO: Check for memory based clist files (or fix the memfile) */
+ return code; /* Threads don't work */
+ }
+ set_dev_proc(dev, get_bits_rectangle, clist_get_bits_rect_mt);
+
+ return 1;
+}
Property changes on: trunk/gs/src/gxclthrd.c
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/gs/src/gxclthrd.h
===================================================================
--- trunk/gs/src/gxclthrd.h 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/gxclthrd.h 2008-05-09 02:18:14 UTC (rev 8721)
@@ -0,0 +1,46 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id$ */
+/* Command list multiple rendering threads */
+/* Requires gxsync.h */
+
+#ifndef gxclthrd_INCLUDED
+# define gxcthrd_INCLUDED
+
+#include "gxsync.h"
+
+#define RENDER_THREAD_IDLE 0
+#define RENDER_THREAD_DONE 1
+#define RENDER_THREAD_BUSY 2
+
+#ifndef clist_render_thread_control_t_DEFINED
+# define clist_render_thread_control_t_DEFINED
+typedef struct clist_render_thread_control_s clist_render_thread_control_t;
+#endif
+
+struct clist_render_thread_control_s {
+ int status; /* 0: not started, 1: done, 2: busy, < 0: error */
+ /* values allow waiting until status < 2 */
+ gs_memory_t *memory; /* thread's 'chunk' memory allocator */
+ gx_semaphore_t *sema_this;
+ gx_semaphore_t *sema_group;
+ gx_device *cdev; /* clist device copy */
+ gx_device *bdev; /* this thread's buffer device */
+ int band;
+#ifdef DEBUG
+ ulong cputime;
+#endif
+};
+
+#endif /* gxclthrd_INCLUDED */
Property changes on: trunk/gs/src/gxclthrd.h
___________________________________________________________________
Name: svn:executable
+ *
Modified: trunk/gs/src/lib.mak
===================================================================
--- trunk/gs/src/lib.mak 2008-05-08 21:47:07 UTC (rev 8720)
+++ trunk/gs/src/lib.mak 2008-05-09 02:18:14 UTC (rev 8721)
@@ -118,6 +118,7 @@
gsexit_h=$(GLSRC)gsexit.h
gsgc_h=$(GLSRC)gsgc.h
gsmalloc_h=$(GLSRC)gsmalloc.h
+gsmchunk_h=$(GLSRC)gsmchunk.h
gsmdebug_h=$(GLSRC)gsmdebug.h
gsmemraw_h=$(GLSRC)gsmemraw.h
gsmemory_h=$(GLSRC)gsmemory.h $(gsmemraw_h) $(gstypes_h) $(gslibctx_h)
@@ -129,6 +130,7 @@
gx_h=$(GLSRC)gx.h $(stdio__h) $(gdebug_h)\
$(gserror_h) $(gsio_h) $(gsmemory_h) $(gstypes_h)
gxsync_h=$(GLSRC)gxsync.h $(gpsync_h) $(gsmemory_h)
+gxclthrd_h=$(GLSRC)gxclthrd.h $(GLSRC)gxsync.h $(gpsync_h) $(gsmemory_h)
# Out of order
gsmemlok_h=$(GLSRC)gsmemlok.h $(gsmemory_h) $(gxsync_h)
gsnotify_h=$(GLSRC)gsnotify.h $(gsstype_h)
@@ -174,7 +176,7 @@
$(GLOBJ)gsmalloc.$(OBJ) : $(GLSRC)gsmalloc.c $(malloc__h)\
$(gdebug_h)\
$(gserror_h) $(gserrors_h)\
- $(gsmalloc_h) $(gsmdebug_h) $(gsmemlok_h) $(gsmemret_h)\
+ $(gsmalloc_h) $(gsmdebug_h) $(gsmemret_h) $(gxsync_h)\
$(gsmemory_h) $(gsstruct_h) $(gstypes_h)
$(GLCC) $(GLO_)gsmalloc.$(OBJ) $(C_) $(GLSRC)gsmalloc.c
@@ -222,9 +224,6 @@
# These are required in the standard configuration, because gsmalloc.c
# needs them even if the underlying primitives are dummies.
-$(GLOBJ)gsmemlok.$(OBJ) : $(GLSRC)gsmemlok.c $(GXERR) $(gsmemlok_h)
- $(GLCC) $(GLO_)gsmemlok.$(OBJ) $(C_) $(GLSRC)gsmemlok.c
-
$(GLOBJ)gxsync.$(OBJ) : $(GLSRC)gxsync.c $(GXERR) $(memory__h)\
$(gsmemory_h) $(gxsync_h)
$(GLCC) $(GLO_)gxsync.$(OBJ) $(C_) $(GLSRC)gxsync.c
@@ -253,7 +252,7 @@
$(GLOBJ)gslibctx.$(OBJ) : $(GLSRC)gslibctx.c $(GXERR)\
$(gslibctx_h) $(stdio__h)
$(GLCC) $(GLO_)gslibctx.$(OBJ) $(C_) $(GLSRC)gslibctx.c
-
+
$(GLOBJ)gsnotify.$(OBJ) : $(GLSRC)gsnotify.c $(GXERR)\
$(gsnotify_h) $(gsstruct_h)
$(GLCC) $(GLO_)gsnotify.$(OBJ) $(C_) $(GLSRC)gsnotify.c
@@ -664,8 +663,8 @@
$(GLCC) $(GLO_)gximono.$(OBJ) $(C_) $(GLSRC)gximono.c
$(GLOBJ)gximask.$(OBJ) : $(GLSRC)gximask.c $(GXERR) $(memory__h) $(gserrors_h)\
- $(gsptype2_h) $(gxdevice_h) $(gxdcolor_h) $(gxcpath_h) $(gximask_h) $(gzacpath_h)\
- $(gzcpath_h)
+ $(gsptype1_h) $(gsptype2_h) $(gxdevice_h) $(gxdcolor_h) $(gxcpath_h) $(gximask_h)\
+ $(gzacpath_h) $(gzcpath_h)
$(GLCC) $(GLO_)gximask.$(OBJ) $(C_) $(GLSRC)gximask.c
$(GLOBJ)gxipixel.$(OBJ) : $(GLSRC)gxipixel.c $(GXERR) $(math__h) $(memory__h)\
@@ -1094,7 +1093,7 @@
LIB7s=$(GLOBJ)gsht.$(OBJ) $(GLOBJ)gshtscr.$(OBJ) $(GLOBJ)gswts.$(OBJ)
LIB8s=$(GLOBJ)gsimage.$(OBJ) $(GLOBJ)gsimpath.$(OBJ) $(GLOBJ)gsinit.$(OBJ)
LIB9s=$(GLOBJ)gsiodev.$(OBJ) $(GLOBJ)gsistate.$(OBJ) $(GLOBJ)gsline.$(OBJ)
-LIB10s=$(GLOBJ)gsmalloc.$(OBJ) $(GLOBJ)gsmatrix.$(OBJ) $(GLOBJ)gsmemlok.$(OBJ)
+LIB10s=$(GLOBJ)gsmalloc.$(OBJ) $(GLOBJ)gsmatrix.$(OBJ)
LIB11s=$(GLOBJ)gsmemory.$(OBJ) $(GLOBJ)gsmemret.$(OBJ) $(GLOBJ)gsmisc.$(OBJ) $(GLOBJ)gsnotify.$(OBJ) $(GLOBJ)gslibctx.$(OBJ)
LIB12s=$(GLOBJ)gspaint.$(OBJ) $(GLOBJ)gsparam.$(OBJ) $(GLOBJ)gspath.$(OBJ)
LIB13s=$(GLOBJ)gsserial.$(OBJ) $(GLOBJ)gsstate.$(OBJ) $(GLOBJ)gstext.$(OBJ)\
@@ -1642,7 +1641,8 @@
# gxclrect.c requires rop_proc_table, so we need gsroptab here.
clbase4_=$(GLOBJ)gsroptab.$(OBJ) $(GLOBJ)stream.$(OBJ)
clpath_=$(GLOBJ)gxclimag.$(OBJ) $(GLOBJ)gxclpath.$(OBJ) $(GLOBJ)gxdhtserial.$(OBJ)
-clist_=$(clbase1_) $(clbase2_) $(clbase3_) $(clbase4_) $(clpath_)
+clthread_=$(GLOBJ)gxclthrd.$(OBJ) $(GLOBJ)gsmchunk.$(OBJ)
+clist_=$(clbase1_) $(clbase2_) $(clbase3_) $(clbase4_) $(clpath_) $(clthread_)
# The old code selected one of clmemory, clfile depending on BAND_LIST_STORAGE.
# Now we meed clmemory to be included permanently for large patterns,
@@ -1650,16 +1650,16 @@
# clfile works for page clist iff it is included.
$(GLD)clist.dev : $(LIB_MAK) $(ECHOGS_XE) $(clist_)\
- $(GLD)cl$(BAND_LIST_STORAGE).dev\
- $(GLD)clmemory.dev\
+ $(GLD)cl$(BAND_LIST_STORAGE).dev $(GLD)clmemory.dev $(GLD)$(SYNC).dev\
$(GLD)cfe.dev $(GLD)cfd.dev $(GLD)rle.dev $(GLD)rld.dev $(GLD)psl2cs.dev
$(SETMOD) $(GLD)clist $(clbase1_)
$(ADDMOD) $(GLD)clist -obj $(clbase2_)
$(ADDMOD) $(GLD)clist -obj $(clbase3_)
$(ADDMOD) $(GLD)clist -obj $(clbase4_)
$(ADDMOD) $(GLD)clist -obj $(clpath_)
+ $(ADDMOD) $(GLD)clist -obj $(clthread_)
$(ADDMOD) $(GLD)clist -include $(GLD)cl$(BAND_LIST_STORAGE)
- $(ADDMOD) $(GLD)clist -include $(GLD)clmemory
+ $(ADDMOD) $(GLD)clist -include $(GLD)clmemory $(GLD)$(SYNC).dev
$(ADDMOD) $(GLD)clist -include $(GLD)cfe $(GLD)cfd $(GLD)rle $(GLD)rld $(GLD)psl2cs
$(GLOBJ)gxclist.$(OBJ) : $(GLSRC)gxclist.c $(GXERR) $(memory__h) $(string__h)\
@@ -1678,7 +1678,7 @@
$(GLOBJ)gxclrast.$(OBJ) : $(GLSRC)gxclrast.c $(GXERR)\
$(memory__h) $(gp_h) $(gpcheck_h)\
$(gscdefs_h) $(gsbitops_h) $(gsparams_h) $(gsstate_h)\
- $(gxdcolor_h) $(gxdevice_h)\
+ $(gxdcolor_h) $(gxpcolor_h) $(gxdevice_h)\
$(gsdevice_h) $(gsiparm4_h)\
$(gxdevmem_h) $(gxcldev_h) $(gxclpath_h) $(gxcmap_h)\
$(gxcolor2_h) $(gxcspace_h) $(gxdhtres_h) $(gxgetbit_h)\
@@ -1764,6 +1764,16 @@
$(gsmemory_h) $(gstypes_h) $(gxclmem_h) $(szlibx_h)
$(GLCC) $(GLO_)gxclzlib.$(OBJ) $(C_) $(GLSRC)gxclzlib.c
+# Support for multi-threaded rendering from the clist. The chunk memory wrapper
+# is used to prevent mutex (locking) contention among threads. The underlying
+# memory allocator must implement the mutex (non-gc memory is usually gsmalloc)
+$(GLOBJ)gxclthrd.$(OBJ) : $(GLSRC)gxclthrd.c $(gxclist_h) $(gxsync_h) $(gxclthrd_h)
+ $(GLCC) $(GLO_)gxclthrd.$(OBJ) $(C_) $(GLSRC)gxclthrd.c
+
+$(GLOBJ)gsmchunk.$(OBJ) : $(GLSRC)gsmchunk.c $(gx_h) $(gsstype_h) $(gserrors_h)\
+ $(gsmchunk_h) $(memory__h)
+ $(GLCC) $(GLO_)gsmchunk.$(OBJ) $(C_) $(GLSRC)gsmchunk.c
+
# ---------------- Vector devices ---------------- #
# We include this here for the same reasons as page.dev.
@@ -1907,6 +1917,9 @@
$(gsstruct_h) $(gxdevice_h) $(gxclist_h) $(gxpageq_h)
$(GLCC) $(GLO_)gxpageq.$(OBJ) $(C_) $(GLSRC)gxpageq.c
+$(GLOBJ)gsmemlok.$(OBJ) : $(GLSRC)gsmemlok.c $(GXERR) $(gsmemlok_h)
+ $(GLCC) $(GLO_)gsmemlok.$(OBJ) $(C_) $(GLSRC)gsmemlok.c
+
# ---------------- TrueType and PostScript Type 42 fonts ---------------- #
ttflib_=$(GLOBJ)gstype42.$(OBJ) $(GLOBJ)gxchrout.$(OBJ) \
@@ -2692,31 +2705,31 @@
$(GLCC) $(GLO_)gsiomacres.$(OBJ) $(C_) $(GLSRC)gsiomacres.c
# ---------------- Font API ---------------- #
-
+
# UFST bridge support :
# This stuff dispatches UFST callbacks for a multilianual (PS, PCL) architecture.
-
+
UFST_INC_1=$(I_)$(UFST_ROOT)$(D)sys$(D)inc$(_I) $(I_)$(UFST_ROOT)$(D)rts$(D)inc$(_I) $(I_)$(UFST_ROOT)$(D)rts$(D)tt$(_I)
UFST_INC_=$(UFST_INC_1) $(I_)$(UFST_ROOT)$(D)rts$(D)fco$(_I) $(I_)$(UFST_ROOT)$(D)rts$(D)gray$(_I)
-
+
gxfapiu_h=$(GLSRC)gxfapiu.h $(GLSRC)gp.h
-
+
$(GLD)gxfapiu1.dev : $(LIB_MAK) $(ECHOGS_XE) $(GLOBJ)gxfapiu.$(OBJ)
$(SETMOD) $(GLD)gxfapiu1 $(GLOBJ)gxfapiu.$(OBJ)
-
+
$(GLOBJ)gxfapiu.$(OBJ) : $(GLSRC)gxfapiu.c\
$(gx_h) $(gxfapiu_h)\
$(UFST_ROOT)$(D)rts$(D)inc$(D)cgconfig.h\
$(UFST_ROOT)$(D)rts$(D)inc$(D)shareinc.h\
$(UFST_ROOT)$(D)sys$(D)inc$(D)ufstport.h
$(GLCC) $(UFST_CFLAGS) $(UFST_INC_) $(GLO_)gxfapiu.$(OBJ) $(C_) $(GLSRC)gxfapiu.c
-
-
+
+
# stub for UFST bridge support :
-
+
$(GLD)gxfapiu.dev : $(LIB_MAK) $(ECHOGS_XE)
$(SETMOD) $(GLD)gxfapiu
-
+
# ================ Platform-specific modules ================ #
# Platform-specific code doesn't really belong here: this is code that is
# shared among multiple platforms.
More information about the gs-cvs
mailing list