为什么Python中的程序比Objective-C更快?

时间:2011-04-11 04:46:05

标签: python objective-c nsstring

我对Python中的算法的this small example感兴趣,以便循环一个大的单词列表。我正在写一些“工具”,这将允许我以与Python类似的方式切片Objective-C字符串或数组。

具体来说,this elegant solution引起了我的注意,非常快速地执行,它使用字符串切片作为算法的关键元素。尝试解决这个没有切片!

我使用下面的Moby word list复制了我的本地版本。如果您不想下载Moby,可以使用/usr/share/dict/words。源只是一个类似于字典的大型独特单词列表。

#!/usr/bin/env python

count=0
words = set(line.strip() for line in   
           open("/Users/andrew/Downloads/Moby/mwords/354984si.ngl"))
for w in words:
    even, odd = w[::2], w[1::2]
    if even in words and odd in words:
        count+=1

print count      

这个脚本将a)由Python解释; b)读取4.1 MB,354,983字的Moby字典文件; c)剥去线条; d)将线条放入一组,并且; e)并找到所有组合,其中平均值和给定单词的几率也是单词。这在MacBook Pro上执行约0.73秒。

我试图在Objective-C中重写相同的程序。我是这种语言的初学者,所以请轻松一下,但请指出错误。

#import <Foundation/Foundation.h>

NSString *sliceString(NSString *inString, NSUInteger start, NSUInteger stop, 
        NSUInteger step){
    NSUInteger strLength = [inString length];

    if(stop > strLength) {
        stop = strLength;
    }

    if(start > strLength) {
        start = strLength;
    }

    NSUInteger capacity = (stop-start)/step;
    NSMutableString *rtr=[NSMutableString stringWithCapacity:capacity];    

    for(NSUInteger i=start; i < stop; i+=step){
        [rtr appendFormat:@"%c",[inString characterAtIndex:i]];
    }
    return rtr;
}

NSSet * getDictWords(NSString *path){

    NSError *error = nil;
    NSString *words = [[NSString alloc] initWithContentsOfFile:path
                         encoding:NSUTF8StringEncoding error:&error];
    NSCharacterSet *sep=[NSCharacterSet newlineCharacterSet];
    NSPredicate *noEmptyStrings = 
                     [NSPredicate predicateWithFormat:@"SELF != ''"];

    if (words == nil) {
        // deal with error ...
    }
    // ...

    NSArray *temp=[words componentsSeparatedByCharactersInSet:sep];
    NSArray *lines = 
        [temp filteredArrayUsingPredicate:noEmptyStrings];

    NSSet *rtr=[NSSet setWithArray:lines];

    NSLog(@"lines: %lul, word set: %lul",[lines count],[rtr count]);
    [words release];

    return rtr;    
}

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int count=0;

    NSSet *dict = 
       getDictWords(@"/Users/andrew/Downloads/Moby/mwords/354984si.ngl");

    NSLog(@"Start");

    for(NSString *element in dict){
        NSString *odd_char=sliceString(element, 1,[element length], 2);
        NSString *even_char=sliceString(element, 0, [element length], 2);
        if([dict member:even_char] && [dict member:odd_char]){
            count++;
        }

    }    
    NSLog(@"count=%i",count);

    [pool drain];
    return 0;
}

Objective-C版本产生相同的结果(13,341个单词),但需要将近3秒钟才能完成。我必须做一些非常错误的错误,因为编译语言比脚本语言慢了3倍,但如果我能理解为什么,我会感到很沮丧。

基本算法是相同的:读取线条,剥离它们,然后将它们放入一组中。

我对NSString元素处理速度慢的猜测,但我不知道另一种选择。

修改

我将Python编辑为:

#!/usr/bin/env python
import codecs
count=0
words = set(line.strip() for line in 
     codecs.open("/Users/andrew/Downloads/Moby/mwords/354984si.ngl",
          encoding='utf-8'))
for w in words:
    if w[::2] in words and w[1::2] in words:
        count+=1

print count 

使utf-8与utf-8 NSString在同一平面上。这使Python的速度降低到1.9秒。

对于Python和obj-c版本,我还将切片测试切换为suggested的短路类型。现在他们接近相同的速度。我也尝试使用C数组而不是NSStrings,这要快得多,但并不容易。你也可以放弃utf-8支持。

Python非常酷......

修改2

我发现了一个瓶颈,大大加快了速度。我没有使用[rtr appendFormat:@"%c",[inString characterAtIndex:i]];方法将字符附加到返回字符串,而是使用了这个:

for(NSUInteger i=start; i < stop; i+=step){
    buf[0]=[inString characterAtIndex:i];
    [rtr appendString:[NSString stringWithCharacters:buf length:1]];
}

现在我可以最终声称Objective-C版本比Python版本更快 - 但不是很多。

5 个答案:

答案 0 :(得分:9)

请记住,Python版本已被编写为在CPython上执行时将大量繁重工作转移到高度优化的C代码中(特别是文件输入缓冲,字符串切片和哈希表查找以检查是否{{ 1}}和even位于odd)。

也就是说,您似乎在Objective-C代码中将文件解码为UTF-8,但在Python代码中将文件保留为二进制文件。在Objective-C版本中使用Unicode NSString,但是Python版本中的8位字节字符串实际上并不是一个公平的比较 - 如果您使用words打开,我会期望Python版本的性能显着下降声明编码为codecs.open()的文件。

您还要进行完整的第二遍以剥离Objective-C中的空行,而Python代码中不存在此类步骤。

答案 1 :(得分:2)

在BOTH代码中,您构建偶数和奇数切片,然后根据单词测试它们。如果你在偶数切片成功后才构建奇数切片会更好。

当前的Python代码:

even, odd = w[::2], w[1::2]
if even in words and odd in words:

更好:

# even, odd = w[::2], w[1::2]
if w[::2] in words and w[1::2] in words:

顺便说一下,你没有提到一个指标:你需要多长时间来编写每个代码?

答案 2 :(得分:2)

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/Reference/Reference.html表示您可能希望将NSSet替换为CFSet,这可能会提高性能。

我无法通过快速Google搜索找到用于NSSet / CFSet的实现:如果他们使用基于树的实现(与stdc ++集类型相同),则查找和检查是O(log (N))而Python的集合查找O(1),这个可以说明速度差异。

[编辑] ncoghlan在下面的一条评论中指出,目标C中的集合使用哈希表,因此您也可以获得一个恒定的时间查找。因此,这归结为集合的实现在Objective C中的效率与在Objective C中的效率有关。正如其他人指出的那样,Python的集合和字典都经过了大量优化,尤其是。当字符串用作键时(Python字典用于实现名称空间并且需要非常快)。

答案 3 :(得分:1)

您的python代码主要在构建数据结构中运行,这些结构在C中实现.Python包含对这些数据类型的极其复杂的优化。有关更多详情,请查看Raymond Hettinger的演讲。它主要是关于对象的非常有效的散列,使用那些散列进行查找,特殊的内存分配策略,......

我在python中实现了一个网络搜索只是为了测试,我们从来没有能够用C ++,C#或类似语言加速它。不是C ++或C#的初学者! ; - )

答案 4 :(得分:0)

首先,CPython的库是用C语言编写的,并且经过高度优化,因此利用该库的程序比未经优化的Objective C运行得更快也就不足为奇了。

如果将Objective C程序逐行转换为Python,结果会有所不同。

我怀疑大部分结果来自于计数器不经常递增的事实,并且Python决定对象不在集合中是非常有效的。毕竟,如果你拿一个单词的每一个字母,你似乎不太可能最终得到一个有效的英文单词,更不用说同一个源文本中的一个。