Как ускорить цикл for в python с помощью Cython

Я пытаюсь сделать датчик, используя Beaglebone Black (BBB) ​​и Python. Мне нужно получить как можно больше данных в секунду с датчика. Приведенный ниже код позволяет мне собирать около 100 000 точек данных в секунду.

import Adafruit_BBIO_GPIO as GPIO
import time 

GPIO.setup("P8_13", GPIO.IN)

def get_data(n):
    my_list = []
    start_time = time.time()
    for i in range(n):
        my_list.append(GPIO.input("P8_13"))
    end_time = time.time() - start_time
    print "Time: {}".format(end-time)
    return my_list

n = 100000
get_data(n)

Если n = 1 000 000, для заполнения my_list требуется около 10 секунд, что соответствует той же скорости, когда n = 100 000 и время = 1 с.

Я решил попробовать Cython, чтобы получить лучшие результаты. Я слышал, что это может значительно ускорить код Python. Я следовал базовому руководству по Cython: создал файл data.pyx с приведенным выше кодом Python, затем создал setup.py и, наконец, создал файл Cython.

К сожалению, мне это совсем не помогло. Итак, мне интересно, использую ли я Cython неуместно или в этом случае, когда нет «тяжелых математических вычислений», Cython не может слишком помочь. Любые предложения о том, как ускорить мой код, приветствуются!


person chemist    schedule 20.11.2014    source источник
comment
используя рекурсию вместо цикла for, если вы сделаете это достаточно хорошо, вы можете снизить его до O (logn), где for равно O (n), а журнал растет медленнее, чем константа   -  person jgr208    schedule 20.11.2014
comment
это также хороший ресурс wiki.python.org/moin/PythonSpeed/PerformanceTips   -  person jgr208    schedule 20.11.2014
comment
@ jgr208 Спасибо за ваши предложения. Я прочитаю предоставленную вами статью.   -  person chemist    schedule 20.11.2014
comment
В вашей реализации cython вы изменили my_list на массив? Насколько я понимаю, если содержимое вашего цикла включает только операции над типами C, то цикл будет преобразован в C и будет выполняться относительно быстро; если там все еще есть типы python (например, добавление к python list), то я думаю, что вам не хватает преимуществ cython   -  person Ryan    schedule 20.11.2014
comment
@Ryan Спасибо за точку зрения. Я не менял my_list на массив. Насколько я понимаю, когда я создаю data.pyx, cython позаботится обо всех необходимых преобразованиях, но я уже использовал cython раньше, поэтому могу ошибаться.   -  person chemist    schedule 20.11.2014
comment
Также может случиться так, что простое чтение входного вывода занимает нетривиальное количество времени; чтобы проверить эту гипотезу, вы можете опустить шаг добавления списка в цикл (но сохранить шаг чтения булавки) и посмотреть, сколько времени занимает сам цикл. Это скажет вам (недостижимую) нижнюю границу; однако, если он очень близок к тому, что вы получаете сейчас, то вы знаете, что пытаться ускорить его будет бесполезно, поскольку это физическое ограничение того, сколько времени требуется для считывания ввода вывода.   -  person Ryan    schedule 20.11.2014
comment
Если вы попробовали приведенное выше предложение и похоже, что есть возможности для улучшения, то я бы предложил аннотировать ваш файл pyx, чтобы вы знали, что происходит при преобразовании: вы можете сделать это в терминале с помощью cython -a <filename>.pyx, который создаст вывод HTML файл, сообщающий вам, где находятся медленные регионы (они отображаются желтым цветом). docs.cython.org/src/quickstart/   -  person Ryan    schedule 20.11.2014
comment
@Ryan Большое спасибо за вашу помощь. Я обязательно проверю вашу гипотезу о физических ограничениях. Мне очень нравится эта идея. Я буду держать вас в курсе. Также спасибо за ссылку!   -  person chemist    schedule 20.11.2014
comment
@Ryan Только что закончил тестирование. Разница между циклом с добавлением списка и без него составляет около 0,1 секунды. Звучит не слишком многообещающе. Как вы считаете, если этот цикл for будет написан, например, на C++, я увижу разницу? Спасибо.   -  person chemist    schedule 20.11.2014
comment
Я бы интерпретировал это как означающее, что независимо от того, на каком языке вы реализуете, вы будете ограничены тем, насколько быстро устройство может считывать входной контакт. Возможно, стоит поискать способы уменьшить количество циклов, если это возможно.   -  person Ryan    schedule 20.11.2014


Ответы (2)


Вы можете начать с добавления объявления статического типа:

import Adafruit_BBIO_GPIO as GPIO
import time 

GPIO.setup("P8_13", GPIO.IN)

def get_data(int n):  # declared as an int
    my_list = []
    start_time = time.time()
    for i in range(n):
        my_list.append(GPIO.input("P8_13"))
    end_time = time.time() - start_time
    print "Time: {}".format(end-time)
    return my_list

n = 100000
get_data(n)

Это позволяет преобразовать сам цикл в чистый цикл C с тем недостатком, что n больше не является произвольной точностью (поэтому, если вы попытаетесь передать значение, превышающее ~ 2 миллиарда, вы получите неопределенное поведение). Эту проблему можно решить, изменив int на unsigned long long, что позволяет использовать значения до 2**64 - 1 или около 18 квинтиллионов. Квантификатор unsigned означает, что вы не сможете передать отрицательное значение.

Вы получите гораздо более существенный прирост скорости, если сможете исключить список. Попробуйте заменить его на array. Cython может более эффективно работать с массивами, чем со списками.

person Kevin    schedule 20.11.2014
comment
Спасибо за ваш ответ. Объявление n как int дало незначительное улучшение скорости. Попытаюсь заменить список массивом. - person chemist; 20.11.2014

Я попробовал ваш тот же код, но с другой сборкой Adafruit_BBIO, подсчет в один миллион занимает всего около 3 секунд для запуска на моей плате rev C.

Я думал, что основным изменением платы от Rev B до Rev C стало то, что eMMC увеличили с 2 ГБ до 4 ГБ.

Если вы пойдете и получите текущий Adafruit_BBIO, все, что вам нужно изменить в приведенном выше коде, — это первый оператор импорта, он должен быть Adafruit_BBIO.GPIO as GPIO

Что вы пробовали дальше?

Рон

person Ron Harding    schedule 18.04.2015
comment
Некоторое время назад я сжег свой BBB и решил попробовать Micro Python, и пока я им доволен. Единственное, что немного улучшило частоту дискретизации при использовании BBB, — это использование понимания списка вместо обычного цикла for. - person chemist; 22.04.2015