From 000bf4a523f0902bcba5749ef2fa92fda27036b1 Mon Sep 17 00:00:00 2001 From: Richard Frith-MacDonald Date: Sat, 26 Apr 2014 09:26:59 +0000 Subject: [PATCH] add GSUniqued git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@37812 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 9 +++ GNUmakefile | 5 +- GSUniqued.h | 80 +++++++++++++++++++++++ GSUniqued.m | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ Performance.h | 1 + 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 GSUniqued.h create mode 100644 GSUniqued.m diff --git a/ChangeLog b/ChangeLog index b9f4f8b..e0964e8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2014-04-26 Richard Frith-Macdonald + + * GSUniqued.h: + * GSUniqued.m: + * Performance.h: + * GNUmakefile: + New code to implement uniqued copies of objects for lowered memory + footprint and faster collection lookups. + 2013-11-05 Niels Grewe * GSFIFO.m: Fix calculation of the timeout for cooperating diff --git a/GNUmakefile b/GNUmakefile index 7bbfa1c..9a6abce 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -43,6 +43,7 @@ Performance_OBJC_FILES += \ GSTicker.m \ GSIndexedSkipList.m \ GSSkipMutableArray.m \ + GSUniqued.m \ Performance_HEADER_FILES += \ @@ -54,6 +55,7 @@ Performance_HEADER_FILES += \ GSThroughput.h \ GSTicker.h \ GSSkipMutableArray.h \ + GSUniqued.h \ Performance_AGSDOC_FILES += \ @@ -64,7 +66,8 @@ Performance_AGSDOC_FILES += \ GSThreadPool.h \ GSThroughput.h \ GSTicker.h \ - GSSkipMutableArray.h + GSSkipMutableArray.h \ + GSUniqued.h \ # Optional Java wrappers for the library diff --git a/GSUniqued.h b/GSUniqued.h new file mode 100644 index 0000000..4e88451 --- /dev/null +++ b/GSUniqued.h @@ -0,0 +1,80 @@ +#if !defined(INCLUDED_GSUNIQUED) +#define INCLUDED_GSUNIQUED 1 +/** + Copyright (C) 2014 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: April 2014 + + 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 Lesser General Public + License as published by the Free Software Foundation; either + version 3 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser 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. + */ +#import + +/** Class used to unique other objects.
+ *

The point of this class is to lower the memory footprint and speed + * up comparisons (pointer equality) in cases where an application + * stores multiple copies of the same object in various maps.
+ * Since uniquing is performed by storing an immutable copy of the + * original object in a map until there are no further references + * to that object, it's pointless to use this uniquing unless the + * application would be storing at least two copies of the object.
+ * Also, since this is thread-safe there is a lock management + * overhead wherever a uniqued object is released, so performance + * gains are to be expected only if the uniqued object has a + * relatively long lifetime and is tested for equality with other + * instances frequently.
+ * In short, use with care; while uniquing can have a big performance + * advantage for some programs, this is actually quite rare. + *

+ *

The internal implementation of the uniquing works by taking + * immutable copies of the objects to be uniqued, storing those copies + * in a hash table, and swizzling their class pointers to a sub-class + * which will automatically remove the instance from the hash table + * before it is deallocated.
+ * Access to the hash table is protected by locks so that uniqued + * objects may be used freely in multiple threads.
+ * The name of the subclass used is the name of the original class + * with 'GSUniqued' added as a prefix. + *

+ */ +@interface GSUniqued : NSObject + +/** This method returns a copy of its argument, uniqued so that other + * such copies of equal objects will be the same instance.
+ * The argument must respond to -copyWithZone: by returning an instance + * of class of immutable objects (ie where the -hash and -isEqual: + * methods are stable for that instance). + */ ++ (id) copyUniqued: (id)anObject; + +@end + +/** Category for uniquing any copyable object.
+ * NB. This must only be used by classes for which -copyWithZone: + * produces an instance of an immutable class. + */ +@interface NSObject (GSUniqued) + +/** This method returns a copy of the receiver uniqued so that other + * such copies of equal objects content will be the same instance. + */ +- (id) copyUniqued; + +@end + +#endif + diff --git a/GSUniqued.m b/GSUniqued.m new file mode 100644 index 0000000..e226419 --- /dev/null +++ b/GSUniqued.m @@ -0,0 +1,176 @@ +/** + Copyright (C) 2014 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: April 2014 + + 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 Lesser General Public + License as published by the Free Software Foundation; either + version 3 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser 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. + */ + +#import +#import +#import +#import +#import +#import +#import +#import "GSUniqued.h" + + +static Class GSUniquedClass = Nil; +static NSLock *uniquedObjectsLock; +static IMP iLock; +static IMP iUnlock; +static NSHashTable *uniquedObjects; +static NSLock *classLock; +static NSMutableDictionary *classMap; + +/* Deallocate a uniqued object ... we must remove it from the uniqued + * objects table and then call the real -dealloc method. + */ +static void +uDealloc(id self, SEL _cmd) +{ + Class c; + IMP i; + + NSHashRemove(uniquedObjects, self); + c = object_getClass(self); + c = class_getSuperclass(c); + i = class_getMethodImplementation(c, _cmd); + (*i)(self, _cmd); +} + +/* Release a uniqued object ... we must obtain a lock in case the uniqued + * objects table has to be modified by removal of this instance on + * deallocation. + */ +static void +uRelease(id self, SEL _cmd) +{ + Class c; + IMP i; + + c = object_getClass(self); + c = class_getSuperclass(c); + i = class_getMethodImplementation(c, _cmd); + (*iLock)(uniquedObjectsLock, @selector(lock)); + (*i)(self, _cmd); + (*iUnlock)(uniquedObjectsLock, @selector(unlock)); +} + +@implementation GSUniqued + ++ (void) initialize +{ + if (Nil == GSUniquedClass) + { + classLock = [NSLock new]; + classMap = [NSMutableDictionary new]; + uniquedObjectsLock = [NSLock new]; + iLock = [uniquedObjectsLock methodForSelector: @selector(lock)]; + iUnlock = [uniquedObjectsLock methodForSelector: @selector(unlock)]; + uniquedObjects = NSCreateHashTable( + NSNonRetainedObjectHashCallBacks, 10000); + GSUniquedClass = [GSUniqued class]; + } +} + ++ (id) allocWithZone: (NSZone*)z +{ + [NSException raise: NSInvalidArgumentException + format: @"Attempt to allocate instance of GSUniqued"]; + return nil; +} + ++ (id) copyUniqued: (id)anObject +{ + NSObject *found; + + NSAssert(nil != anObject, NSInvalidArgumentException); + (*iLock)(uniquedObjectsLock, @selector(lock)); + found = [(NSObject*)NSHashGet(uniquedObjects, anObject) retain]; + (*iUnlock)(uniquedObjectsLock, @selector(unlock)); + + if (nil == found) + { + NSObject *aCopy; + Class c; + Class u; + + aCopy = [anObject copyWithZone: NSDefaultMallocZone()]; + c = object_getClass(aCopy); + + [classLock lock]; + u = [classMap objectForKey: c]; + if (Nil == u) + { + const char *cn = class_getName(c); + char name[strlen(cn) + 20]; + Method method; + + sprintf(name, "GSUniqued%s", cn); + u = objc_allocateClassPair(c, name, 0); + + method = class_getInstanceMethod([NSObject class], + @selector(dealloc)); + class_addMethod(u, @selector(dealloc), + (IMP)uDealloc, method_getTypeEncoding(method)); + + method = class_getInstanceMethod([NSObject class], + @selector(release)); + class_addMethod(u, @selector(release), + (IMP)uRelease, method_getTypeEncoding(method)); + + objc_registerClassPair(u); + [classMap setObject: u forKey: c]; + } + [classLock unlock]; + + (*iLock)(uniquedObjectsLock, @selector(lock)); + found = [(NSObject*)NSHashGet(uniquedObjects, anObject) retain]; + if (nil == found) + { + found = aCopy; +#if defined(GNUSTEP) + GSClassSwizzle(found, u); +#else + object_setClass(found, u); +#endif + NSHashInsert(uniquedObjects, found); + } + else + { + [aCopy release]; // Already uniqued by another thread + } + (*iUnlock)(uniquedObjectsLock, @selector(unlock)); + } + return found; +} + +@end + +@implementation NSObject (GSUniqued) + +- (id) copyUniqued +{ + if (Nil == GSUniquedClass) [GSUniqued class]; + return [GSUniquedClass copyUniqued: (id)self]; +} + +@end + diff --git a/Performance.h b/Performance.h index c0af6d9..2392214 100644 --- a/Performance.h +++ b/Performance.h @@ -32,4 +32,5 @@ #import "GSThroughput.h" #import "GSTicker.h" #import "GSSkipMutableArray.h" +#import "GSUniqued.h"