使用NSXMLParser的迅速性能
比较目标C与Swift中使用NSXMLParser的性能,存在较大的性能差异。目标C中仅注册didStartElement
,didEndElement
和foundCharacters
的性能为〜17 MB/s,但Swift中的低至〜1.4 MB/s(当转换为String时,请参见下文)。代码以发布(优化)模式运行。使用NSXMLParser的迅速性能
目标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;
}
斯威夫特:
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")
铸造时大量的性能损失;看到在didStartElement
为用于attributes
参数的类型定义的区别:
NSDictionary
:19 MB/s的[NSObject: AnyObject]
:8.5百万字节/秒[String: String]
:1.4百万字节/秒
使用计数器仪表,显然36%的时间花费在将字典转换为Swift上(使用[NSObject: AnyObject]
):
由于节点的属性与进一步处理相关,因此c将它们转移到Swift字符串是无法避免的。如何仍然在Swift中获得不错的处理性能?
更新
当用C直接使用的libxml2的SAX解析器,性能大约是110 MB /秒。所以这里确实存在一些性能问题。
我建议使用SAX风格的解析器和C API作为基础的XML解析器(例如libxml2)。创建NSDictionary
s和特别是NSString
s是过于昂贵(恕我直言,不必要)。因此,当从XML解析器中获得的数据结构直接创建Swift容器和Swift字符串时,我们可能至少会保证这些成本。
但是,我不知道创建Swift字符串和Swift字典的代价是多么昂贵。 Swift和它的图书馆仍处于起步阶段。也
编辑
现在可以通过将字典参数声明为NSDictionary而不是Swift字典来解决现在的转换开销。
这只会推迟到其他地方,其中字典值正在使用的地方。他们需要被转换为字符串的地方。 – bouke 2014-10-19 16:07:41
它会避免字典转换,而不是字符串转换。 – 2014-10-19 16:47:23
使用libxml的SAX解析器需要定义回调。目前无法从Swift函数创建C函数指针。那么这甚至可能绕过Objective C和桥接? – bouke 2014-10-19 15:33:36
我担心,传递一个指向Swift函数的指针在C代码中被调用尚不支持。这个问题需要调查。有一个有趣的观察:http://stackoverflow.com/questions/24107099/function-callback-from-c-to-swift。 – CouchDeveloper 2014-10-19 15:43:53
谢谢!虽然目前不可能直接从Swift实现libxml的sax解析器,但可以使用它的ObjC包装器。通过将字符指针传递给Swift,从ObjC到Swift没有太多桥接开销。 – bouke 2014-11-01 09:44:38