Выбрать главу

Код выглядит так:

#include <stdio.h>

#include <time.h>

int main() {

clock_t start = clock();

unsigned long long int sum = 0, i;

for (i = 1; i < 1000001; i + +) {

sum += i * i;

}

clock_t end = clock();

printf("Sum = %llu, T = %fs", sum, (float)(end - start)/CLOCKS_PER_SEC);

return 0;

}

Как можно видеть, он ненамного сложнее python-версии. Перед запуском программы, ее надо скомпилировать, выполнив команду C:\GCC\bin\gcc.exe "Appendix-2 - speedTest.c" -o"Appendix-2 - speedTest". Результат очевиден: T = 0.007 секунд. И еще чуть-чуть: добавляем флаг оптимизации по скорости, выполнив команду C:\GCC\bin\gcc.exe "Appendix-2 - speedTest.c" -o"Appendix-2 - speedTest" -O3. Результат: 0.0035 секунд, разница в быстродействии более 100 раз!

Увы, в более сложных задачах такого прироста реально не бывает (в последнем примере очень короткий код, который видимо полностью помещается в кеш-памяти процессора), но на некоторое улучшение быстродействия можно рассчитывать. Хотя переписывание программы - это крайний случай, сначала целесообразно поискать стандартные библиотеки, которые возможно уже решают данную задачу. К примеру, следующий код на языке Python вычисляет сумму элементов массива за 0.1с:

a = xrange(1000001)

s = 0

for x in a:

s += x

print(s)

Можно использовать встроенную функцию sum:

a = range(1000001)

s = sum(a)

print(s)

Данный код выполняется за 0.02 секунды, т.е. в 5 раз быстрее первого варианта.

Вопрос, что выбрать, на самом деле, не так однозначен - язык Си быстрее, но и сложнее в использовании. Если заранее известно, что задача состоит в обработке большого набора чисел (например поиск простых чисел или магических квадратов), то может быть более целесообразным сразу писать программу на Си или С++, в принципе это не намного сложнее, а работать программа будет быстрее. С другой стороны, при наличии в программе сложной логики или структур данных, написание ее на Python будет гораздо проще. Поэтому, на практике целесообразно прототип программы делать на языке Python, и только в том случае, если проблема быстродействия становится критичной и не решаемой доступными средствами, переходить на более низкоуровневые средства разработки. Также рекомендуется поискать решения среди уже написанных Python-библиотек - для многих задач такие библиотеки уже написаны на языке C++, и их можно использовать, и они работают довольно-таки быстро.

Для тех, кто захочет максимально широко использовать Python в математических расчетах, крайне рекомендуется ознакомиться с библиотекой numpy, она является стандартом де-факто для всех серьезных расчетов.

Теперь посмотрим, как еще можно ускорить Python-программу с помощью многопоточности и других хитростей.

Приложение 3 - Пример ускорения Python-программы

Рассмотрим практический пример сравнения скорости вычислений на Python. Допустим, есть массив целых чисел, для каждого числа надо найти количество его делителей.

Функцию нахождения делителей числа будем использовать “как есть”, без какой-либо оптимизации: перебираем все числа, если число делится без остатка, увеличиваем счетчик.

def get_dividers(n):

divs = 0

for i in xrange(1, n+1):

if n % i == 0:

divs += 1

return divs

Рассмотрим способы решения задачи. Исходные данные: массив целых чисел:

values = [41212317, 4672313, 4342311, 46512319, 51212317, 5672313, 5342311, 56512319]

Способ-1

Решение “в лоб” (заодно показан способ измерения времени выполнения).

import timeit

start_time = timeit.default_timer()

res = []

for v in values:

res.append(get_dividers(v))

print(res)

print("T =", timeit.default_timer() - start_time)

Время выполнения кода на компьютере с процессором Core i7: 10c.

Способ-2

Используем функцию map, позволяющую применить функцию сразу к массиву.

res = map(get_dividers, values)

print(list(res))

Время выполнения: те же 10с, пока мы выиграли разве что в краткости записи.

Способ-3

Используем многопоточность, класс multiprocessing.Pool, позволяющий разбить вычисления над массивом на несколько потоков (способ работает только в Python 3).

if __name__ == '__main__':

p = multiprocessing.Pool(processes=4)

res = p.map(get_dividers, values)

print(list(res))

Время выполнения: 3.7с, что уже лучше. Интересно, что несмотря на разбивку на 4 процесса, реальное время выполнения уменьшилось лишь вдвое. Многопоточные вычисления - достаточно дорогостоящая в плане накладных расходов операция, множество ресурсов процессора тратится на синхронизацию и передачу данных между процессами.