diff --git a/ChangeLog b/ChangeLog index 85aa68e..c331cce 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2006-09-27 Matt Rice + + * GSSkipMutableArray.[hm]: New NSMutableArray subclass. + * GSIndexedSkipList.[hm]: Underlying C implementation. + 2006-06-07 Richard Frith-Macdonald * GSCache.h: diff --git a/GNUmakefile b/GNUmakefile index 9d8e2ec..5187ca7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -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 diff --git a/GSIndexedSkipList.h b/GSIndexedSkipList.h new file mode 100644 index 0000000..ceae82e --- /dev/null +++ b/GSIndexedSkipList.h @@ -0,0 +1,82 @@ +/** + Copyright (C) 2006 Free Software Foundation, Inc. + + Written by: Matt Rice + 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 +#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); + diff --git a/GSIndexedSkipList.m b/GSIndexedSkipList.m new file mode 100644 index 0000000..dacff69 --- /dev/null +++ b/GSIndexedSkipList.m @@ -0,0 +1,362 @@ +/** + Copyright (C) 2006 Free Software Foundation, Inc. + + Written by: Matt Rice + 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 +#include +#include +#include +#include +#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; +} diff --git a/GSSkipMutableArray.h b/GSSkipMutableArray.h new file mode 100644 index 0000000..b72fc8a --- /dev/null +++ b/GSSkipMutableArray.h @@ -0,0 +1,69 @@ +/** + Copyright (C) 2006 Free Software Foundation, Inc. + + Written by: Matt Rice + 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 +#include + +/** +

A NSMutableArray subclass which uses a skip list variant for it's underlying + data structure.

+ +

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.

+ +

the underlying data structure looks much like the figure below:
+index -> HEAD 1 2 3 4 5 6 TAIL
+ 5| ---------------------> # ------> #
+ 3| -----------> 2 ------> # ------> #
+ 1| -> 1 -> 1 -> 1 -> 1 -> 1 -> # -> #

+ +

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.

+ +

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.

+ +

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.

+ +

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).

+ +

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).

+ +

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..

+ */ +@interface SkipMutableArray : NSMutableArray +{ + GSISList l; +} + +@end + diff --git a/GSSkipMutableArray.m b/GSSkipMutableArray.m new file mode 100644 index 0000000..158865b --- /dev/null +++ b/GSSkipMutableArray.m @@ -0,0 +1,251 @@ +/** + Copyright (C) 2006 Free Software Foundation, Inc. + + Written by: Matt Rice + 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 +#include +#include + +@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) |{ 0 | }",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%@ |{ %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:@"|{ %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