Прискорення коду Python засобами самої мови

Найчастіше пропонують такі рішення:
- Використати Psyco
- Переписати частину програми на C за допомогою Python C Extensions
- Змінити мозок алгоритм
То що робити?
Тоді, якщо для вашого проекту вищезазначені методи не підійшли, що робити? Міняти Python іншою мовою? Ні, здаватися не можна. Оптимізуватимемо сам код.Приклади будуть взяті з програми, що будує безліч Мандельброта заданого розміру із заданим числом ітерацій. Час роботи вихідної версії при параметрах 600*600 пікселів, 100 ітерацій становив3.07 сек, цю величину ми візьмемо за 100%
Скажу заздалегідь, частина оптимізацій призведе до того, що код стане меншим за pythonic, і вибачать мені адепти python-way.
Крок 0. Винесення основного коду програми на окрему
Цей крок допомагає інтерпретатору python краще проводити внутрішні оптимізації для запуску, а й при використанні psyco цей крок може сильно допомогти, т.к. psyco оптимізує лише функції, не торкаючись основне тіло програми. Якщо раніше розрахункова частина вихідної програми виглядала так:
Те, змінивши її на:
ми отримали час2.4 сек, тобто.78%від вихідного.
Крок 1. Профілювання
Стандартна бібліотека Python'a, це просто клондайк найкорисніших модулів. Зараз нас цікавить модуль cProfile, завдяки якому профільування коду стає простим і навіть цікавим заняттям. Повну документацію по цьому модулю можна знайти тут, нам вистачить пари простих команд.
python -m cProfile sample.pyКлючІнтерпетатор -m дозволяє запускати модулі як окремі програми, якщо сам модуль надає таку можливість.
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function) 1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot) . З її допомогою, легко визначити місця, що вимагають оптимізації (рядки з найбільшими значеннями ncalls (кількість викликів функції), tottime і percall (час роботи всіх викликів даної функції та кожного окремого відповідно)).
Для зручності можна додати ключ -s time, відсортувавши висновок профілювача за часом виконання.
У моєму випадку цікавою частиною висновку було (час виконання відрізняється від зазначеного вище, тому що профільник додає свій «оверхед»): 4613944 function calls (4613943 primitive calls) in 2.818 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function) 1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot) 3533224 0.296 0.000 0.090 0.000 00 0.081 0.000 360 000 0.044 0.000 0.044 0.000 360000 0.036 0.000 0.036 0.000 . Отже, профіль отриманий, тепер займемося оптимізацією впритул.
Крок 2. Аналіз профілю
Бачимо, що на першому місці за часом стоїть наша основна функція mandelbrot, за нею йде системна функція abs, за нею кілька функцій з модуля math, далі одиночні виклики функцій, з мінімальними витратами часу, вони нам не цікаві.
Отже, системні функції, «вилізані» спільнотою, нам навряд чи вдасться покращити, тож перейдемо до нашого власного коду:
Крок 3. Математика
Зараз код виглядає так:
Зауважимо, що оператор зведення ступінь ** — досить «загальний», нам необхідно лише зведення на другий ступінь, тобто. всі конструкції виду x**2 можна замінити на х*х, вигравши таким чином ще трохи часу. Подивимося на час:1.9 сек, або62%початкового часу, досягнуто простою заміною двох рядків:
Кроки 5, 6 та 7. Маленькі, але важливі
Прописна істина, про яку знають усі програмісти на Python - робота з глобальними змінними повільнішими роботи з локальними. Але часто забувається факт, що це правильно не тільки для змінних, але й взагалі для всіх об'єктів. У коді функції йдуть виклики кількох функцій із модуля math. То чому б не імпортувати їх у самій функції? Зроблено:
Ще 0.1сек відвойовано. Згадаймо, що abs(x) поверне число типу float. Так що і порівнювати його варто з float, а не int:
Ще 0.15сек.53%від початкового часу.
І, зрештою, брудний хак. У цій задачі, можна зрозуміти, що нижня половина зображення, дорівнює верхній, т.ч. число обчислень можна скоротити вдвічі, отримавши в результаті0.84секабо27%від вихідного часу.
Висновок
Профілюйте. Використовуйте timeit. Оптимізуйте. Python — потужна мова, і програми нею працюватимуть зі швидкістю, пропорційною вашому бажанню розібратися і все відполірувати:) Мета даної статті, показати, що за рахунок дрібних і незначних змін, таких як заміна ** на *, можна змусити зеленого змія повзати до двох разів швидше, без застосування важкої артилерії у вигляді Сі, або шаманств psyco. Також, можна поєднати різні засоби, такі як вищезазначені оптимізації та модуль psyco, гірше не стане:)
UPDКорисне посилання у коментарях навів funca.
Хардкорна конфа за С++. Ми запрошуємо лише профі.