Вращение больших массивов, максимально быстрое

Я относительно новичок в 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 пикселей).

Другие вопросы по тегам