[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
lynx-dev memory pool for lines and anchors (patch8)
From: |
Leonid Pauzner |
Subject: |
lynx-dev memory pool for lines and anchors (patch8) |
Date: |
Sun, 27 Oct 2002 06:36:47 +0300 (MSK) |
Memory optimization:
* GridText.c: store lines, anchors, and forms in the same HText memory pool
as styles. This will optimize memory allocation/deallocation by 8Kb units.
(The down side - lines in TRST mode will be stored twice.) Some structs
made a bit more compact.
[GridText.c, LYStructs.h]
diff -u -p old/gridtext.c ./gridtext.c
--- old/gridtext.c Wed Oct 23 17:41:22 2002
+++ ./gridtext.c Sun Oct 27 05:58:04 2002
@@ -151,8 +151,6 @@ PUBLIC int LYsb_begin = -1;
PUBLIC int LYsb_end = -1;
#endif
-#if defined(USE_COLOR_STYLE)
-#define MAX_STYLES_ON_LINE 64
/*try to fit in 2 shorts*/
typedef struct {
@@ -161,49 +159,38 @@ typedef struct {
/* horizontal position of this change */
unsigned short style; /* which style to change to */
} HTStyleChange;
-#endif
-typedef struct _line {
- struct _line *next;
- struct _line *prev;
- unsigned offset; /* Implicit initial spaces */
- unsigned size; /* Number of characters */
#if defined(USE_COLOR_STYLE)
- HTStyleChange* styles;
- int numstyles;
+#define MAX_STYLES_ON_LINE 64
+ /* buffers used when current line is being aggregated, in split_line() */
+static HTStyleChange stylechanges_buffers[2][MAX_STYLES_ON_LINE];
#endif
- char data[1]; /* Space for terminator at least! */
-} HTLine;
-#if defined(USE_COLOR_STYLE)
-typedef struct _HTStyleChangePool {
- HTStyleChange data[4092];
- struct _HTStyleChangePool* next;
- int free_items;
-} HTStyleChangePool;
-
-/*these are used when current line is being aggregated. */
-HTStyleChange stylechanges_buffers[2][MAX_STYLES_ON_LINE];
-int stylechanges_buffers_free;/*this is an index of the free buffer.
- Can be 0 or 1*/
-/* These are generic macros for any pools (provided those structures have the
-same members as HTStyleChangePool). Pools are used for allocation of groups of
+typedef struct _HTPool {
+ HTStyleChange data[2042];
+ struct _HTPool* next;
+ int used;
+} HTPool;
+
+/************************************************************************
+These are generic macros for any pools (provided those structures have the
+same members as HTPool). Pools are used for allocation of groups of
objects of the same type T. Pools are represented as a list of structures of
type P (called pool chunks here). Structure P has an array of N objects of
type T named 'data' (the number N in the array can be chosen arbitrary),
-pointer to the next pool chunk named 'pool', and the number of free items in
-that pool chunk named 'free_items'. Here is a definition of the structure P:
+pointer to the next pool chunk named 'next', and the number of used items in
+that pool chunk named 'used'. Here is a definition of the structure P:
struct P
{
T data[N];
struct P* next;
- int free_items;
+ int used;
};
It's recommended that sizeof(P) be memory page size minus 32 in order malloc'd
chunks to fit in machine page size.
- Allocation of 'n' items in the pool is implemented by decrementing member
-'free_items' by 'n' if 'free_items' >= 'n', or allocating a new pool chunk and
+ Allocation of 'n' items in the pool is implemented by incrementing member
+'used' by 'n' if (used+n <= N), or malloc a new pool chunk and
allocating 'n' items in that new chunk. It's the task of the programmer to
assert that 'n' is <= N. Only entire pool may be freed - this limitation makes
allocation algorithms trivial and fast - so the use of pools is limited to
@@ -214,6 +201,9 @@ speed due to the simple algorithms used.
'allocated' in array, alignment overhead is minimal. Allocating strings in a
pool provided their length will never exceed N and is much smaller than N seems
to be very efficient.
+ [Several types of memory-hungry objects are stored in the pool now: styles,
+lines, anchors, and FormInfo. Arrays of HTStyleChange are stored as is,
+other objects are aligned to sizeof(void*) bytes and stored using a cast.]
Pool are referenced by pointer to the chunk that contains free slots. Macros
that allocate memory in pools update that pointer if needed.
@@ -222,7 +212,7 @@ ALLOC_IN_POOL.
Here is a description of those macros as C++ functions (with names mentioned
above and with use of C++ references)
-void ALLOC_IN_POOL( P*& pool, pool_type, int toalloc, T*& ptr)
+void ALLOC_IN_POOL( P*& pool, pool_type, int toalloc, T*& ptr, int align=1)
- allocates 'toalloc' items in the pool of type 'pool_type' pointed by
'pool', sets the pointer 'ptr' to the "allocated" memory and updates 'pool'
if necessary. Sets 'ptr' to NULL if fails.
@@ -231,84 +221,125 @@ void POOL_NEW( pool_type , P*& ptr)
Initializes a pool of type 'pool_type' pointed by 'ptr', updating 'ptr'.
Sets 'ptr' to NULL if fails.
-void POOL_FREE( pool_type , P* ptr)
- Frees a pool of type 'pool_type' pointed by ptr.
+void POOL_FREE( pool_type , P*& ptr)
+ Frees a pool of type 'pool_type' pointed by ptr. Sets ptr to NULL.
- - VH */
+ - VH
+*************************************************************************/
/*
-void ALLOC_IN_POOL( P*& pool, pool_type, int toalloc, T*& ptr)
+void ALLOC_IN_POOL( P*& pool, pool_type, int toalloc, T*& ptr, int align=1)
- allocates 'toalloc' items in the pool of type 'pool_type' pointed by
'pool', sets the pointer 'ptr' to the "allocated" memory and updates 'pool'
if necessary. Sets 'ptr' to NULL if fails.
*/
-#define ALLOC_IN_POOL(pool,pool_type,toalloc,ptr) \
-if (!pool) \
- ptr = NULL; \
-else { \
- if ((pool)->free_items > toalloc) { \
- (pool)->free_items -= toalloc; \
- ptr = (pool)->data + (pool)->free_items; \
- } else { \
- pool_type* newpool = (pool_type*)malloc(sizeof(pool_type)); \
- if (!newpool) { \
- ptr = NULL; \
- } else { \
- newpool->next = pool; \
- newpool->free_items = sizeof newpool->data/ \
- sizeof newpool->data[0] - toalloc; \
- pool = newpool; \
- ptr = newpool->data + sizeof newpool->data/sizeof newpool->data[0]
- toalloc; \
- } \
- } \
+#define ALLOC_IN_POOL(pool,pool_type,toalloc,ptr,align) \
+if (!pool) \
+ ptr = NULL; \
+else { \
+ pool->used += (pool->used%align); \
+ if (TABLESIZE(pool->data) - pool->used >= toalloc) { \
+ ptr = pool->data + pool->used; \
+ pool->used += toalloc; \
+ } else { \
+ pool_type* newpool = (pool_type*)LY_CALLOC(1, sizeof(pool_type)); \
+ if (!newpool) { \
+ ptr = NULL; \
+ } else { \
+ newpool->next = pool; \
+ newpool->used = toalloc; \
+ ptr = newpool->data; \
+ pool = newpool; \
+ } \
+ } \
}
/*
void POOL_NEW( pool_type , P*& ptr)
Initializes a pool of type 'pool_type' pointed by 'ptr', updating 'ptr'.
Sets 'ptr' to NULL if fails.
*/
-#define POOL_NEW(pool_type,ptr) \
- { \
- pool_type* newpool = (pool_type*)malloc(sizeof(pool_type)); \
- if (!newpool) { \
- ptr = NULL; \
- } else { \
- newpool->next = NULL; \
- newpool->free_items = sizeof newpool->data/sizeof newpool->data[0];
\
- ptr = newpool; \
- } \
- }
-/*
-void POOL_FREE( pool_type , P* ptr)
- Frees a pool of type 'pool_type' pointed by ptr.
-*/
-#define POOL_FREE(pool_type,xptr) \
- { \
- pool_type* ptr = xptr; \
- do { \
- pool_type* prevpool = ptr; \
- ptr = ptr->next; \
- FREE(prevpool); \
- } while (ptr); \
- }
+#define POOL_NEW(pool_type,ptr) \
+ { \
+ ptr = (pool_type*)LY_CALLOC(1, sizeof(pool_type)); \
+ if (ptr) { \
+ ptr->next = NULL; \
+ ptr->used = 0; \
+ } \
+ }
+/*
+void POOL_FREE( pool_type, P*& ptr)
+ Frees a pool of type 'pool_type' pointed by ptr. Sets ptr to NULL.
+*/
+#define POOL_FREE(pool_type,ptr) \
+ { \
+ pool_type* cur = ptr; \
+ pool_type* prev; \
+ while (cur) { \
+ prev = cur->next; \
+ FREE(cur); \
+ cur = prev; \
+ } \
+ ptr = NULL; \
+ }
+/**************************************************************************/
+
+#define _sz_ sizeof(HTStyleChange) /* 4 */
+#define _align_ (sizeof(void*)/_sz_) /*64bit OS!*/
+#define _round_(x) (x%_sz_ ? x/_sz_ + 1: x/_sz_)
+
+#define POOLallocstyles(ptr, N) ALLOC_IN_POOL(HTMainText->pool,HTPool,\
+ N, \
+ ptr, \
+ 1)
+#define POOLallocHTLine(ptr, size) { HTStyleChange* _tmp_; \
+ ALLOC_IN_POOL(HTMainText->pool,HTPool,\
+ _round_(LINE_SIZE(size)), \
+ _tmp_, \
+ _align_); \
+ ptr = (HTLine*)_tmp_; \
+ }
+#define POOLtypecalloc(T,ptr) { HTStyleChange* _tmp_; \
+ ALLOC_IN_POOL(HTMainText->pool,HTPool,\
+ _round_(sizeof(T)), \
+ _tmp_, \
+ _align_); \
+ ptr = (T*)_tmp_; \
+ }
+
+typedef struct _line {
+ struct _line *next;
+ struct _line *prev;
+ unsigned short offset; /* Implicit initial spaces */
+ unsigned short size; /* Number of characters */
+#if defined(USE_COLOR_STYLE)
+ HTStyleChange* styles;
+ unsigned short numstyles;
#endif
+ char data[1]; /* Space for terminator at least! */
+} HTLine;
+
#define LINE_SIZE(l) (sizeof(HTLine)+(l)) /* Allow for terminator */
-#define allocHTLine(l) (HTLine *)calloc(1, LINE_SIZE(l))
+
+/* last line buffer, the second is used in split_line(). Not in pool! */
+/* "can't wrap in middle of multibyte sequences, so allocate 2 extra" */
+static char tmp_long_line[2][LINE_SIZE(MAX_LINE+2)]; /* (HTLine*) in fact*/
+
typedef struct _TextAnchor {
struct _TextAnchor * next;
struct _TextAnchor * prev; /* www_user_search only! */
int number; /* For user interface */
- int line_pos; /* Bytes/chars - extent too */
- int extent; /* (see HText_trimHightext) */
int line_num; /* Place in document */
- HiliteList lites;
- int link_type; /* Normal, internal, or form? */
- FormInfo * input_field; /* Info for form links */
+ short line_pos; /* Bytes/chars - extent too */
+ short extent; /* (see HText_trimHightext) */
BOOL show_anchor; /* Show the anchor? */
BOOL inUnderline; /* context is underlined */
BOOL expansion_anch; /* TEXTAREA edit new anchor */
+ char link_type; /* Normal, internal, or form? */
+ FormInfo * input_field; /* Info for form links */
+ HiliteList lites;
+
HTChildAnchor * anchor;
} TextAnchor;
@@ -325,22 +356,7 @@ typedef struct {
*/
struct _HText {
HTParentAnchor * node_anchor;
-#ifdef SOURCE_CACHE
- /*
- * Parse settings when this HText was generated.
- */
- BOOLEAN clickable_images;
- BOOLEAN pseudo_inline_alts;
- BOOLEAN verbose_img;
- BOOLEAN raw_mode;
- BOOLEAN historical_comments;
- BOOLEAN minimal_comments;
- BOOLEAN soft_dquotes;
- int old_dtd;
- int keypad_mode;
- int disp_lines; /* Screen size */
- int disp_cols; /* Used for reports only */
-#endif
+
HTLine * last_line;
int Lines; /* Number of them */
TextAnchor * first_anchor; /* double-linked on demand */
@@ -402,8 +418,24 @@ struct _HText {
HTStream * target; /* Output stream */
HTStreamClass targetClass; /* Output routines */
-#if defined(USE_COLOR_STYLE)
- HTStyleChangePool* styles_pool;
+
+ HTPool* pool; /* this HText memory pool */
+
+#ifdef SOURCE_CACHE
+ /*
+ * Parse settings when this HText was generated.
+ */
+ BOOL clickable_images;
+ BOOL pseudo_inline_alts;
+ BOOL verbose_img;
+ BOOL raw_mode;
+ BOOL historical_comments;
+ BOOL minimal_comments;
+ BOOL soft_dquotes;
+ short old_dtd;
+ short keypad_mode;
+ short disp_lines; /* Screen size */
+ short disp_cols; /* Used for reports only */
#endif
};
@@ -854,17 +886,15 @@ PUBLIC HText * HText_new ARGS1(
#endif /* VMS && VAXC && !__DECC */
}
- line = self->last_line = allocHTLine(MAX_LINE);
- if (line == NULL)
+ POOL_NEW(HTPool, self->pool);
+ if (!self->pool)
outofmem(__FILE__, "HText_New");
+
+ line = self->last_line = (HTLine*)tmp_long_line[0];
line->next = line->prev = line;
line->offset = line->size = 0;
#ifdef USE_COLOR_STYLE
line->numstyles = 0;
- POOL_NEW(HTStyleChangePool,self->styles_pool);
- if (!self->styles_pool)
- outofmem(__FILE__, "HText_New");
- stylechanges_buffers_free = 0;
line->styles = stylechanges_buffers[0];
#endif
self->Lines = 0;
@@ -1035,26 +1065,6 @@ PUBLIC void HText_free ARGS1(
return;
HTAnchor_setDocument(self->node_anchor, (HyperDoc *)0);
-#if defined(USE_COLOR_STYLE)
- POOL_FREE(HTStyleChangePool,self->styles_pool);
-#endif
- while (YES) { /* Free off line array */
- HTLine * l = self->last_line;
- if (l) {
- l->next->prev = l->prev;
- l->prev->next = l->next; /* Unlink l */
- self->last_line = l->prev;
- if (l != self->last_line) {
- FREE(l);
- } else {
- free(l);
- }
- }
- if (l == self->last_line) { /* empty */
- l = self->last_line = NULL;
- break;
- }
- }
while (self->first_anchor) { /* Free off anchor array */
TextAnchor * l = self->first_anchor;
@@ -1105,12 +1115,12 @@ PUBLIC void HText_free ARGS1(
FREE(l->input_field->accept_cs);
- FREE(l->input_field);
+ /*FREE(l->input_field); pool*/
}
LYSetHiText(l, NULL, 0);
- FREE(l);
+ /*FREE(l); pool*/
}
FormList_delete(self->forms);
@@ -1168,6 +1178,7 @@ PUBLIC void HText_free ARGS1(
HTMainAnchor = NULL;
}
+ POOL_FREE(HTPool, self->pool);
FREE(self);
}
@@ -2494,7 +2505,7 @@ PRIVATE void move_anchors_in_region ARGS
* Some necessary changes for anchors starting on this line are also done
* here if needed.
* Returns a newly allocated HTLine* if changes were made
- * (caller has to free the old one).
+ * (lines allocated in pool, caller should not free the old one).
* Returns NULL if no changes needed. (Remove-spaces code may be buggy...)
* - kw
*/
@@ -2530,15 +2541,18 @@ PRIVATE HTLine * insert_blanks_in_line A
if (line->size + added_chars > MAX_LINE - 2)
return NULL;
if (line == text->last_line)
- mod_line = allocHTLine(MAX_LINE);
+ if (line == (HTLine*)tmp_long_line[0])
+ mod_line = (HTLine*)tmp_long_line[1];
+ else
+ mod_line = (HTLine*)tmp_long_line[0];
else
- mod_line = allocHTLine(line->size + added_chars);
+ POOLallocHTLine(mod_line, line->size + added_chars);
if (!mod_line)
return NULL;
if (!prev_anchor)
prev_anchor = text->first_anchor;
head_processed = (prev_anchor && prev_anchor->line_num < line_number);
- memcpy(mod_line, line, LINE_SIZE(1));
+ memcpy(mod_line, line, LINE_SIZE(0));
t = newdata = mod_line->data;
ip = 0;
while (ip <= ninserts) {
@@ -2639,7 +2653,6 @@ PRIVATE void split_line ARGS2(
unsigned, split)
{
HTStyle * style = text->style;
- HTLine * temp;
int spare;
int indent = text->in_line_1 ?
text->style->indent1st : text->style->leftIndent;
@@ -2651,18 +2664,22 @@ PRIVATE void split_line ARGS2(
int TailTrim = 0;
int s, s_post, s_pre, t_underline = underline_on, t_bold = bold_on;
char *p;
- HTLine * previous = text->last_line;
int ctrl_chars_on_previous_line = 0;
int utfxtra_on_previous_line = UTFXTRA_ON_THIS_LINE;
char * cp;
- /* can't wrap in middle of multibyte sequences, so allocate 2 extra */
- HTLine * line = (HTLine *)LY_CALLOC(1, LINE_SIZE(MAX_LINE)+2);
+ HTLine * previous = text->last_line;
+ HTLine * line;
+
/*
- * Make new line.
+ * Set new line.
*/
- if (line == NULL)
- outofmem(__FILE__, "split_line_1");
+ if (previous == (HTLine*)tmp_long_line[0])
+ line = (HTLine*)tmp_long_line[1];
+ else
+ line = (HTLine*)tmp_long_line[0];
+ memset(line, 0, LINE_SIZE(0));
+
ctrl_chars_on_this_line = 0; /*reset since we are going to a new line*/
utfxtra_on_this_line = 0; /*reset too, we'll count them*/
text->LastChar = ' ';
@@ -2854,7 +2871,10 @@ PRIVATE void split_line ARGS2(
#endif
#if defined(USE_COLOR_STYLE)
- line->styles = stylechanges_buffers[stylechanges_buffers_free =
(stylechanges_buffers_free + 1) &1];
+ if (previous->styles == stylechanges_buffers[0])
+ line->styles = stylechanges_buffers[1];
+ else
+ line->styles = stylechanges_buffers[0];
line->numstyles = 0;
{
HTStyleChange *from = previous->styles + previous->numstyles - 1;
@@ -2948,18 +2968,21 @@ PRIVATE void split_line ARGS2(
}
#endif /*USE_COLOR_STYLE*/
- temp = (HTLine *)LY_CALLOC(1, LINE_SIZE(previous->size));
- if (temp == NULL)
+ {
+ HTLine* temp;
+ POOLallocHTLine(temp, previous->size);
+ if (!temp)
outofmem(__FILE__, "split_line_2");
memcpy(temp, previous, LINE_SIZE(previous->size));
#if defined(USE_COLOR_STYLE)
-
ALLOC_IN_POOL((text->styles_pool),HTStyleChangePool,previous->numstyles,temp->styles);
+ POOLallocstyles(temp->styles, previous->numstyles);
if (!temp->styles)
outofmem(__FILE__, "split_line_2");
memcpy(temp->styles, previous->styles,
sizeof(HTStyleChange)*previous->numstyles);
#endif
- FREE(previous);
+ /*FREE(previous); tmp_long_line*/
previous = temp;
+ }
previous->prev->next = previous; /* Link in new line */
previous->next->prev = previous; /* Could be same node of course */
@@ -3243,7 +3266,7 @@ PRIVATE void split_line ARGS2(
previous->next->prev = jline;
previous->prev->next = jline;
- FREE(previous);
+ /*FREE(previous); tmp_long_line*/
previous = jline;
}
@@ -4523,7 +4546,7 @@ PRIVATE int HText_insertBlanksInStblLine
lines_changed++;
if (line == first_line)
first_line = mod_line;
- free(line);
+ /*free(line); POOL*/
line = mod_line;
#ifdef DISP_PARTIAL
/*
@@ -4909,8 +4932,9 @@ PUBLIC int HText_beginAnchor ARGS3(
BOOL, underline,
HTChildAnchor *, anc)
{
- TextAnchor * a = typecalloc(TextAnchor);
+ TextAnchor * a;
+ POOLtypecalloc(TextAnchor, a);
if (a == NULL)
outofmem(__FILE__, "HText_beginAnchor");
a->inUnderline = underline;
@@ -5533,7 +5557,7 @@ PUBLIC void HText_endAppend ARGS1(
*/
next_to_the_last_line->next = line_ptr;
line_ptr->prev = next_to_the_last_line;
- FREE(text->last_line);
+ /*FREE(text->last_line); tmp_long_line*/
text->last_line = next_to_the_last_line;
text->Lines--;
CTRACE((tfp, "GridText: New bottom line: `%s'\n",
@@ -8520,18 +8544,16 @@ PUBLIC void HText_RemovePreviousLine ARG
HText *, text)
{
HTLine *line, *previous;
- char *data;
if (!(text && text->Lines > 1))
return;
line = text->last_line->prev;
- data = line->data;
previous = line->prev;
previous->next = text->last_line;
text->last_line->prev = previous;
text->Lines--;
- FREE(line);
+ /*FREE(line); POOL or tmp_long_line*/
}
/*
@@ -9266,8 +9288,8 @@ PUBLIC int HText_beginInput ARGS3(
BOOL, underline,
InputFieldData *, I)
{
- TextAnchor * a = typecalloc(TextAnchor);
- FormInfo * f = typecalloc(FormInfo);
+ TextAnchor * a;
+ FormInfo * f;
CONST char *cp_option = NULL;
char *IValue = NULL;
unsigned char *tmp = NULL;
@@ -9278,6 +9300,8 @@ PUBLIC int HText_beginInput ARGS3(
CTRACE((tfp, "GridText: Entering HText_beginInput\n"));
+ POOLtypecalloc(TextAnchor, a);
+ POOLtypecalloc(FormInfo, f);
if (a == NULL || f == NULL)
outofmem(__FILE__, "HText_beginInput");
@@ -9491,8 +9515,8 @@ PUBLIC int HText_beginInput ARGS3(
*/
CTRACE((tfp,
"GridText: No name present in input field; not
displaying\n"));
- FREE(a);
- FREE(f);
+ /*FREE(a); pool*/
+ /*FREE(f); pool*/
FREE(IValue);
return(0);
}
@@ -11720,9 +11744,10 @@ PRIVATE void insert_new_textarea_anchor
* Clone and initialize the struct's needed to add a new TEXTAREA
* anchor.
*/
- if (((a = typecalloc(TextAnchor)) == 0) ||
- ((f = typecalloc(FormInfo)) == 0) ||
- ((l = allocHTLine(MAX_LINE)) == 0))
+ POOLallocHTLine(l, MAX_LINE);
+ POOLtypecalloc(TextAnchor, a);
+ POOLtypecalloc(FormInfo, f);
+ if (a == NULL || l == NULL || f == NULL)
outofmem(__FILE__, "insert_new_textarea_anchor");
/* Init all the fields in the new TextAnchor. */
@@ -12543,9 +12568,10 @@ PUBLIC int HText_InsertFile ARGS1(
break;
}
- if (((a = typecalloc(TextAnchor)) == 0) ||
- ((f = typecalloc(FormInfo)) == 0) ||
- ((l = allocHTLine(MAX_LINE)) == 0))
+ POOLallocHTLine(l, MAX_LINE);
+ POOLtypecalloc(TextAnchor, a);
+ POOLtypecalloc(FormInfo, f);
+ if (a == NULL || l == NULL || f == NULL)
outofmem(__FILE__, "HText_InsertFile");
/* Init all the fields in the new TextAnchor. */
diff -u -p old/lystruct.h ./lystruct.h
--- old/lystruct.h Sun Oct 6 17:43:28 2002
+++ ./lystruct.h Sun Oct 27 03:17:18 2002
@@ -6,14 +6,14 @@
#endif /* HTANCHOR_H */
typedef struct {
- int hl_x;
char *hl_text;
+ short hl_x;
} HiliteInfo;
typedef struct {
- int hl_len; /* number of strings in this struct */
- HiliteInfo hl_base;
HiliteInfo *hl_info;
+ HiliteInfo hl_base;
+ short hl_len; /* number of strings in this struct */
} HiliteList;
typedef struct {
; To UNSUBSCRIBE: Send "unsubscribe lynx-dev" to address@hidden
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- lynx-dev memory pool for lines and anchors (patch8),
Leonid Pauzner <=