【问题标题】:Why is this program faster in Python than Objective-C?为什么这个程序在 Python 中比 Objective-C 更快?
【发布时间】:2011-04-11 04:46:05
【问题描述】:

我对@9​​87654321@ 的 Python 算法感兴趣,该算法用于遍历一个大的单词列表。我正在编写一些“工具”,它们允许我以与 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 版本快——但速度并不快。

【问题讨论】:

  • 在阅读了 NSString 之后(请参阅我的建议答案),当您在 Python 版本中使用 codecs.open() 时,我很想知道这些数字是多少。
  • Objective C 在看到那个简短的 Python 小程序后确实看起来很丑!
  • Objective-C 有一张只有妈妈才会喜欢的脸,我想...

标签: python objective-c nsstring


【解决方案1】:

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

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

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

【讨论】:

  • 我同意你的怀疑,但从另一端开始。该文件是纯 7 位 ASCII(我真的很怀念那些日子!)我使用 NSASCIIStringEncoding 绑定 Cocoa 中的编码器。小但明显的速度增加。我现在正在使用 unichar 的纯 C 数组,而且速度很快。当我有更明确的结果时,我会发布我的结果。谢谢!
【解决方案2】:

在这两种代码中,您都构建了偶数和奇数切片,然后根据单词对它们进行测试。如果在偶数切片成功后才构建奇数切片会更好。

当前 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:

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

【讨论】:

  • 写 Python 花了几分钟; obj-c 几个小时!不完全是重点。我对 Python 的理解非常好,并且知道我会非常想念 obj-c 中的 slices 和 interables。我写了这个小东西来计算字符串切片...
  • 顺便说一句:您的 Python 注释将 Python 从 0.75 秒增加到 0.63 秒。非常感谢!我正在尝试加快 Obj-C 的速度!!! :-J
【解决方案3】:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/Reference/Reference.html 建议您可能想用 CFSet 替换 NSSet,这可能会提高性能。

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

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

【讨论】:

  • 在树中的每次查找或插入都应该是 O(logN),而在 Python 集中应该是 O(1)。有 O(N) 次查找/插入。因此,一棵树的总时间为 O(N.logN),而一组的总时间为 O(N)——没有你想象的那么糟糕。
  • @john machin:感谢您的提醒,我相应地编辑了答案(我保证在早上喝咖啡之前我永远不会再对算法复杂性发表声明......)
  • 我对 NSSet 算法复杂性的参考信息进行了同样的搜索,但运气不佳。我确实发现的一个提示是 NSSet 需要元素来定义哈希,而不是排序比较,因此基于哈希表的实现似乎很可能。在刚才的第二次尝试中,我想我在这里找到了算法复杂性的确认:developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
【解决方案4】:

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

我在 python 中实现了一个网络搜索,只是为了进行测试,但我们永远无法用 C++、C# 或类似语言加速它。不是 C++ 或 C# 的初学者! ;-)

【讨论】:

    【解决方案5】:

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

    如果将Objective C程序逐行翻译成Python,结果会有所不同。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-21
      • 1970-01-01
      • 1970-01-01
      • 2014-09-13
      • 1970-01-01
      • 2014-11-10
      • 2014-09-09
      相关资源
      最近更新 更多