繁体   English   中英

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

[英]Why is this program faster in Python than Objective-C?

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

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

我使用下面的Moby单词列表复制了我的本地版本。 如果您不想下载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版本所建议的那样。 现在他们接近相同的速度。 我也尝试使用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版本更快 - 但不是很多。

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

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

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

在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:

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

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/Reference/Reference.html建议你可以用NSFS替换CFSet,这样可以提高性能。

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM