Вращение больших массивов, максимально быстрое
Я относительно новичок в Python и ищу лучший оптимизированный код для вращения больших многомерных массивов. В следующем коде у меня есть многомерный массив 16X600000 с 32-битной плавающей точкой, и в соответствии с таймером вращение содержимого моего четырехъядерного планшета Acer Windows 8 занимает около 30 мс. Я подумывал об использовании некоторых подпрограмм Cython или чего-то подобного, если можно было бы сократить время, необходимое для вращения массива.
В конечном итоге код будет использоваться для хранения значений оси Y для высокоскоростного графика построения данных, основанного на пакете VisPy, и 32-битный массив с плавающей точкой будет передан в процедуру OpenGL. Я хотел бы достичь менее 1 мс, если это возможно.
Любые комментарии, рекомендации или примеры кода будут высоко оценены.
import sys, timeit
from threading import Thread
from PyQt4 import QtGui
import numpy as np
m = 16 # Number of signals.
n = 600000 # Number of samples per signal.
y = 0.0 * np.random.randn(m, n).astype(np.float32)
Running = False
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Start', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
global Running, thread, thrTest
if Running == True:
Running = False
self.button.setText('Start')
thrTest.isRunning = False
print ('stop')
else:
Running = True
self.button.setText('Stop')
thrTest = testThread()
thread = Thread(target=thrTest.run, daemon=True )
thread.start()
print ("Start")
class testThread(Thread):
def __init__(self):
self.isRunning = True
def run(self):
print('Test: Thread Started')
while self.isRunning == True:
start_time = timeit.default_timer()
y[:, :-1] = y[:, 1:]
elapsed = timeit.default_timer() - start_time
print ('Time (s)= ' + str(elapsed))
print('Test: Closed Thread')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Обновить
Я предполагаю, что была некоторая путаница относительно того, что именно я пытаюсь сделать, поэтому я попытаюсь объяснить немного лучше.
Конечная цель состоит в том, чтобы иметь быстрое устройство регистрации данных в реальном времени, которое рисует линию на графике, представляющем значение сигнала. Будет несколько каналов с частотой дискретизации не менее 1 мс и максимально возможное время записи. Я начал с этого примера VisPy. Код в примере, который записывает новые данные в массивы и отправляет их в OpenGL, находится в On_Timer
функция около дна. Я немного изменил этот код, чтобы интегрировать холст OpenGL в графический интерфейс Qt, и добавил немного кода для получения данных из Arduino Mega через сокет Ethernet.
В настоящее время я могу создать график в реальном времени из 16 строк с частотой дискретизации около 1 мс и частотой кадров около 30 Гц со временем записи около 14 секунд. Если я попытаюсь увеличить количество каналов или длительность записи, программа перестанет работать, так как не сможет справиться с потоком данных, поступающих через порт Ethernet, на 1 мс.
Самым большим виновником, который я могу найти для этого, является время, необходимое для завершения сдвига буфера данных с использованием y[:, :-1] = y[:, 1:]
рутина. Первоначально я представил тестовый код, где эта функция рассчитывалась в надежде, что кто-то знает способ сделать то же самое более эффективным способом. Цель этой строки - сместить весь массив на один индекс влево, а затем в следующей строке кода я записываю новые данные в первый слот справа.
Ниже вы можете увидеть мою модифицированную процедуру обновления графика. Сначала он берет новые данные из очереди и распаковывает их во временный массив, затем переносит содержимое основного буферного массива и, наконец, копирует новые данные в последний слот основного массива. Когда очередь пуста, она вызывает функцию обновления, чтобы OpenGL обновлял отображение.
def on_timer(self, event):
"""Add some data at the end of each signal (real-time signals)."""
k=1
s = struct.Struct('>16H')
AdrArray = 0.0 * np.random.randn(16,1).astype(np.float32)
if not q.qsize() == 0:
while q.qsize() > 0:
print (q.qsize())
print ('iin ' + str(datetime.datetime.now()))
AdrArray[:,0]= s.unpack_from(q.get(), offset=4)
y[:, :-1] = y[:, 1:]
y[:, -1:] = .002*AdrArray
print ('out ' + str(datetime.datetime.now()))
self.program['a_position'].set_data(y.ravel().astype(np.float32))
self.update()
3 ответа
Вы действительно хотите этот "ролл"? Он сдвигает значения влево, заполняя последний столбец
In [179]: y = np.arange(15).reshape(3,5)
In [180]: y[:,:-1]=y[:,1:]
In [181]: y
Out[181]:
array([[ 1, 2, 3, 4, 4],
[ 6, 7, 8, 9, 9],
[11, 12, 13, 14, 14]])
Для такого нестандартного "крена" вряд ли что-то будет быстрее.
np.roll
имеет другую заливку слева
In [190]: np.roll(y,-1,1)
Out[190]:
array([[ 1, 2, 3, 4, 0],
[ 6, 7, 8, 9, 5],
[11, 12, 13, 14, 10]])
Для чего это стоит, ядро roll
является:
indexes = concatenate((arange(n - shift, n), arange(n - shift)))
res = a.take(indexes, axis)
Ваш конкретный "ролл" может быть воспроизведен с аналогичным "дублем"
In [204]: indexes=np.concatenate([np.arange(1,y.shape[1]),[y.shape[1]-1]])
In [205]: y.take(indexes,1)
Ваш y[:,:-1]...
быстрее чем roll
потому что это не создает новый массив; вместо этого он просто перезаписывает часть существующего.
take
принимает out
параметр, так что это возможно:
y.take(indexes,1,y)
хотя по скорости это помогает только с небольшими массивами. Для больших ваших заданий перезаписи быстрее.
Я бы также посоветовал посмотреть на использование транспонирования и axis=0
, Для order='C'
массив, значения строки образуют непрерывный блок.
Большим потребителем времени является то, что вам нужно скопировать (почти) весь массив из одного места в другое, либо в новый массив, либо на саму себя. Если бы данные были в каком-то кольцевом буфере, вы могли бы просто изменить указатель, не копируя какие-либо данные.
Как уже упоминали некоторые другие, я не думаю, что вы хотите сдвинуть массив на процессоре. Перемещение всех элементов в массиве при каждом обновлении всегда будет медленным. Я не ожидаю, что Cython поможет здесь, так как Numpy уже будет достаточно оптимальным.
Вместо этого вы хотите позаботиться о смещении в вершинном шейдере. Пример того, что я думаю, похож на то, что вы хотите, здесь: http://vispy.org/examples/demo/gloo/realtime_signals.html
Изменить: один из подходов заключается в рассмотрении вашего VBO круговой буфер. Вы добавляете новые значения в "старом" месте, а в вершинном шейдере вы используете форму смещения для отображения сигналов в правильное положение. Аналогичная техника используется в http://vispy.org/examples/demo/gloo/spacy.html
Пример realtime-signals.py в glumpy реализует кольцевой буфер и может помочь вам:
https://github.com/glumpy/glumpy/blob/master/examples/realtime-signals.py
Он легко адаптируется (и скоро будет адаптирован) для вязкости. Хитрость заключается в том, чтобы использовать фортраноподобную компоновку, чтобы обновление 16 сигналов приводило к загрузке непрерывного блока из 16 чисел с плавающей запятой в графический процессор. Пример должен обрабатывать ваши 16 x 600 000 данных, если они помещаются в память графического процессора (тестировалось только 16 x 100 000 при 250 кадрах в секунду).
Обратите внимание, что поскольку у вас ограниченное разрешение экрана по горизонтали, это может помешать вам увидеть все данные (в конце концов, для каждого сигнала будет отображаться только 1600 данных, если ваш экран имеет ширину 1600 пикселей).