* GSSkipMutableArray.[hm]: New NSMutableArray subclass.

* GSIndexedSkipList.[hm]: Underlying C implementation.



git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@23633 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Matt Rice 2006-09-27 09:23:25 +00:00
parent dc6f3dda03
commit 05cebfc22b
6 changed files with 775 additions and 1 deletions

View file

@ -1,3 +1,8 @@
2006-09-27 Matt Rice <ratmice@yahoo.com>
* GSSkipMutableArray.[hm]: New NSMutableArray subclass.
* GSIndexedSkipList.[hm]: Underlying C implementation.
2006-06-07 Richard Frith-Macdonald <rfm@gnu.org>
* GSCache.h:

View file

@ -18,18 +18,23 @@ Performance_OBJC_FILES += \
GSCache.m \
GSThroughput.m \
GSTicker.m \
GSIndexedSkipList.m \
GSSkipMutableArray.m \
Performance_HEADER_FILES += \
GSCache.h \
GSThroughput.h \
GSTicker.h \
GSIndexedSkipList.h \
GSSkipMutableArray.h \
Performance_AGSDOC_FILES += \
GSCache.h \
GSThroughput.h \
GSTicker.h
GSTicker.h \
GSSkipMutableArray.h
# Optional Java wrappers for the library

82
GSIndexedSkipList.h Normal file
View file

@ -0,0 +1,82 @@
/**
Copyright (C) 2006 Free Software Foundation, Inc.
Written by: Matt Rice <ratmice@yahoo.com>
Date: 2006
This file is part of the Performance Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include <Foundation/NSZone.h>
#define GSISLMaxNumberOfLevels 16
#define GSISLMaxLevel 15
/*
* attempt at caching the previously looked up index
* to reduce the search time required when wantedIndex > previousIndex
* this didn't seem to provide any benefit, actually negatively impacting
* performance though it was never thoroughly tested it
*/
#define GSISL_CACHE_OPT 0
typedef id GSISLValueType;
typedef struct GSISLNode_t *GSISLNode;
extern GSISLNode GSISLNil;
struct GSISLForward_t
{
unsigned delta;
GSISLNode next;
};
struct GSISLNode_t
{
GSISLValueType value;
struct GSISLForward_t forward[1];
};
typedef struct GSIndexedSkipList
{
int level; /* Maximum level of the list
(1 more than the number of levels in the list) */
GSISLNode header; /* pointer to header */
unsigned count;
NSZone *zone;
#if GSISL_CACHE_OPT
unsigned indexCache[GSISLMaxNumberOfLevels];
GSISLNode nodeCache[GSISLMaxNumberOfLevels];
#endif
} * GSISList;
void GSISLInitialize();
void GSISLFreeList(GSISList l);
GSISList GSISLInitList(NSZone *zone);
void GSISLInsertItemAtIndex(GSISList l,
GSISLValueType value,
unsigned index);
GSISLValueType GSISLItemAtIndex(GSISList l, unsigned index);
GSISLValueType GSISLRemoveItemAtIndex(GSISList l,
unsigned index);
GSISLValueType GSISLReplaceItemAtIndex(GSISList l,
GSISLValueType newVal,
unsigned index);

362
GSIndexedSkipList.m Normal file
View file

@ -0,0 +1,362 @@
/**
Copyright (C) 2006 Free Software Foundation, Inc.
Written by: Matt Rice <ratmice@yahoo.com>
Date: 2006
This file is part of the Performance Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "GSIndexedSkipList.h"
#define PrettyErr(x) do { fprintf(stderr, "%s:%i: %s\n",__FILE__, __LINE__, x); exit(EXIT_FAILURE); } while (0)
GSISLNode GSISLNil;
GSISLNode GSISLNewNodeOfLevel(int l, NSZone *zone)
{
GSISLNode ret = (GSISLNode) NSZoneMalloc(zone, sizeof(struct GSISLNode_t)
+ ((l) * sizeof(struct GSISLForward_t)));
if (ret == NULL)
{
PrettyErr(strerror(errno));
}
do
{
ret->forward[l].delta = 0;
} while (--l >= 0);
return ret;
}
void GSISLInitialize()
{
if (GSISLNil != NULL) return;
GSISLNil = GSISLNewNodeOfLevel(0, NSDefaultMallocZone());
GSISLNil->forward[0].delta = UINT_MAX;
GSISLNil->value = nil;
GSISLNil->forward[0].next = NULL;
}
GSISList GSISLInitList(NSZone *zone)
{
GSISList l;
int i;
l = (GSISList)NSZoneMalloc(zone, sizeof(struct GSIndexedSkipList));
if (l == NULL)
{
PrettyErr(strerror(errno));
}
l->zone = zone;
l->level = 0;
l->count = 0;
l->header = GSISLNewNodeOfLevel(GSISLMaxNumberOfLevels, l->zone);
l->header->value = nil;
for (i=0; i < GSISLMaxNumberOfLevels; i++)
{
l->header->forward[0].delta = 0;
l->header->forward[0].next = GSISLNil;
#if GSISL_CACHE_OPT
l->indexCache[i] = 0;
l->nodeCache[i] = l->header;
#endif
}
return(l);
}
void GSISLFreeList(GSISList l)
{
GSISLNode p,q;
p = l->header;
do
{
q = p->forward[0].next;
NSZoneFree(l->zone,p);
p = q;
} while (p != GSISLNil);
NSZoneFree(l->zone, l);
};
int GSISLRandomLevel()
{
int level = 0;
static int p = RAND_MAX / 4;
while (rand() < p && level < GSISLMaxLevel)
{
level++;
}
return level;
}
void GSISLInsertItemAtIndex(GSISList l, GSISLValueType value,
unsigned index)
{
int k, i;
GSISLNode update[GSISLMaxNumberOfLevels];
unsigned updateIndexes[GSISLMaxNumberOfLevels];
GSISLNode p,q;
unsigned depth;
depth = 0;
k = l->level;
#if GSISL_CACHE_OPT
if (l->indexCache[k] < index)
{
p = l->nodeCache[k];
depth = l->indexCache[k];
}
else
{
p = l->header;
}
#else
p = l->header;
#endif
do
{
while (q = p->forward[k].next,
q != GSISLNil && depth + p->forward[k].delta < index + 1)
{
depth += p->forward[k].delta;
p = q;
}
updateIndexes[k] = depth;
update[k] = p;
} while(--k >= 0);
k = GSISLRandomLevel();
q = GSISLNewNodeOfLevel(k, l->zone);
if (k > l->level)
{
/* we are creating a new level that looks like
* header ---> new node ---> tail
*/
k = l->level;
l->level++;
#if GSISL_CACHE_OPT
l->nodeCache[l->level] = l->header;
l->indexCache[l->level] = 0;
#endif
l->header->forward[l->level].delta = index + 1;
l->header->forward[l->level].next = q;
q->forward[l->level].delta = 0;
q->forward[l->level].next = GSISLNil;
}
else
{
/* if there are higher nodes than this nodes level.
increment the deltas in the update, as we are inserting
a node inbetween their starting point and their ending
*/
for (i = k + 1; i <= l->level; i++)
{
if (update[i]->forward[i].delta != 0)
update[i]->forward[i].delta++;
#if GSISL_CACHE_OPT
l->nodeCache[i] = update[i];
l->indexCache[i] = updateIndexes[i];
#endif
}
}
q->value = value;
do
{
/* update from the nodes highest level down to level 0
* on all the levels already existing in the list
*/
p = update[k];
if (p->forward[k].delta)
q->forward[k].delta = updateIndexes[k] + p->forward[k].delta - depth;
p->forward[k].delta = depth + 1 - updateIndexes[k];
q->forward[k].next = p->forward[k].next;
p->forward[k].next = q;
#if GSISL_CACHE_OPT
l->indexCache[k] = updateIndexes[k];
l->nodeCache[k] = update[k];
#endif
} while(--k >= 0);
l->count++;
}
GSISLValueType GSISLRemoveItemAtIndex(GSISList l, unsigned index)
{
int k,m;
GSISLNode update[GSISLMaxNumberOfLevels];
unsigned updateIndexes[GSISLMaxNumberOfLevels];
GSISLNode p,q;
unsigned depth = 0;
GSISLValueType ret;
k = m = l->level;
#if GSISL_CACHE_OPT
if (l->indexCache[k] < index)
{
p = l->nodeCache[k];
depth = l->indexCache[k];
}
else
{
p = l->header;
}
#else
p = l->header;
#endif
do
{
while (q = p->forward[k].next,
q != GSISLNil && depth + p->forward[k].delta < index + 1)
{
depth += p->forward[k].delta;
p = q;
}
update[k] = p;
updateIndexes[k] = depth;
} while(--k >= 0);
for (k = 0; k <= m; k++)
{
p = update[k];
#if GSISL_CACHE_OPT
l->indexCache[k] = updateIndexes[k];
l->nodeCache[k] = update[k];
#endif
if (p->forward[k].next == q)
{
p->forward[k].delta = (q->forward[k].next == GSISLNil)
? 0
: p->forward[k].delta + q->forward[k].delta - 1;
p->forward[k].next = q->forward[k].next;
}
else if (p->forward[k].next != GSISLNil)
{
p->forward[k].delta--;
}
else
{
p->forward[k].delta = 0;
}
}
ret = q->value;
NSZoneFree(l->zone,q);
/* if header points to nil, decrement the list level */
while (l->header->forward[m].next == GSISLNil && m > 0 )
{
l->header->forward[m].delta = 0;
m--;
}
l->level = m;
l->count--;
return ret;
}
GSISLValueType GSISLItemAtIndex(GSISList l, unsigned index)
{
int k;
unsigned depth = 0;
GSISLNode p,q;
k = l->level;
#if GSISL_CACHE_OPT
if (l->indexCache[k] < index)
{
p = l->nodeCache[k];
depth = l->indexCache[k];
}
else
{
p = l->header;
}
#else
p = l->header;
#endif
do
{
while (q = p->forward[k].next, q != GSISLNil && depth + p->forward[k].delta < index + 1)
{
depth += p->forward[k].delta;
p = q;
}
#if GSISL_CACHE_OPT
l->nodeCache[k] = p;
l->indexCache[k] = depth;
#endif
} while(--k >= 0);
return(q->value);
}
GSISLValueType GSISLReplaceItemAtIndex(GSISList l, GSISLValueType newVal, unsigned index)
{
int k;
unsigned depth = 0;
GSISLNode p,q;
GSISLValueType ret;
k = l->level;
#if GSISL_CACHE_OPT
if (l->indexCache[k] < index)
{
p = l->nodeCache[k];
depth = l->indexCache[k];
}
else
{
p = l->header;
}
#else
p = l->header;
#endif
do
{
while (q = p->forward[k].next,
q != GSISLNil && depth + p->forward[k].delta < index + 1)
{
depth += p->forward[k].delta;
p = q;
}
#if GSISL_CACHE_OPT
l->indexCache[k] = depth;
l->nodeCache[k] = p;
#endif
} while(--k >= 0);
ret = q->value;
q->value = newVal;
return ret;
}

69
GSSkipMutableArray.h Normal file
View file

@ -0,0 +1,69 @@
/**
Copyright (C) 2006 Free Software Foundation, Inc.
Written by: Matt Rice <ratmice@yahoo.com>
Date: 2006
This file is part of the Performance Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include <Foundation/NSArray.h>
#include <GSIndexedSkipList.h>
/**
<p>A NSMutableArray subclass which uses a skip list variant for it's underlying
data structure.</p>
<p>while a skip list is typically sorted and represents a dictionary.
the indexed skip list is sorted by index and maintains deltas to represent
the distance between linked nodes.</p>
<p><code>the underlying data structure looks much like the figure below:<br/>
index -> HEAD 1 2 3 4 5 6 TAIL<br/>
5| ---------------------> # ------> #<br/>
3| -----------> 2 ------> # ------> #<br/>
1| -> 1 -> 1 -> 1 -> 1 -> 1 -> # -> #<br/></code></p>
<p>where the numbers represent how many indexes it is to the next node
of the appropriate level. The bottom level always points to the next node.</p>
<p>finding a specific index starts at the top level, until the current
depth + the next nodes delta is larger than wanted index, then it goes down
1 level, and repeats until it finds the wanted index.</p>
<p>addition and removal of indexes requires an update of the deltas of nodes
which begin before, and end after the wanted index,
these are the places where it goes down a level.</p>
<p>the rationale behind it was where a linked list based mutable array will
quickly add and remove elements, it may perform poorly at accessing any
random index (because it must traverse the entire list to get to the index).</p>
<p>and while a c array based mutable array will perform good at random index
access it may perform poorly at adding and removing indexes
(because it must move all items after the altered index).</p>
<p>so while a SkipMutableArray may not outperform a linked list or a c array
mutable array at their specific strengths, it attempts to not suffer from
either of their weaknesses, at the cost of additional memory overhead..</p>
*/
@interface SkipMutableArray : NSMutableArray
{
GSISList l;
}
@end

251
GSSkipMutableArray.m Normal file
View file

@ -0,0 +1,251 @@
/**
Copyright (C) 2006 Free Software Foundation, Inc.
Written by: Matt Rice <ratmice@yahoo.com>
Date: 2006
This file is part of the Performance Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include "GSSkipMutableArray.h"
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSEnumerator.h>
@interface SkipMutableArray(Private)
- (GSISList) _list;
@end
@implementation SkipMutableArray(Private)
- (GSISList) _list
{
return l;
}
@end
@interface SkipMutableArrayEnumerator : NSEnumerator
{
GSISLNode node;
}
@end
@implementation SkipMutableArrayEnumerator
- (id) initWithArray:(NSArray *)arr
{
if (![arr isKindOfClass:[SkipMutableArray class]])
{
[[NSException exceptionWithName:NSInternalInconsistencyException reason:@"not a SkipMutableArray" userInfo:nil] raise];
}
self = [super init];
node = [(SkipMutableArray *)arr _list]->header->forward[0].next;
return self;
}
- (id) nextObject
{
id foo = node->value;
if (node == GSISLNil)
return nil;
node = node->forward[0].next;
return foo;
}
@end
@implementation SkipMutableArray : NSMutableArray
- (void) _raiseRangeExceptionWithIndex: (unsigned)index from: (SEL)sel
{
NSDictionary *info;
NSException *exception;
NSString *reason;
info = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt: index], @"Index",
[NSNumber numberWithUnsignedInt: l->count], @"Count",
self, @"Array", nil, nil];
reason = [NSString stringWithFormat: @"Index %d is out of range %d (in '%@')", index, l->count, NSStringFromSelector(sel)];
exception = [NSException exceptionWithName: NSRangeException
reason: reason
userInfo: info];
[exception raise];
}
+ (void) initialize
{
GSISLInitialize();
}
- (id) initWithObjects:(id *)objects count:(unsigned) count
{
int i;
self = [super init];
if (!self) return nil;
l = GSISLInitList([self zone]);
for (i = 0; i < count; i++)
{
GSISLInsertItemAtIndex(l, RETAIN(objects[i]), i);
}
return self;
}
- (id) init
{
self = [super init];
if (!self) return nil;
l = GSISLInitList([self zone]);
return self;
}
- (void) dealloc
{
GSISLNode p,q;
p = l->header->forward[0].next;
do
{
q = p->forward[0].next;
RELEASE(p->value);
NSZoneFree(l->zone,p);
p = q;
} while (p != GSISLNil);
NSZoneFree(l->zone, l->header);
NSZoneFree(l->zone, l);
}
- (void) insertObject:(id)object atIndex:(unsigned)index
{
if (index > l->count)
{
[self _raiseRangeExceptionWithIndex: index from: _cmd];
}
GSISLInsertItemAtIndex(l, RETAIN(object), index);
}
- (id) objectAtIndex:(unsigned)index
{
if (index >= l->count)
{
[self _raiseRangeExceptionWithIndex: index from: _cmd];
}
return GSISLItemAtIndex(l, index);
}
- (void) removeObjectAtIndex:(unsigned) index
{
if (index >= l->count)
{
[self _raiseRangeExceptionWithIndex: index from: _cmd];
}
RELEASE(GSISLRemoveItemAtIndex(l, index));
}
- (void) addObject:(id)obj
{
GSISLInsertItemAtIndex(l, RETAIN(obj), l->count);
}
- (unsigned) count
{
return l->count;
}
- (void) replaceObjectAtIndex:(unsigned)index withObject:(id)obj
{
RELEASE(GSISLReplaceItemAtIndex(l, RETAIN(obj), index));
}
- (NSEnumerator*) objectEnumerator
{
id e;
e = [SkipMutableArrayEnumerator allocWithZone: NSDefaultMallocZone()];
e = [e initWithArray: self];
return AUTORELEASE(e);
}
/* returns an in an NSString suitable for running through graphviz,
* with the graph named 'graphName'
*/
- (NSString *) _makeGraphOfInternalLayoutNamed:(NSString *)graphName
{
GSISLNode p;
unsigned k, i;
p = l->header;
k = l->level;
NSMutableString *graph = [[NSMutableString alloc] init];
[graph appendString:[NSString stringWithFormat:@"digraph %@ {\n", graphName]];
[graph appendString:@"graph [rankdir = LR];\n"];
[graph appendString:@"node [shape = record];\n"];
NSMutableDictionary *values = [[NSMutableDictionary alloc] init];
NSMutableArray *edges = [[NSMutableArray alloc] init];
NSArray *tmp;
[values setObject:[NSMutableString stringWithFormat:@"\"%p\" [label = \"%p (NIL) |{ <delta0> 0 | <forward0> }",GSISLNil, GSISLNil] forKey:[NSString stringWithFormat:@"%p", GSISLNil]];
for (k = 0; k < l->level + 1; k++)
{
for (p = l->header; p != GSISLNil; p = p->forward[k].next)
{
NSString *value = [NSString stringWithFormat:@"%p", p];
NSMutableString *foo = [values objectForKey:value];
if (foo == nil)
{
foo = [[NSMutableString alloc] init];
[foo appendString:[NSString stringWithFormat:@"\"%p\" [label = \"%p%@ |{ <delta%i> %i | <forward%i> }", p, p, p == l->header ? @"(HEADER)" : @"", k, p->forward[k].delta, k]];
if (p != GSISLNil)
[edges addObject:[NSString stringWithFormat:@"\"%p\":forward%i -> \"%p\":delta%i;\n",p,k, p->forward[k].next,p->forward[k].next == GSISLNil ? 0 : k]];
[values setObject:foo forKey:value];
RELEASE(foo);
}
else
{
[foo appendString:[NSString stringWithFormat:@"|{ <delta%i> %i | <forward%i> }", k, p->forward[k].delta, k]];
if (p != GSISLNil)
[edges addObject:[NSString stringWithFormat:@"\"%p\":forward%i -> \"%p\":delta%i;\n",p,k, p->forward[k].next, p->forward[k].next == GSISLNil ? 0 : k]];
[values setObject:foo forKey:value];
}
}
}
tmp = [values allKeys];
for (i = 0; i < [tmp count]; i++)
{
[graph appendString:[values objectForKey:[tmp objectAtIndex:i]]];
[graph appendString:@"\"];\n"];
}
for (i = 0; i < [edges count]; i++)
{
[graph appendString:[edges objectAtIndex:i]];
}
[graph appendString:@"}\n"];
RELEASE(values);
RELEASE(edges);
return AUTORELEASE(graph);
}
@end