简体   繁体   English

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

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

I got interested in this small example of an algorithm in Python for looping through a large word list. 我对Python中这个用于循环遍历大词列表的算法的小例子感兴趣。 I am writing a few "tools" that will allow my to slice a Objective-C string or array in a similar fashion as Python. 我正在写一些“工具”,这将允许我以与Python类似的方式切片Objective-C字符串或数组。

Specifically, this elegant solution caught my attention for executing very quickly and it uses a string slice as a key element of the algorithm. 具体来说, 这个优雅的解决方案引起了我的注意,非常快速地执行,它使用字符串切片作为算法的关键元素。 Try and solve this without a slice! 尝试解决这个没有切片!

I have reproduced my local version using the Moby word list below. 我使用下面的Moby单词列表复制了我的本地版本。 You can use /usr/share/dict/words if you do not feel like downloading Moby. 如果您不想下载Moby,可以使用/usr/share/dict/words The source is just a large dictionary-like list of unique 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      

This script will a) be interpreted by Python; 这个脚本将a)由Python解释; b) read the 4.1 MB, 354,983 word Moby dictionary file; b)读取4.1 MB,354,983字的Moby字典文件; c) strip the lines; c)剥去线条; d) place the lines into a set, and; d)将线条放入一组,并且; e) and find all the combinations where the evens and the odds of a given word are also words. e)并找到所有组合,其中平均值和给定单词的几率也是单词。 This executes in about 0.73 seconds on a MacBook Pro. 这在MacBook Pro上执行约0.73秒。

I tried to rewrite the same program in Objective-C. 我试图在Objective-C中重写相同的程序。 I am a beginner at this language, so go easy please, but please do point out the errors. 我是这种语言的初学者,所以请放轻松,但请指出错误。

#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;
}

The Objective-C version produces the same result, (13,341 words), but takes almost 3 seconds to do it. Objective-C版本产生相同的结果(13,341个单词),但需要将近3秒钟才能完成。 I must be doing something atrociously wrong for a compiled language to be more than 3X slower than a scripted language, but I'll be darned if I can see why. 我必须做一些非常错误的错误,因为编译语言比脚本语言慢了3倍,但如果我能理解为什么,我会感到很沮丧。

The basic algorithm is the same: read the lines, strip them, and put them in a set. 基本算法是相同的:读取线条,剥离它们,并将它们放在一个集合中。

My guess of what is slow is the processing of the NSString elements, but I do not know an alternative. 我对慢速的猜测是对NSString元素的处理,但我不知道另一种选择。

Edit 编辑

I edited the Python to be this: 我编辑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 

For the utf-8 to be on the same plane as the utf-8 NSString. 因为utf-8与utf-8 NSString在同一平面上。 This slowed the Python down to 1.9 secs. 这使Python的速度降低到1.9秒。

I also switch the slice test to short-circuit type as suggested for both the Python and obj-c version. 我还将切片测试切换为短路类型,如Python和obj-c版本所建议的那样。 Now they are close to the same speed. 现在他们接近相同的速度。 I also tried using C arrays rather than NSStrings, and this was much faster, but not as easy. 我也尝试使用C数组而不是NSStrings,这要快得多,但并不容易。 You also loose utf-8 support doing that. 你也可以放弃utf-8支持。

Python is really cool... Python真的很酷......

Edit 2 编辑2

I found a bottleneck that sped things up considerably. 我找到了一个瓶颈,大大加快了速度。 Instead of using the [rtr appendFormat:@"%c",[inString characterAtIndex:i]]; 而不是使用[rtr appendFormat:@"%c",[inString characterAtIndex:i]]; method to append a character to the return string, I used this: 将字符附加到返回字符串的方法,我使用了这个:

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

Now I can finally claim that the Objective-C version is faster than the Python version -- but not by much. 现在我终于可以声称Objective-C版本比Python版本更快 - 但不是很多。

Keep in mind that the Python version has been written to move a lot of the heavy lifting down into highly optimised C code when executed on CPython (especially the file input buffering, string slicing and the hash table lookups to check whether even and odd are in words ). 请记住,Python版本的编写是为了在CPython上执行时将大量繁重工作转移到高度优化的C代码中(特别是文件输入缓冲,字符串切片和哈希表查找,以检查evenodd是否在words )。

That said, you seem to be decoding the file as UTF-8 in your Objective-C code, but leaving the file in binary in your Python code. 也就是说,您似乎在Objective-C代码中将文件解码为UTF-8,但在Python代码中将文件保留为二进制文件。 Using Unicode NSString in the Objective-C version, but 8-bit byte strings in the Python version isn't really a fair comparison - I would expect the performance of the Python version to drop noticeably if you used codecs.open() to open the file with a declared encoding of "utf-8" . 在Objective-C版本中使用Unicode NSString,但Python版本中的8位字节字符串实际上并不是一个公平的比较 - 如果您使用codecs.open()打开,我希望Python版本的性能会明显下降声明编码为"utf-8"

You're also making a full second pass to strip the empty lines in your Objective-C, while no such step is present in the Python code. 您还要进行完整的第二遍以去除Objective-C中的空行,而Python代码中不存在此类步骤。

In BOTH codes you build the even and odd slices then test them against the words. 在BOTH代码中,您构建偶数和奇数切片,然后根据单词测试它们。 It would be better if you built the odd slice only after the even slice succeeds. 如果只在偶数切片成功后构建奇数切片会更好。

Current Python code: 当前的Python代码:

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

Better: 更好:

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

By the way, one metric you didn't mention: How long did it take you to WRITE each code? 顺便说一下,你没有提到一个指标:你需要多长时间来编写每个代码?

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/Reference/Reference.html suggests that you may want to substitute NSSet with CFSet which may improve the performances. http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/Reference/Reference.html建议你可以用NSFS替换CFSet,这样可以提高性能。

I could not find with a quick Google search the implementation used for NSSet / CFSet : if they use a Tree based implementation (same as stdc++ set type), then lookup and check are O(log(N)) whereas Python's set lookup O(1), this could account for the speed difference. 我无法通过快速Google搜索找到用于NSSet / CFSet的实现: 如果他们使用基于树的实现(与stdc ++ set类型相同),则查找和检查是O(log(N))而Python的set lookup O( 1),这可以解释速度差异。

[edit] ncoghlan pointed in a remark below that the sets in objective C use a hash table so you get a constant time lookup also. [编辑] ncoghlan在下面的一条评论中指出,目标C中的集合使用哈希表,因此您也可以获得一个恒定的时间查找。 So this boils down to how efficient the implementation of sets is in Python vs in Objective C. As others pointed out, Python's sets and dictionaries are heavily optimized, esp. 因此,这归结为集合的实现在Objective C中的效率与在Objective C中的效率有关。正如其他人指出的那样,Python的集合和字典都经过了大量优化,尤其是。 when strings are used as keys (the Python dictionaries are used to implement the namespaces and need to be very fast). 当字符串用作键时(Python字典用于实现名称空间并且需要非常快)。

Your python code is running mainly in build in data structures, which are implemented in C. Python contains incredibly sophisticated optimizations for those data types. 您的python代码主要在构建数据结构中运行,这些结构在C中实现.Python包含对这些数据类型的极其复杂的优化。 Look for talks of Raymond Hettinger for more details. 有关更多详情,请查看Raymond Hettinger的演讲。 It is mainly about very efficient hashing of objects, use of those hashes for lookup, special memory allocation strategies, ... 它主要是关于非常有效的对象散列,使用那些散列进行查找,特殊的内存分配策略,......

I had implemented a network search in python just for testing and we were never able to speed it up in C++, C# or a similar language. 我在python中实现了一个网络搜索只是为了测试,我们从来没有能够用C ++,C#或类似语言加速它。 Not being beginners in C++ or C#! 不是C ++或C#的初学者! ;-) ;-)

First of all, CPython's library is written in C, and is highly optimized so it is not surprising that a program which leverages the library runs faster than unoptimized Objective C. 首先,CPython的库是用C语言编写的,并且经过高度优化,因此利用该库的程序比未优化的Objective C运行得更快也就不足为奇了。

The result would be different if you translated the Objective C program line by line into Python. 如果将Objective C程序逐行转换为Python,结果会有所不同。

I suspect that much of the result comes from the fact that the counter is not incremented very often and that it is very efficient for Python to decide that an object is NOT in the set. 我怀疑大部分结果来自于计数器不经常递增的事实,并且Python决定对象不在集合中是非常有效的。 After all, if you take every second letter of a word, it seems rather unlikely that you will end up with a valid English word, let alone one in the same source text. 毕竟,如果你拿一个单词的每一个字母,你似乎不太可能最终得到一个有效的英文单词,更不用说同一个源文本中的一个。

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

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