2014-10-19 2 views
0

Сравнивая производительность использования NSXMLParser в объективных C и Swift, существует большое несоответствие производительности. Только регистрация didStartElement, didEndElement и foundCharacters имеет производительность ~ 17 Мбайт/с в объективе C, но низкий ~ 1,4 МБ/с в Swift (при кастинге в String, см. Ниже). Код запускается в режиме Release (оптимизирован).Быстрая работа с использованием NSXMLParser

Objective C:

#import <Foundation/Foundation.h> 

@interface MyDelegate: NSObject <NSXMLParserDelegate> { 
    @public 
    int didStartElement; 
    int didEndElement; 
    int foundCharacters; 
} 
@end 

@implementation MyDelegate 
-(MyDelegate *)init { 
    didStartElement = 0; 
    didEndElement = 0; 
    foundCharacters = 0; 
    return self; 
} 

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { 
    didStartElement += 1; 
} 

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { 
    didEndElement += 1; 
} 

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 
    foundCharacters += 1; 
} 
@end 

int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
     NSURL *input = [NSURL fileURLWithPath: [[NSProcessInfo processInfo] arguments][1]]; 

     NSError *error; 

     if (![input checkResourceIsReachableAndReturnError:&error]) { 
      NSLog(error.description); 
      abort(); 
     } 

     NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:input]; 
     MyDelegate *delegate = [[MyDelegate alloc] init]; 
     parser.delegate = delegate; 

     NSDate *start = [NSDate new]; 
     if (![parser parse]) { 
      NSLog(parser.parserError.description); 
     } 
     NSDate *end = [NSDate new]; 

     NSLog(@"Done. #didStartElement: %d, #didEndElement: %d, #foundCharacters: %d", delegate->didStartElement, delegate->didEndElement, delegate->foundCharacters); 

     NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:input.path error:&error]; 

     // Determine MB/s 
     if (error != nil) { 
      NSLog(@"%@", error); 
      abort(); 
     } 

     double throughput = ((NSNumber *)[attrs valueForKey:NSFileSize]).doubleValue/[end timeIntervalSinceDate:start]/1e6; 
     NSLog(@"Throughput %f MB/s", throughput); 
    } 
    return 0; 
} 

Swift:

import Foundation 

var input = NSURL(fileURLWithPath: Process.arguments[1])! 
var error: NSError? 
if !input.checkResourceIsReachableAndReturnError(&error) { 
    println(error) 
    abort() 
} 

class MyDelegate: NSObject, NSXMLParserDelegate { 
    var didStartElement = 0 
    var didEndElement = 0 
    var foundCharacters = 0 

    func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) { 
     didStartElement += 1 
    } 

    func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { 
     didEndElement += 1 
    } 

    func parser(parser: NSXMLParser, foundCharacters string: String) { 
     foundCharacters += 1 
    } 
} 


var parser = NSXMLParser(contentsOfURL: input)! 
println(input) 
var delegate = MyDelegate() 
parser.delegate = delegate 

var start = NSDate() 
parser.parse() 
var end = NSDate() 

println("Done. #didStartElement: \(delegate.didStartElement), #didEndElement \(delegate.didEndElement), #foundCharacters \(delegate.foundCharacters)") 

// Determine MB/s 
var attrs = NSFileManager.defaultManager().attributesOfItemAtPath(input.path!, error: &error) 
if error != nil { 
    println(error!) 
    abort() 
} 
var throughput = Double(attrs![NSFileSize]! as Int)/end.timeIntervalSinceDate(start)/1e6 
println("Throughput \(throughput) MB/s") 

Много производительности теряется при литье; увидеть разницу для определения типа для attributes аргумента в didStartElement:

NSDictionary: 19 МБ/с
[NSObject: AnyObject]: 8,5 Мб/с
[String: String]: 1,4 Мб/с

Использование счетчика на инструменте, по-видимому, 36% времени тратится на преобразовании словаря в Swift (с использованием [NSObject: AnyObject]): Swift NSXMLParser Counter Instrument Results

в качестве атрибутов узлов имеют значение для дальнейшей обработки, с не следует избегать их для Swift String. Как все еще получить приличную производительность обработки в Swift?

Update

При использовании парсера SAX Libxml2 непосредственно в C, производительность составляет около 110 Мбайт/с. Так что здесь действительно проблема с производительностью.

ответ

1

Я бы предложил использовать синтаксический анализатор SAX с C API в качестве базового анализатора XML (например, libxml2). Создание NSDictionary s и особенноNSString s неоправданно дорого (необязательно ИМХО). Таким образом, мы можем сэкономить, по крайней мере, эти затраты при создании непосредственно контейнеров Swift и Swift Strings из структур данных, полученных из анализатора XML.

Я не знаю, как дорого создавать Swift Strings и Swift Dictionaries. Свифт и его библиотека все еще находятся в зачаточном состоянии.

Редактировать

Смотрите также How to use the CoreAudio API in Swift,

и Function callback from C to Swift.

+0

Использование анализатора SAX от libxml требует определения обратных вызовов. В настоящее время невозможно создать C Function Pointer из функций Swift. Так возможно ли это, минуя Objective C и мостик? – bouke

+0

Я боюсь, передача указателя на функцию Swift, которая вызывается в C-коде, пока не поддерживается. Эту проблему нужно исследовать. Есть интересное замечание: http://stackoverflow.com/questions/24107099/function-callback-from-c-to-swift. – CouchDeveloper

+0

Спасибо!Хотя в настоящее время невозможно реализовать саксовый анализатор libxml непосредственно из Swift, для него можно использовать обертку ObjC. Передавая указатели на символы Swift, на ObjC до Swift не так много накладных расходов. – bouke

1

На данный момент вы можете обойти изменения на основе преобразования, объявив словарные аргументы как NSDictionary вместо словаря Swift.

+0

Это откладывает листинг только в другом месте, где используются значения словаря. Они должны быть брошены в String где-то. – bouke

+0

Это позволит избежать преобразования словаря, а не преобразования строк. –