libs-performance/GSFIFO.m
Richard Frith-MacDonald 16b09c5202 add option for writing a whole block in one go
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@39039 72102866-910b-0410-8b05-ffd578937521
2015-10-07 13:54:54 +00:00

1058 lines
23 KiB
Objective-C

/**
Copyright (C) 2011 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
Date: April 2011
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 "GSFIFO.h"
#import <Foundation/NSArray.h>
#import <Foundation/NSException.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSString.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSZone.h>
#include <inttypes.h>
@implementation GSFIFO
static NSLock *classLock = nil;
static NSMapTable *allFIFOs = nil;
static NSArray *defaultBoundaries = nil;
static Class NSDateClass = 0;
static SEL tiSel = 0;
static NSTimeInterval (*tiImp)(Class, SEL) = 0;
#define NOW ((*tiImp)(NSDateClass, tiSel))
#define START if (boundsCount > 0) ti = NOW;
#define ENDGET if (boundsCount > 0 && ti > 0.0) {\
ti = NOW - ti; getWaitTotal += ti; \
stats(ti, boundsCount, waitBoundaries, getWaitCounts); }
#define ENDPUT if (boundsCount > 0 && ti > 0.0) {\
ti = NOW - ti; putWaitTotal += ti; \
stats(ti, boundsCount, waitBoundaries, putWaitCounts); }
static void
stats(NSTimeInterval ti, uint32_t max, NSTimeInterval *bounds, uint64_t *bands)
{
if (ti > bounds[max - 1])
{
bands[max]++;
}
else
{
uint32_t min = 0;
uint32_t pos = max / 2;
while (max > min)
{
if (ti <= bounds[pos])
{
max = pos;
}
else
{
min = pos + 1;
}
pos = (max + min)/2;
}
bands[pos]++;
}
}
+ (void) initialize
{
if (nil == defaultBoundaries)
{
classLock = [NSLock new];
allFIFOs = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonRetainedObjectMapValueCallBacks, 0);
defaultBoundaries = [[NSArray alloc] initWithObjects:
[NSNumber numberWithDouble: 0.1],
[NSNumber numberWithDouble: 0.2],
[NSNumber numberWithDouble: 0.5],
[NSNumber numberWithDouble: 1.0],
[NSNumber numberWithDouble: 2.0],
[NSNumber numberWithDouble: 5.0],
[NSNumber numberWithDouble: 10.0],
[NSNumber numberWithDouble: 20.0],
[NSNumber numberWithDouble: 50.0],
nil];
NSDateClass = [NSDate class];
tiSel = @selector(timeIntervalSinceReferenceDate);
tiImp
= (NSTimeInterval (*)(Class,SEL))[NSDateClass methodForSelector: tiSel];
}
}
+ (NSString*) stats
{
NSMutableString *m = [NSMutableString stringWithCapacity: 1024];
NSString *k;
GSFIFO *f;
NSMapEnumerator e;
[classLock lock];
e = NSEnumerateMapTable(allFIFOs);
while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&f) != 0)
{
[m appendString: [f stats]];
}
NSEndMapTableEnumeration(&e);
[classLock unlock];
return m;
}
- (unsigned) _cooperatingGet: (void**)buf
count: (unsigned)count
shouldBlock: (BOOL)block
before: (NSDate*)before
{
NSTimeInterval ti;
unsigned index;
BOOL wasFull;
[condition lock];
if (_head - _tail == 0)
{
emptyCount++;
_getTryFailure++;
if (NO == block)
{
[condition unlock];
return 0;
}
START
if ((0 == timeout) && (before == nil))
{
while (_head - _tail == 0)
{
[condition wait];
}
}
else
{
NSDate *d = nil;
NSDate *effective;
[before retain];
if (timeout != 0)
{
d = [[NSDateClass alloc]
initWithTimeIntervalSinceNow: timeout / 1000.0f];
}
if ((d != nil) && (before != nil))
{
effective = [d earlierDate: before];
}
else if (d != nil)
{
effective = d;
}
else
{
effective = before;
}
while (_head - _tail == 0)
{
if (NO == [condition waitUntilDate: effective])
{
[d release];
[before release];
ENDGET
[condition broadcast];
[condition unlock];
if (before != effective)
{
[NSException raise: NSGenericException
format: @"Timeout waiting for new data in FIFO"];
}
else
{
return 0;
}
}
}
[d release];
[before release];
ENDGET
}
}
else
{
_getTrySuccess++;
}
if (_head - _tail == _capacity)
{
wasFull = YES;
}
else
{
wasFull = NO;
}
for (index = 0; index < count && (_head - _tail) != 0; index++)
{
buf[index] = _items[_tail % _capacity];
_tail++;
}
if (YES == wasFull)
{
[condition broadcast];
}
[condition unlock];
return index;
}
- (void*) _cooperatingPeek
{
[condition lock];
if ((_head - _tail) == 0)
{
// We do not need to signal the condition because
// nothing about the qeuue did change
[condition unlock];
return NULL;
}
void *ptr = _items[_tail % _capacity];
[condition unlock];
return ptr;
}
- (id) _cooperatingPeekObject
{
[condition lock];
if ((_head - _tail) == 0)
{
// We do not need to signal the condition because
// nothing about the qeuue did change
[condition unlock];
return nil;
}
id obj =
[[(id<NSObject>)_items[_tail % _capacity] retain] autorelease];
[condition unlock];
return obj;
}
- (void*) peek
{
if (condition != nil)
{
return [self _cooperatingPeek];
}
if (_head - _tail == 0)
{
return NULL;
}
return _items[_tail % _capacity];
}
- (NSObject*) peekObject
{
if (condition != nil)
{
return [self _cooperatingPeekObject];
}
if (_head - _tail == 0)
{
return nil;
}
return [[(id<NSObject>)_items[_tail % _capacity] retain] autorelease];
}
- (unsigned) _cooperatingPut: (void**)buf
count: (unsigned)count
shouldBlock: (BOOL)block
{
NSTimeInterval ti;
unsigned index;
BOOL wasEmpty;
[condition lock];
if (_head - _tail == _capacity)
{
_putTryFailure++;
fullCount++;
if (NO == block)
{
[condition unlock];
return 0;
}
START
if (0 == timeout)
{
while (_head - _tail == _capacity)
{
[condition wait];
}
}
else
{
NSDate *d;
d = [[NSDateClass alloc]
initWithTimeIntervalSinceNow: timeout / 1000.0f];
while (_head - _tail == _capacity)
{
if (NO == [condition waitUntilDate: d])
{
[d release];
ENDPUT
[condition broadcast];
[condition unlock];
[NSException raise: NSGenericException
format: @"Timeout waiting for space in FIFO"];
}
}
[d release];
}
ENDPUT
}
else
{
_putTrySuccess++;
}
if (_head - _tail == 0)
{
wasEmpty = YES;
}
else
{
wasEmpty = NO;
}
for (index = 0; index < count && (_head - _tail < _capacity); index++)
{
_items[_head % _capacity] = buf[index];
_head++;
}
if (YES == wasEmpty)
{
[condition broadcast];
}
[condition unlock];
return index;
}
- (void) putAll: (void**)buf count: (unsigned)count shouldRetain: (BOOL)rtn
{
NSTimeInterval ti;
unsigned index;
BOOL wasEmpty;
NSAssert(nil != condition, NSGenericException);
NSAssert(count <= _capacity, NSInvalidArgumentException);
[condition lock];
if (_head - _tail < count)
{
if (_head - _tail == _capacity)
{
_putTryFailure++;
fullCount++;
}
START
if (0 == timeout)
{
while (_head - _tail < count)
{
[condition wait];
}
}
else
{
NSDate *d;
d = [[NSDateClass alloc]
initWithTimeIntervalSinceNow: timeout / 1000.0f];
while (_head - _tail < count)
{
if (NO == [condition waitUntilDate: d])
{
[d release];
ENDPUT
[condition broadcast];
[condition unlock];
[NSException raise: NSGenericException
format: @"Timeout waiting for space in FIFO"];
}
}
[d release];
}
ENDPUT
}
else
{
_putTrySuccess++;
}
if (_head - _tail == 0)
{
wasEmpty = YES;
}
else
{
wasEmpty = NO;
}
for (index = 0; index < count; index++)
{
_items[_head % _capacity] = buf[index];
_head++;
if (YES == rtn)
{
RETAIN((NSObject*)buf[index]);
}
}
if (YES == wasEmpty)
{
[condition broadcast];
}
[condition unlock];
}
- (void) dealloc
{
[classLock lock];
if (NSMapGet(allFIFOs, name) == self)
{
NSMapRemove(allFIFOs, name);
}
[classLock unlock];
[name release];
[condition release];
if (0 != _items)
{
NSZoneFree(NSDefaultMallocZone(), _items);
}
if (0 != waitBoundaries)
{
NSZoneFree(NSDefaultMallocZone(), waitBoundaries);
}
if (0 != getWaitCounts)
{
NSZoneFree(NSDefaultMallocZone(), getWaitCounts);
}
if (0 != putWaitCounts)
{
NSZoneFree(NSDefaultMallocZone(), putWaitCounts);
}
[super dealloc];
}
- (NSUInteger) count
{
return (NSUInteger)(_head - _tail);
}
- (NSString*) description
{
return [NSString stringWithFormat:
@"%@ (%@) capacity:%"PRIu32" lockless:%c"
@" get:%"PRIu64" put:%"PRIu64" empty:%"PRIu64" full:%"PRIu64"",
[super description], name,
_capacity,
((nil == condition) ? 'Y' : 'N'),
_tail,
_head,
emptyCount,
fullCount];
}
- (unsigned) get: (void**)buf
count: (unsigned)count
shouldBlock: (BOOL)block
before: (NSDate*)date
{
unsigned index;
NSTimeInterval ti;
NSTimeInterval sum;
NSTimeInterval waitLength;
uint32_t old;
uint32_t fib;
if (0 == count) return 0;
if (nil != condition)
{
return [self _cooperatingGet: buf
count: count
shouldBlock: block
before: date];
}
waitLength = [date timeIntervalSinceNow];
if (nil == getThread)
{
getThread = [NSThread currentThread];
}
if (_head > _tail)
{
for (index = 0; index < count && _head > _tail; index++)
{
buf[index] = _items[_tail % _capacity];
_tail++;
_getTrySuccess++;
}
return index;
}
_getTryFailure++;
emptyCount++;
if (NO == block)
{
return 0;
}
START
old = 0;
fib = 1;
sum = 0.0;
while (_head <= _tail)
{
uint32_t tmp;
NSTimeInterval dly;
if (timeout > 0 && sum * 1000 > timeout)
{
ENDGET
[NSException raise: NSGenericException
format: @"Timeout waiting for new data in FIFO"];
}
else if (date != nil && sum > waitLength)
{
ENDGET
return 0;
}
tmp = fib + old;
old = fib;
fib = tmp;
if (granularity > 0 && fib > granularity)
{
fib = granularity;
}
dly = ((NSTimeInterval)fib) / 1000.0;
[NSThread sleepForTimeInterval: dly];
sum += dly;
}
ENDGET
for (index = 0; index < count && _head > _tail; index++)
{
buf[index] = _items[_tail % _capacity];
_tail++;
}
return index;
}
- (unsigned) get: (void**)buf count: (unsigned)count shouldBlock: (BOOL)block
{
return [self get: buf count: count shouldBlock: block before: nil];
}
- (unsigned) getObjects: (NSObject**)buf
count: (unsigned)count
shouldBlock: (BOOL)block
before: (NSDate*)date
{
unsigned result;
unsigned index;
index = result = [self get: (void**)buf
count: count
shouldBlock: block
before: date];
while (index-- > 0)
{
[buf[index] autorelease];
}
return result;
}
- (unsigned) getObjects: (NSObject**)buf
count: (unsigned)count
shouldBlock: (BOOL)block
{
return [self getObjects: buf
count: count
shouldBlock: block
before: nil];
}
- (void*) get
{
void *item = 0;
while (0 == [self get: &item count: 1 shouldBlock: YES])
;
return item;
}
- (NSObject*) getObject
{
void *item = 0;
while (0 == [self get: &item count: 1 shouldBlock: YES])
;
return [(NSObject*)item autorelease];
}
- (id) initWithCapacity: (uint32_t)c
granularity: (uint16_t)g
timeout: (uint16_t)t
multiProducer: (BOOL)mp
multiConsumer: (BOOL)mc
boundaries: (NSArray*)a
name: (NSString*)n
{
if (c < 1 || c > 1000000)
{
[self release];
return nil;
}
_capacity = c;
granularity = g;
timeout = t;
_items = (void*)NSAllocateCollectable(c * sizeof(void*), NSScannedOption);
if (YES == mp || YES == mc)
{
condition = [NSCondition new];
}
name = [n copy];
if (nil == a)
{
a = defaultBoundaries;
}
if ((c = [a count]) > 0)
{
NSTimeInterval l;
NSNumber *n;
waitBoundaries
= (NSTimeInterval*)NSAllocateCollectable(c * sizeof(NSTimeInterval), 0);
boundsCount = c++;
getWaitCounts
= (uint64_t*)NSAllocateCollectable(c * sizeof(uint64_t), 0);
putWaitCounts
= (uint64_t*)NSAllocateCollectable(c * sizeof(uint64_t), 0);
n = [a lastObject];
if (NO == [n isKindOfClass: [NSNumber class]]
|| (l = [n doubleValue]) <= 0.0)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"Bad boundaries"];
}
c = boundsCount;
waitBoundaries[--c] = l;
while (c-- > 0)
{
NSTimeInterval t;
n = [a objectAtIndex: c];
if (NO == [n isKindOfClass: [NSNumber class]]
|| (t = [n doubleValue]) <= 0.0 || t >= l)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"Bad boundaries"];
}
waitBoundaries[c] = t;
l = t;
}
}
[classLock lock];
if (nil != NSMapGet(allFIFOs, n))
{
[classLock unlock];
[self release];
[NSException raise: NSInvalidArgumentException
format: @"GSFIFO ... name (%@) already in use", n];
}
NSMapInsert(allFIFOs, name, self);
[classLock unlock];
return self;
}
- (id) initWithCapacity: (uint32_t)c
name: (NSString*)n
{
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
NSString *key;
NSInteger i;
uint16_t g;
uint16_t t;
BOOL mc;
BOOL mp;
NSArray *b;
key = [NSString stringWithFormat: @"GSFIFOCapacity%@", n];
if (nil == [defs objectForKey: key]) key = @"GSFIFOCapacity";
i = [defs integerForKey: key];
if (i > 0)
{
c = i;
}
key = [NSString stringWithFormat: @"GSFIFOGranularity%@", n];
if (nil == [defs objectForKey: key]) key = @"GSFIFOGranularity";
g = [defs integerForKey: key];
key = [NSString stringWithFormat: @"GSFIFOTimeout%@", n];
if (nil == [defs objectForKey: key]) key = @"GSFIFOTimeout";
t = [defs integerForKey: key];
key = [NSString stringWithFormat: @"GSFIFOSingleConsumer%@", n];
if (nil == [defs objectForKey: key]) key = @"GSFIFOSingleConsumer";
mc = (YES == [defs boolForKey: key]) ? NO : YES;
key = [NSString stringWithFormat: @"GSFIFOSingleProducer%@", n];
if (nil == [defs objectForKey: key]) key = @"GSFIFOSingleProducer";
mp = (YES == [defs boolForKey: key]) ? NO : YES;
key = [NSString stringWithFormat: @"GSFIFOBoundaries%@", n];
if (nil == [defs objectForKey: key]) key = @"GSFIFOBoundaries";
b = [defs arrayForKey: key];
return [self initWithCapacity: c
granularity: g
timeout: t
multiProducer: mp
multiConsumer: mc
boundaries: b
name: n];
}
- (id) initWithName: (NSString*)n
{
return [self initWithCapacity: 10000 name: n];
}
- (unsigned) put: (void**)buf count: (unsigned)count shouldBlock: (BOOL)block
{
NSTimeInterval sum;
unsigned index;
NSTimeInterval ti = 0.0;
uint32_t old;
uint32_t fib;
if (0 == count)
{
return 0;
}
if (nil != condition)
{
return [self _cooperatingPut: buf count: count shouldBlock: block];
}
if (nil == putThread)
{
putThread = [NSThread currentThread];
}
if (_head - _tail < _capacity)
{
for (index = 0; index < count && _head - _tail < _capacity; index++)
{
_items[_head % _capacity] = buf[index];
_head++;
}
_putTrySuccess++;
return index;
}
_putTryFailure++;
fullCount++;
if (NO == block)
{
return 0;
}
START
old = 0;
fib = 1;
sum = 0.0;
while (_head - _tail >= _capacity)
{
uint32_t tmp;
NSTimeInterval dly;
if (timeout > 0 && sum * 1000 > timeout)
{
ENDPUT
[NSException raise: NSGenericException
format: @"Timeout waiting for space in FIFO"];
}
tmp = fib + old;
old = fib;
fib = tmp;
if (granularity > 0 && fib > granularity)
{
fib = granularity;
}
dly = ((NSTimeInterval)fib) / 1000.0;
[NSThread sleepForTimeInterval: dly];
sum += dly;
}
ENDPUT
for (index = 0; index < count && _head - _tail < _capacity; index++)
{
_items[_head % _capacity] = buf[index];
_head++;
}
return index;
}
- (unsigned) putObjects: (NSObject**)buf
count: (unsigned)count
shouldBlock: (BOOL)block
{
unsigned result;
unsigned index;
/* NB we must retain objects *before* putting them in the FIFO since
* another thread may grab them immediately.
* That means, if we fail to put all of the objects, we must release
* any that were left over.
*/
for (index = 0; index < count; index++)
{
[buf[index] retain];
}
result = [self put: (void**)buf count: count shouldBlock: block];
while (count-- > result)
{
[buf[count] release];
}
return result;
}
- (void) put: (void*)item
{
while (0 == [self put: &item count: 1 shouldBlock: YES])
;
}
- (void) putObject: (NSObject*)item
{
[item retain];
while (0 == [self put: (void**)&item count: 1 shouldBlock: YES])
;
}
- (void) _getStats: (NSMutableString*)s
{
[s appendFormat:
@" empty:%"PRIu64" failures:%"PRIu64" successes:%"PRIu64"\n",
emptyCount,
_getTryFailure,
_getTrySuccess];
if (boundsCount > 0)
{
unsigned i;
[s appendFormat: @" blocked total:%g average:%g perfail:%g\n",
getWaitTotal,
(_getTryFailure+_getTrySuccess > 0)
? getWaitTotal / (_getTryFailure+_getTrySuccess) : 0.0,
(_getTryFailure > 0)
? getWaitTotal / _getTryFailure : 0.0];
for (i = 0; i < boundsCount; i++)
{
[s appendFormat: @" up to %g: %"PRIu64"\n",
waitBoundaries[i], getWaitCounts[i]];
}
[s appendFormat: @" above %g: %"PRIu64"\n",
waitBoundaries[boundsCount-1], getWaitCounts[boundsCount]];
}
}
- (void) _putStats: (NSMutableString*)s
{
[s appendFormat: @" full:%"PRIu64" failures:%"PRIu64" successes:%"PRIu64"\n",
fullCount,
_putTryFailure,
_putTrySuccess];
if (boundsCount > 0)
{
unsigned i;
[s appendFormat: @" blocked total:%g average:%g perfail:%g\n",
putWaitTotal,
(_putTryFailure+_putTrySuccess > 0)
? putWaitTotal / (_putTryFailure+_putTrySuccess) : 0.0,
(_putTryFailure > 0)
? putWaitTotal / _putTryFailure : 0.0];
for (i = 0; i < boundsCount; i++)
{
[s appendFormat: @" up to %g: %"PRIu64"\n",
waitBoundaries[i], putWaitCounts[i]];
}
[s appendFormat: @" above %g: %"PRIu64"\n",
waitBoundaries[boundsCount-1], putWaitCounts[boundsCount]];
}
}
- (NSString*) stats
{
NSMutableString *s = [NSMutableString stringWithCapacity: 100];
[s appendFormat: @"%@ (%@) capacity:%"PRIu32" lockless:%c\n",
[super description], name,
_capacity,
((nil == condition) ? 'Y' : 'N')];
if (nil != condition || [NSThread currentThread] == getThread)
{
[condition lock];
[self _getStats: s];
[condition unlock];
}
if (nil != condition || [NSThread currentThread] == putThread)
{
[condition lock];
[self _putStats: s];
[condition unlock];
}
return s;
}
- (NSString*) statsGet
{
NSMutableString *s = [NSMutableString stringWithCapacity: 100];
if (nil == condition)
{
if ([NSThread currentThread] != getThread)
{
if (nil == getThread)
{
getThread = [NSThread currentThread];
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called from wrong thread for %@",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
name];
}
}
}
[condition lock];
[s appendFormat: @"%@ (%@)\n", [super description], name];
[self _getStats: s];
[condition unlock];
return s;
}
- (NSString*) statsPut
{
NSMutableString *s = [NSMutableString stringWithCapacity: 100];
if (nil == condition)
{
if ([NSThread currentThread] != putThread)
{
if (nil == putThread)
{
putThread = [NSThread currentThread];
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called from wrong thread for %@",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
name];
}
}
}
[condition lock];
[s appendFormat: @"%@ (%@)\n", [super description], name];
[self _putStats: s];
[condition unlock];
return s;
}
- (void*) tryGet
{
void *item = nil;
[self get: &item count: 1 shouldBlock: NO];
return item;
}
- (NSObject*) tryGetObject
{
void *item = nil;
[self get: &item count: 1 shouldBlock: NO];
return [(NSObject*)item autorelease];
}
- (BOOL) tryPut: (void*)item
{
if (1 == [self put: &item count: 1 shouldBlock: NO])
{
return YES;
}
return NO;
}
- (BOOL) tryPutObject: (NSObject*)item
{
[item retain];
if (1 == [self put: (void**)&item count: 1 shouldBlock: NO])
{
return YES;
}
[item release];
return NO;
}
- (NSUInteger)sizeInBytesExcluding: (NSHashTable*)excluding
{
NSUInteger size = 0;
if (0 == (size = [super sizeInBytesExcluding: excluding]))
{
return 0;
}
return size
+ (_capacity * sizeof(void)) // item storage
+ (boundsCount * sizeof(NSTimeInterval)) // boundaries
+ (2 * (boundsCount + 1) * sizeof(uint64_t)) // get and put counts
+ [condition sizeInBytesExcluding: excluding]
+ [name sizeInBytesExcluding: excluding]
+ [putThread sizeInBytesExcluding: excluding]
+ [getThread sizeInBytesExcluding: excluding];
}
@end