-[NSScanner scanDecimal:] takes an average of 4 milliseconds to scan a short 
string of decimal digits, which means tens of seconds for thousands of scans, 
which is unacceptable for my application.  Also, excessive memory allocations 
require a local autorelease pool around each invocation.

Surprisingly, I was able to fix both problem by replacing -scanDecimal: with a 
rather bone-headed home-brew implementation, using 
-scanCharactersFromSet:intoString: instead.  The home-brew implementation runs 
100 to 150 times faster.  How can this be?

#import <Cocoa/Cocoa.h>

@interface NSScanner (Speedy)

- (BOOL)scanDecimalNumber:(NSNumber**)number ;

@end

@implementation NSScanner (Speedy)

/*
 Bone-headed home-brew implementation
 */
- (BOOL)scanDecimalNumber:(NSNumber**)number {
    NSString* string = nil ;
    NSCharacterSet* decimalSet ;
    decimalSet = [NSCharacterSet 
characterSetWithCharactersInString:@"0123456789-."] ;
    BOOL isDecimal = [self scanCharactersFromSet:decimalSet
                                      intoString:&string] ;
    if (isDecimal) {
        double value ;
        value = [string doubleValue] ;  // See Note, below
        *number = [NSNumber numberWithDouble:value] ;
    }
    
    return isDecimal ;
    
    /*
     Note.  At first, I'd written a more complicated implementation which 
checked
     if the number was an integer and if so used -numberWithInteger instead of
     -numberWithDouble, but then found that performance of the current
     implementation here is the same.  I suppose this is because we've been
     doing floating-point calculations in hardware for quite a few years now.
     */
}

@end

NSTimeInterval TestScanner(
                           NSScanner *scanner,
                           BOOL homeBrew,
                           NSInteger exponent) {
    // We use a local autorelease pool, otherwise -scanDecimal: starts
    // using gigabytes of virtual memory, which makes both the HomeBrew
    // and scanDecimal methods take longer, and slow our Mac.
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;
    
    [scanner setScanLocation:0] ;
    double checksum = 0.0 ;
    NSDate* date = [NSDate date] ;
    while (![scanner isAtEnd]) {
        NSNumber* number = nil ;
        BOOL didScanNumber ;
        if (homeBrew) {
            didScanNumber = [scanner scanDecimalNumber:&number] ;
        }
        else {
            NSDecimal decimal;
            didScanNumber = [scanner scanDecimal:&decimal];
            if (didScanNumber) {
                number = [NSDecimalNumber decimalNumberWithDecimal:decimal];
            }
        }
        if (didScanNumber) {
            double value = [number doubleValue] ;
            checksum += value ;
        }
        else {
            [scanner setScanLocation:[scanner scanLocation] + 1] ;
        }
    }
    
    /* The template includes four numbers: 1, -2, 3.5, -1.5.  These
     numbers sum to 1.  Therefore when we add together the sums
     of all the scanned numbers, the "checksum" should be equal
     to the number of times that the template appears in the
     scanner's string, which is 2^exponent. */    
    BOOL checksumOK = checksum == pow(2,exponent) ;
    
    NSTimeInterval elapsed = -[date timeIntervalSinceNow] ;
    NSLog(@"%@:  time = %9.3e seconds   checksum = %0.16f (%@)",
          homeBrew ? @"    Using Home-Brew" : @"Using -scanDecimal:",
          elapsed,
          checksum,
          checksumOK ? @"OK" : @"Error!") ;
    [pool release] ;
    
    return elapsed ;
}

#define HOME_BREW YES
#define SCAN_DECIMAL NO

int main(int argc, char *argv[]) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;
    
    NSString* template = @"+int:1 -int:-2 +float:3.5 -float:-1.5 " ;
    NSMutableString* s = [template mutableCopy] ;
    NSInteger i ;
    NSInteger exponent = 9 ;
    // Repeat the string 2^exponent times
    for (i=0; i<exponent; i++) {
        [s appendString:s] ;        
    }
    
    NSScanner* scanner = [[NSScanner alloc] initWithString:s] ;
    
    NSTimeInterval elapsedScanDecimal = 0.0 ;
    NSTimeInterval elapsedHomeBrew = 0.0 ;
    
    NSInteger j ;
    for (j=0; j<5; j++) {
        elapsedScanDecimal += TestScanner(scanner, SCAN_DECIMAL, exponent) ;
        elapsedHomeBrew += TestScanner(scanner, HOME_BREW, exponent) ;
    }
    NSLog (@"Speed Improvement HomeBrew/scanDecimal: %0.1f",
           elapsedScanDecimal/elapsedHomeBrew) ;
    
    [scanner release] ;
    [s release] ;
    
    [pool drain] ;
    
    return 0 ;     
}_______________________________________________

Cocoa-dev mailing list ([email protected])

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [email protected]

Reply via email to