Почему эта программа работает быстрее в Python, чем Objective-C?
Я заинтересовался этим небольшим примером алгоритма в Python для прокрутки большого списка слов. Я пишу несколько "инструментов", которые позволят мне нарезать строку или массив Objective-C так же, как Python.
В частности, это элегантное решение привлекло мое внимание для выполнения очень быстро, и он использует строковый срез в качестве ключевого элемента алгоритма. Попробуйте и решите это без кусочка!
Я воспроизвел свою локальную версию, используя список слов Моби > ниже. Вы можете использовать /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
Этот script будет a) интерпретироваться Python; б) читать файл словаря Moby объемом 4.1 MB, 354 983 слов; c) линять линии; 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, но я не знаю альтернативы.
Edit
Я редактировал 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, но не сильно.
Ответы
Ответ 1
Имейте в виду, что версия 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
В кодах 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:
Кстати, один показатель, который вы не упомянул: сколько времени вам понадобилось, чтобы ПЕЧАТЬ каждый код?
Ответ 3
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/Reference/Reference.html предполагает, что вы можете заменить NSSet на CFSet, который может улучшить производительность.
Я не смог найти с помощью быстрого поиска Google реализацию, используемую для NSSet/CFSet: , если они используют реализацию на основе дерева (то же, что и тип установки stdС++), затем поиск и проверка - это O (log (N)), тогда как Python устанавливает поиск O (1), этот может учитывать разницу в скорости.
[edit] ncoghlan указал в примечании ниже, что наборы в объективе C используют хеш-таблицу, чтобы вы также получали постоянный поиск времени. Таким образом, это сводится к тому, насколько эффективна реализация наборов в Python и в Objective C. Как указывали другие, наборы Python и словари сильно оптимизированы, особенно. когда строки используются как ключи (словари Python используются для реализации пространств имен и должны быть очень быстрыми).
Ответ 4
Ваш код python работает в основном в сборке структур данных, которые реализованы в C. Python содержит невероятно сложные оптимизации для этих типов данных. Посмотрите на переговоры с Раймондом Хеттингером для получения более подробной информации. В основном это очень эффективное хеширование объектов, использование этих хэшей для поиска, специальные стратегии распределения памяти,...
Я реализовал сетевой поиск в python только для тестирования, и мы не смогли ускорить его в С++, С# или аналогичном языке. Не будучи новичками на С++ или С#!; -)
Ответ 5
Прежде всего, библиотека CPython написана на C и сильно оптимизирована, поэтому неудивительно, что программа, которая использует библиотеку, работает быстрее, чем неоптимизированный Objective C.
Результат будет другим, если вы переводите программу Objective C по строкам в Python.
Я подозреваю, что большая часть результата исходит из того факта, что счетчик не увеличивается очень часто, и что для Python очень эффективно решать, что объект НЕ установлен в наборе. В конце концов, если вы берете каждую вторую букву слова, кажется маловероятным, что вы закончите с действительным английским словом, не говоря уже о одном в том же исходном тексте.