Memory usage improvements

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@8099 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2000-11-13 11:54:58 +00:00
parent fca9aafedb
commit 17ef6f2a6b
2 changed files with 173 additions and 29 deletions

View file

@ -1,3 +1,9 @@
2000-11-13 Richard Frith-Macdonald <rfm@gnu.org>
* Source/GSTextStorage.m: partial rewrite to cache/unique dictionaries
and to coalesce adjacent regions of string with the same attributes
when setting attributes.
2000-11-12 Fred Kiefer <FredKiefer@gmx.de> 2000-11-12 Fred Kiefer <FredKiefer@gmx.de>
* Headers/gnustep/gui/IMLoading.h: * Headers/gnustep/gui/IMLoading.h:

View file

@ -49,6 +49,78 @@
#define SANITY_CHECKS 0 #define SANITY_CHECKS 0
#define GSI_MAP_RETAIN_KEY(X)
#define GSI_MAP_RELEASE_KEY(X)
#define GSI_MAP_RETAIN_VAL(X)
#define GSI_MAP_RELEASE_VAL(X)
#define GSI_MAP_EQUAL(X,Y) [(X).obj isEqualToDictionary: (Y).obj]
#define GSI_MAP_KTYPES GSUNION_OBJ
#define GSI_MAP_VTYPES GSUNION_INT
#include <base/GSIMap.h>
static NSLock *attrLock = nil;
static GSIMapTable_t attrMap;
static SEL lockSel;
static SEL unlockSel;
static IMP lockImp;
static IMP unlockImp;
#define ALOCK() if (attrLock != nil) (*lockImp)(attrLock, lockSel)
#define AUNLOCK() if (attrLock != nil) (*unlockImp)(attrLock, unlockSel)
/*
* Add a dictionary to the cache - if it was not already there, return
* the copy added to the cache, if it was, count it and return retained
* object that was there.
*/
static NSDictionary*
cacheAttributes(NSDictionary *attrs)
{
GSIMapNode node;
ALOCK();
node = GSIMapNodeForKey(&attrMap, (GSIMapKey)attrs);
if (node == 0)
{
attrs = [attrs copy];
GSIMapAddPair(&attrMap, (GSIMapKey)attrs, (GSIMapVal)(unsigned)1);
}
else
{
node->value.uint++;
attrs = RETAIN(node->key.obj);
}
AUNLOCK();
return attrs;
}
static void
unCacheAttributes(NSDictionary *attrs)
{
GSIMapBucket bucket;
ALOCK();
bucket = GSIMapBucketForKey(&attrMap, (GSIMapKey)attrs);
if (bucket != 0)
{
GSIMapNode node;
node = GSIMapNodeForKeyInBucket(bucket, (GSIMapKey)attrs);
if (node != 0)
{
if (--node->value.uint == 0)
{
GSIMapRemoveNodeFromMap(&attrMap, bucket, node);
GSIMapFreeNode(&attrMap, node);
}
}
}
AUNLOCK();
}
@interface GSTextStorage : NSTextStorage @interface GSTextStorage : NSTextStorage
{ {
NSMutableString *textChars; NSMutableString *textChars;
@ -56,7 +128,7 @@
} }
@end @end
@interface GSTextInfo : NSObject @interface GSTextInfo : NSObject <GCFinalization>
{ {
@public @public
unsigned loc; unsigned loc;
@ -69,18 +141,28 @@
@implementation GSTextInfo @implementation GSTextInfo
/*
* Called to record attributes at a particular location - the given attributes
* dictionary must have been produced by 'cacheAttributes()' so that it is
* already copied/retained and this method doesn't need to do it.
*/
+ (GSTextInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l; + (GSTextInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l;
{ {
GSTextInfo *info = (GSTextInfo*)NSAllocateObject(self, 0, z); GSTextInfo *info = (GSTextInfo*)NSAllocateObject(self, 0, z);
info->loc = l; info->loc = l;
info->attrs = [a copyWithZone: z]; info->attrs = a;
return info; return info;
} }
- (Class) classForPortCoder
{
return [self class];
}
- (void) dealloc - (void) dealloc
{ {
RELEASE(attrs); [self gcFinalize];
NSDeallocateObject(self); NSDeallocateObject(self);
} }
@ -90,26 +172,30 @@
loc, attrs]; loc, attrs];
} }
- (Class) classForPortCoder
{
return [self class];
}
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
{
return self;
}
- (void) encodeWithCoder: (NSCoder*)aCoder - (void) encodeWithCoder: (NSCoder*)aCoder
{ {
[aCoder encodeValueOfObjCType: @encode(unsigned) at: &loc]; [aCoder encodeValueOfObjCType: @encode(unsigned) at: &loc];
[aCoder encodeValueOfObjCType: @encode(id) at: &attrs]; [aCoder encodeValueOfObjCType: @encode(id) at: &attrs];
} }
- (void) gcFinalize
{
unCacheAttributes(attrs);
DESTROY(attrs);
}
- (id) initWithCoder: (NSCoder*)aCoder - (id) initWithCoder: (NSCoder*)aCoder
{ {
NSDictionary *a;
[aCoder decodeValueOfObjCType: @encode(unsigned) at: &loc]; [aCoder decodeValueOfObjCType: @encode(unsigned) at: &loc];
[aCoder decodeValueOfObjCType: @encode(id) at: &attrs]; a = [aCoder decodeObject];
attrs = cacheAttributes(a);
return self;
}
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
{
return self; return self;
} }
@ -151,6 +237,8 @@ static void _setup()
{ {
NSMutableArray *a; NSMutableArray *a;
GSIMapInitWithZoneAndCapacity(&attrMap, NSDefaultMallocZone(), 32);
infSel = @selector(newWithZone:value:at:); infSel = @selector(newWithZone:value:at:);
addSel = @selector(addObject:); addSel = @selector(addObject:);
cntSel = @selector(count); cntSel = @selector(count);
@ -194,7 +282,8 @@ _setAttributesFrom(
attr = [attributedString attributesAtIndex: aRange.location attr = [attributedString attributesAtIndex: aRange.location
effectiveRange: &range]; effectiveRange: &range];
info = [GSTextInfo newWithZone: z value: attr at: 0]; attr = cacheAttributes(attr);
info = NEWINFO(z, attr, 0);
ADDOBJECT(info); ADDOBJECT(info);
RELEASE(info); RELEASE(info);
@ -202,7 +291,8 @@ _setAttributesFrom(
{ {
attr = [attributedString attributesAtIndex: loc attr = [attributedString attributesAtIndex: loc
effectiveRange: &range]; effectiveRange: &range];
info = [GSTextInfo newWithZone: z value: attr at: loc - aRange.location]; attr = cacheAttributes(attr);
info = NEWINFO(z, attr, loc - aRange.location);
ADDOBJECT(info); ADDOBJECT(info);
RELEASE(info); RELEASE(info);
} }
@ -321,9 +411,34 @@ _attributesAtIndexEffectiveRange(
#define SANITY() #define SANITY()
#endif #endif
/*
* If we are multi-threaded, we must guard access to the uniquing set.
*/
+ (void) _becomeThreaded: (id)notification
{
attrLock = [NSLock new];
lockSel = @selector(lock);
unlockSel = @selector(unlock);
lockImp = [attrLock methodForSelector: lockSel];
unlockImp = [attrLock methodForSelector: unlockSel];
}
+ (void) initialize + (void) initialize
{ {
_setup(); _setup();
if ([NSThread isMultiThreaded])
{
[self _becomeThreaded: nil];
}
else
{
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_becomeThreaded:)
name: NSWillBecomeMultiThreadedNotification
object: nil];
}
} }
- (Class) classForPortCoder - (Class) classForPortCoder
@ -369,6 +484,11 @@ _attributesAtIndexEffectiveRange(
{ {
GSTextInfo *info; GSTextInfo *info;
if (attributes == nil)
{
attributes = [NSDictionary dictionary];
}
attributes = cacheAttributes(attributes);
info = NEWINFO(z, attributes, 0); info = NEWINFO(z, attributes, 0);
ADDOBJECT(info); ADDOBJECT(info);
RELEASE(info); RELEASE(info);
@ -423,6 +543,7 @@ _attributesAtIndexEffectiveRange(
{ {
attributes = [NSDictionary dictionary]; attributes = [NSDictionary dictionary];
} }
attributes = cacheAttributes(attributes);
SANITY(); SANITY();
tmpLength = [textChars length]; tmpLength = [textChars length];
GS_RANGE_CHECK(range, tmpLength); GS_RANGE_CHECK(range, tmpLength);
@ -436,7 +557,25 @@ SANITY();
*/ */
attrs = _attributesAtIndexEffectiveRange( attrs = _attributesAtIndexEffectiveRange(
afterRangeLoc, &effectiveRange, tmpLength, infoArray, &arrayIndex); afterRangeLoc, &effectiveRange, tmpLength, infoArray, &arrayIndex);
if (effectiveRange.location > beginRangeLoc) if (attrs == attributes)
{
/*
* The located range has the same attributes as us - so we can
* extend our range to include it.
*/
if (effectiveRange.location < beginRangeLoc)
{
range.length += beginRangeLoc - effectiveRange.location;
range.location = effectiveRange.location;
beginRangeLoc = range.location;
}
if (NSMaxRange(effectiveRange) > afterRangeLoc)
{
range.length = NSMaxRange(effectiveRange) - range.location;
afterRangeLoc = NSMaxRange(range);
}
}
else if (effectiveRange.location > beginRangeLoc)
{ {
/* /*
* The located range also starts at or after our range. * The located range also starts at or after our range.
@ -445,13 +584,13 @@ SANITY();
info->loc = afterRangeLoc; info->loc = afterRangeLoc;
arrayIndex--; arrayIndex--;
} }
else else if (effectiveRange.location < beginRangeLoc)
{ {
/* /*
* The located range starts before our range. * The located range starts before our range.
* Create a subrange to go from our end to the end of the old range. * Create a subrange to go from our end to the end of the old range.
*/ */
info = NEWINFO(z, attrs, afterRangeLoc); info = NEWINFO(z, cacheAttributes(attrs), afterRangeLoc);
arrayIndex++; arrayIndex++;
INSOBJECT(info, arrayIndex); INSOBJECT(info, arrayIndex);
RELEASE(info); RELEASE(info);
@ -475,11 +614,17 @@ SANITY();
arrayIndex--; arrayIndex--;
} }
/*
* Use the location/attribute info in the current slot if possible,
* otherwise, add a new slot and use that.
*/
info = OBJECTAT(arrayIndex); info = OBJECTAT(arrayIndex);
if (info->loc >= beginRangeLoc) if (info->loc >= beginRangeLoc || info->attrs == attributes)
{ {
info->loc = beginRangeLoc; info->loc = beginRangeLoc;
ASSIGNCOPY(info->attrs, attributes); unCacheAttributes(info->attrs);
RELEASE(info->attrs);
info->attrs = attributes;
} }
else else
{ {
@ -548,14 +693,7 @@ SANITY();
tmpLength, infoArray, &arrayIndex); tmpLength, infoArray, &arrayIndex);
arrayIndex++; arrayIndex++;
if (NSMaxRange(effectiveRange) > NSMaxRange(range)) if (NSMaxRange(effectiveRange) < NSMaxRange(range))
{
info = NEWINFO(z, attrs, NSMaxRange(range));
INSOBJECT(info, arrayIndex);
arraySize++;
SANITY();
}
else if (NSMaxRange(effectiveRange) < NSMaxRange(range))
{ {
/* /*
* Remove all range info for ranges enclosed within the one * Remove all range info for ranges enclosed within the one