Почему эта программа на Python быстрее, чем Objective-C? - PullRequest
18 голосов
/ 11 апреля 2011

Я заинтересовался этим небольшим примером алгоритма в Python для циклического прохождения большого списка слов. Я пишу несколько «инструментов», которые позволят мне нарезать строку или массив Objective-C аналогично Python.

В частности, это элегантное решение привлекло мое внимание к очень быстрому выполнению, и оно использует срез строки в качестве ключевого элемента алгоритма. Попробуйте и решить это без среза!

Я воспроизвел свою локальную версию, используя список слов Moby ниже. Вы можете использовать /usr/share/dict/words, если не хотите загружать Moby. Источник - просто большой словарный список уникальных слов.

#!/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      

Этот скрипт будет а) интерпретироваться Python; b) прочитайте файл словаря Moby размером 4,1 МБ, 354 983 слова; в) лишить линии; d) поместите линии в набор и; д) и найти все комбинации, где четные и вероятные значения данного слова также являются словами. Это выполняется примерно за 0,73 секунды на MacBook Pro.

Я пытался переписать ту же программу в 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. Это замедлило Питона до 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, но ненамного.

Ответы [ 5 ]

9 голосов
/ 11 апреля 2011

Имейте в виду, что версия Python была написана для того, чтобы перенести большую часть тяжелой работы в высокооптимизированный C-код при выполнении на CPython (особенно буферизацию файлового ввода, нарезку строк и поиск в хеш-таблице, чтобы проверить, является ли even и odd находятся в words).

Тем не менее, вы, кажется, декодируете файл как UTF-8 в своем коде Objective C, но оставляете файл в двоичном виде в своем коде Python.Использование Unicode NSString в версии Objective-C, но 8-битные строки байтов в версии Python не совсем справедливое сравнение - я ожидаю, что производительность версии Python заметно снизится, если вы использовали codecs.open() для открытия файлас объявленной кодировкой "utf-8".

Вы также делаете полный второй проход, чтобы удалить пустые строки в Objective-C, в то время как в коде Python такого шага нет.

2 голосов
/ 11 апреля 2011

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), этот может учитывать разницу в скорости.

[edit] ncoghlan указал в примечании ниже, что множества в задаче C используют хеш-таблицу, так что вы также получаете поиск с постоянным временем. Таким образом, это сводится к тому, насколько эффективна реализация наборов в Python по сравнению с Objective C. Как уже указывали другие, наборы и словари Python сильно оптимизированы, особенно. когда строки используются в качестве ключей (словари Python используются для реализации пространств имен и должны быть очень быстрыми).

2 голосов
/ 11 апреля 2011

В обоих кодах вы строите четные и нечетные фрагменты, а затем проверяете их на соответствие словам.Было бы лучше, если бы вы построили нечетный срез только после успешного выполнения четного среза.

Текущий код 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:

Кстатиодна метрика, которую вы не упомянули: сколько времени вам потребовалось НАПИСАТЬ каждый код?

1 голос
/ 11 апреля 2011

Ваш код на python выполняется в основном для построения структур данных, которые реализованы на C. Python содержит невероятно сложные оптимизации для этих типов данных. Ищите разговоры о Раймонде Хеттингере для более подробной информации. В основном речь идет об очень эффективном хешировании объектов, использовании этих хэшей для поиска, специальных стратегиях выделения памяти, ...

Я реализовал сетевой поиск в python только для тестирования, и мы никогда не могли ускорить его на C ++, C # или подобном языке. Не быть новичками в C ++ или C #! ; -)

0 голосов
/ 11 апреля 2011

Прежде всего, библиотека CPython написана на C и высоко оптимизирована, поэтому неудивительно, что программа, использующая библиотеку, работает быстрее, чем неоптимизированная Objective C.

Результат будет другим, если вы переведете программу Objective C построчно на Python.

Я подозреваю, что большая часть результата происходит из-за того, что счетчик не очень часто увеличивается и что для Python очень эффективно решить, что объект НЕ находится в наборе. В конце концов, если вы берете каждую вторую букву слова, кажется маловероятным, что в результате вы получите правильное английское слово, не говоря уже о том, которое содержится в том же исходном тексте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...