Как я могу ускорить мою программу Perl?
Это на самом деле два вопроса, но они настолько похожи, и, чтобы было проще, я решил, что я просто свожу их вместе:
Во-первых: учитывая устоявшийся проект Perl, какие есть приличные способы ускорить его помимо простой оптимизации в коде?
Во-вторых: при написании программы с нуля на Perl, какие есть хорошие способы значительно повысить производительность?
Что касается первого вопроса, представьте, что вы получили достойно написанный проект, и вам нужно улучшить производительность, но вы не можете получить большую выгоду от рефакторинга / оптимизации. Что бы вы сделали, чтобы ускорить его в этом случае, за исключением переписывания чего-то вроде C?
Пожалуйста, держитесь подальше от общих методов оптимизации, если они не специфичны для Perl.
Я спрашивал об Python ранее и подумал, что было бы неплохо сделать это для других языков (мне особенно любопытно, есть ли следствия для psycho и pyrex для Perl).
12 ответов
Пожалуйста, помните правила Клуба оптимизации:
- Первое правило Клуба оптимизации - вы не оптимизируете.
- Второе правило Клуба оптимизации - вы не оптимизируете без измерения.
- Если ваше приложение работает быстрее, чем основной транспортный протокол, оптимизация завершена.
- Один фактор за один раз.
- Нет маркетроидов, нет графиков Marketroid.
- Тестирование будет продолжаться столько, сколько потребуется.
- Если это ваша первая ночь в Клубе оптимизации, вы должны написать контрольный пример.
Итак, если у вас действительно есть рабочий код, запустите вашу программу под Devel:: NYTProf.
Найдите узкие места. Тогда возвращайся сюда, чтобы рассказать нам, кто они.
Если у вас нет рабочего кода, сначала запустите его. Самая большая оптимизация, которую вы когда-либо сделаете, - это переход от нерабочего к рабочему.
Энди уже упомянул Devel:: NYTProf. Это круто Действительно, действительно круто. Используй это.
Если по какой-то причине вы не можете использовать Devel::NYTProf
тогда вы можете вернуться к старому доброму Devel:: DProf, который уже давно входит в стандартную комплектацию Perl. Если у вас есть истинные функции (в математическом смысле), для вычисления которых требуется много времени (например, числа Фибоначчи), то вы можете обнаружить, что Memoize обеспечивает некоторое улучшение скорости.
Много плохой производительности происходит из-за неподходящих структур данных и алгоритмов. Хороший курс по информатике может очень помочь здесь. Если у вас есть два способа работы и вы хотите сравнить их производительность, модуль Benchmark также может оказаться полезным.
Следующие советы по Perl также могут оказаться здесь полезными:
- Сортировка с дорогими сравнениями
- Профилирование с Devel:: DProf
- Биг-О нотация и алгоритмическая сложность
- Поиск предметов в большом списке
- Основы бенчмаркинга
- Memoizing
Отказ от ответственности: я написал некоторые из ресурсов выше, поэтому я могу быть предвзятым к ним.
Есть много вещей, которые вы могли бы улучшить, поэтому сначала вы должны выяснить, что медленно. Другие уже ответили на этот вопрос. Я немного об этом говорю и в мастеринге Perl.
Неполный список вещей, о которых стоит подумать, когда вы пишете новый код:
Профиль с чем-то вроде Devel:: NYTProf, чтобы увидеть, где вы проводите большую часть своего времени в коде. Иногда это удивительно и легко исправить. Освоение Perl дает много советов по этому поводу.
Perl должен компилировать исходный код каждый раз, и компиляция может быть медленной. Он должен найти все файлы и так далее. См., Например, "Своевременный запуск", Жан-Луи Леруа, где он ускоряет все, просто оптимизируя расположение модулей в
@INC
, Если ваши начальные затраты дороги и неизбежны, вы также можете посмотреть на постоянные perls, такие как pperl, mod_perl и так далее.Посмотрите на некоторые модули, которые вы используете. У них есть длинные цепочки зависимостей, чтобы делать простые вещи? Конечно, нам не нравится повторное изобретение, но если колесо, которое вы хотите поставить на свой автомобиль, также поставляется с тремя лодками, пятью козами и чизбургером, возможно, вы захотите построить собственное колесо (или найти другое),
Вызов метода может быть дорогим. Например, в тестовом наборе Perl::Critic его вызовы
isa
замедляет вещи. Это не то, чего вы действительно можете избежать во всех случаях, но это то, что нужно иметь в виду. У кого-то была замечательная цитата, которая звучала примерно так: "Никто не возражает отказаться от коэффициента 2; это плохо, когда у вас десять человек делают это".:) Perl v5.22 имеет некоторые улучшения производительности для этого.Если вы снова и снова вызываете одни и те же дорогостоящие методы, но получаете одни и те же ответы, вам может пригодиться что-то вроде Memoize. Это прокси для вызова метода. Если это действительно функция (то есть один и тот же ввод дает одинаковый вывод без побочных эффектов), вам не нужно вызывать ее повторно.
Модули, такие как Apache:: DBI, могут повторно использовать дескрипторы базы данных, чтобы избежать дорогостоящего открытия соединений с базой данных. Это действительно простой код, поэтому взгляд внутрь может показать вам, как это сделать, даже если вы не используете Apache.
Perl не выполняет для вас оптимизацию хвостовой рекурсии, поэтому не думайте, что вы создадите эти сверхбыстрые рекурсивные алгоритмы. Вы можете легко превратить их в итеративные решения (об этом мы поговорим в Intermediate Perl).
Посмотрите на свои регулярные выражения. Много открытых квантификаторов (например,
.*
) может привести к большому откату. Ознакомьтесь с регулярными выражениями Джеффри Фрейдла для всех кровавых подробностей (и на нескольких языках). Также проверьте его регулярное выражение сайта.Знайте, как ваш Perl компилируется. Вам действительно нужны потоки и
DDEBUGGING
? Это немного замедляет тебя. Проверьте утилиту perlbench, чтобы сравнить различные двоичные файлы perl.Сравните ваши приложения с различными Perls. Некоторые новые версии имеют ускорения, но также некоторые старые версии могут быть быстрее для ограниченного набора операций. У меня нет особого совета, так как я не знаю, что вы делаете.
Разложи работу. Можете ли вы выполнить некоторую асинхронную работу в других процессах или на удаленных компьютерах? Пусть ваша программа работает над другими вещами, так как кто-то еще решает некоторые подзадачи. В Perl есть несколько асинхронных и перераспределяющих модулей. Остерегайтесь, однако, что леса, чтобы сделать это хорошо, могут потерять какую-либо выгоду от этого.
Без необходимости переписывать большие куски, вы можете использовать Inline::C для преобразования любой медленной подпрограммы в C. Или напрямую использовать XS. Также возможно постепенно преобразовывать сабвуферы с помощью XS. PPI / PPI:: XS делает это, например.
Но переход на другой язык - это всегда последнее средство. Может быть, вам стоит попросить опытного программиста на Perl взглянуть на ваш код? Скорее всего, он (и) обнаружит некоторую особенность, которая серьезно подрывает вашу производительность. Кроме этого, профиль вашего кода. Помните, серебряной пули нет.
Что касается psyco и pyrex: нет, для Perl нет эквивалента.
Профилируйте ваше приложение - используя, например, профилировщик, упомянутый выше. Затем вы увидите, где время идет
Если время тратится на выполнение операций, отличных от использования ЦП, сначала нужно их уменьшить - ЦП легко масштабируется, а другие нет.
Я обнаружил, что некоторые операции выполняются особенно медленно:
keys()
на большой хеш очень плохоИспользование
Data::Dumper
для отладки. Использование этой функции на большой структуре очень медленно. Избегайте этого, если можете. Мы видели код, который делает:use Data::Dumper; $debugstr = Dumper(\%bighash); if ($debugflag_mostlyoff) { log($debugstr); }
Большинство модулей имеют альтернативы с разными характеристиками производительности - некоторые буквально ужасно плохо сосут.
- Некоторые регулярные выражения могут быть очень медленными (много. * И т. Д.) И могут быть заменены эквивалентными, которые работают быстрее. Регулярные выражения довольно просты в модульном тестировании и тестировании производительности (просто напишите программу, которая запускает ее в цикле для большого имитированного набора данных). Лучшие регулярные выражения начинаются с чего-то, что может быть проверено очень быстро, например с литеральной строки. Иногда лучше сначала не искать то, что вы ищете, а "оглянуться назад", чтобы проверить, действительно ли это то, что вы ищете. Оптимизация регулярных выражений - это черное искусство, в котором я не очень хорош.
Не рассматривайте переписывание чего-либо на С, кроме как в крайнем случае. Вызов C из Perl (или наоборот) имеет относительно большие издержки. Если вы можете получить быструю реализацию Perl, это лучше.
Если вы что-то переписываете на C, попробуйте сделать это таким образом, чтобы минимизировать накладные расходы на вызовы и вызовы времени выполнения perl (например, функции SV* в основном копируют строки). Одним из способов достижения этого является создание функции C, которая делает больше и вызывает ее меньше раз. Копирование строк в памяти не круто.
С другой стороны, переписывание чего-либо в C сопряжено с большим риском, поскольку вы можете вводить новые режимы сбоев, например утечки памяти, сбои, проблемы безопасности.
Это только половина относится к вашему вопросу - но в интересах документации я выложу его здесь.
Недавнее исправление CentOS/Perl увеличило скорость нашего приложения более чем в два раза. Это необходимо для любого, кто использует CentOS Perl и использует функции bless/overload.
Эссе, достойное прочтения на эту тему, - речь Николаса Кларка " Когда Perl недостаточно быстр" (PDF). Некоторые пункты немного устарели, например, ссылка на Devel::DProf, но имейте в виду, что она была написана в 2002 году.
Тем не менее, большая часть материала остается актуальной.
Вызовы методов и подпрограмм не являются бесплатными в Perl. Они относительно дорогие. Итак, если ваше профилирование окажется, что вы тратите достаточно большую часть времени выполнения в методах с малым доступом, возможно, стоит обратить внимание на микрооптимизацию.
Однако, что вы не должны делать, это заменять методы доступа, такие как get_color() здесь:
package Car;
# sub new {...}
sub get_color {
my $self = shift;
return $self->{color};
}
package main;
#...
my $color = $car->get_color();
с прямым доступом, нарушающим инкапсуляцию:
my $color = $car->{color};
Можно было бы подумать, что это само собой разумеется, но каждый также видит, что это делается повсеместно. Вот что вы можете сделать, используя Class:: XSAccessor
package Car;
# sub new {...}
use Class::XSAccessor
getters => {
get_color => 'color',
},
setters => {
set_color => 'color',
};
Это создает новые методы get- и set_color(), которые реализованы в XS и, таким образом, примерно в два раза быстрее, чем ваша ручная версия. Мутаторы (то есть "$car->color('red')") также доступны, как и цепочечные методы.
В зависимости от вашего приложения, это может дать вам очень небольшое (но по сути бесплатное) повышение. Не ожидайте больше 1-2%, если вы не делаете что-то особенное.
Лучший способ заставить вашу программу работать быстрее - заставить вашу программу выполнять меньше работы. Выберите правильный алгоритм для работы. Я видел много медленных приложений, потому что они выбирают тупой алгоритм в некоторой области кода, который вызывается миллионы раз. Когда вы выполняете миллион * миллион операций вместо миллиона операций, ваша программа будет работать в миллион раз медленнее. В прямом смысле.
Например, вот некоторый код, который я видел, который вставляет элемент в отсортированный список:
while(my $new_item = <>){
push @list, $new_item;
@list = sort @list;
... use sorted list
}
сортировка - это O(n log n). Вставка в отсортированный список - это O(log n).
Исправьте алгоритм.
Наиболее экономически эффективным методом может быть рассмотрение более быстрого аппаратного обеспечения (=> соответствующая аппаратная архитектура). Я говорю не о более быстрых процессорах, а о более быстрых дисках, более быстрых сетях… быстрее всего, что действительно ускоряет ввод / вывод.
Я испытал это много лет назад, когда мы переместили приложение на основе XML-анализа (современная технология в то время
Как всегда, рассмотрим
- производительность разработчика (сколько времени занимает код, насколько сложна проблема, поддерживается ли результат),
- Производительность оборудования,
- Производительность программного обеспечения
и улучшить, где наиболее (стоимость!) эффективна для решения проблемы...
Если ваш код нуждается в ускорении, то есть вероятность, что ваш набор тестов тоже. Этот разговор затрагивает ключевые моменты:
Дамп Perl и использовать Golang. Я изменил свою программу, чтобы использовать Go, и это увеличило время выполнения в 34 раза. Это время выполнения Perl.
реальный 0m16.724s
Это время Го.
реальный 0m0.425s