1

OSX で Python 3.5、PyQt5 を使用していますが、コンピューティング作業全体を遅くすることなく QProgressBar を更新する可能性があるかどうか疑問に思っていました。これが私のコードで、プログレスバーを更新せずにタスクだけを実行すると、はるかに高速になりました!!

from PyQt5.QtWidgets import (QWidget, QProgressBar, QPushButton, QApplication)
from jellyfish import levenshtein_distance, jaro_winkler
import sys

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)
        self.btn = QPushButton('Start', self)
        self.btn.move(40, 80)
        self.btn.clicked.connect(self.doAction)
        self.setGeometry(300, 300, 280, 170)
        self.show()


    def doAction(self):

        #setup variables
        step = 0
        m = 1000
        n = 500
        step_val = 100 / (m * n)

        #make task
        for i in range(m):
            for j in range(n):
                jaro_winkler(str(i), str(j))

                #show task
                print(i,j)

                #update progressbar
                step += step_val
                self.pbar.setValue(step)
                QApplication.processEvents()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

次に、stackoverflow ユーザーの助けを借りて、別の作業スレッドを作成し、更新シグナルを GUI に接続するヒントを得ました。私はそれを行い、次のコードのようになりました。それも機能し、はるかに高速ですが、放出された信号を GUI に接続する方法がわかりません。誰か助けてくれませんか?よろしくお願いします!

from jellyfish import jaro_winkler
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QMainWindow
import time
import numpy as np

class Main_Window(QMainWindow):

    def __init__(self):
        super(Main_Window,self).__init__()
        self.initUI()

    def initUI(self):
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)
        self.btn = QPushButton('Start', self)
        self.btn.move(40, 80)
        self.btn.clicked.connect(MyThread.doAction)
        self.setGeometry(300, 300, 280, 170)
        self.show()

    def updateProgressBar(self, val):
        self.pbar.setValue.connect(val)


class MySignal(QWidget):
    pbar_signal = QtCore.pyqtSignal(int)


class MyThread(QtCore.QThread):
    def __init__(self):
        super().__init__()

    def doAction(self):
        t = time.time()     #for time measurement

        #setup variables
        step = 0
        m = 1000
        n = 500
        pbar_val = 100 / m
        signal_instance = MySignal()

        #make task
        for i in range(m):
            for j in range(n):
                jaro_winkler(str(i), str(j))
            signal_instance.pbar_signal.emit(pbar_val)

        #measuring task time
        print(np.round_(time.time() - t, 3), 'sec elapsed')


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    w = Main_Window()
    sys.exit(app.exec_())
4

1 に答える 1

1

コードの速度を低下させる 3 つの要因があります。

  1. stdout への出力は非常にコストがかかります - 特に 500,000 回実行すると! 私のシステムでは、コメントアウトすると、実行にかかるprint(i,j)時間が約半分になりdoActionます。
  2. processEvents500,000回呼び出すのもかなり高価です。コメントアウトQApplication.processEvents()すると、実行時間がさらに 3 分の 2 短縮されます。
  3. コメントアウトするself.pbar.setValue(step)と、時間が再び半分になります。

うまくいけば、1 秒もかからないはずのタスク中に GUI を 500,000 回更新しようとするのは非常にやり過ぎであることが明らかになったはずです! ほとんどのユーザーの反応時間はせいぜい約 200 ミリ秒なので、GUI を更新する必要があるのは 100 ミリ秒ごとに約 1 回だけです。

それを考えると、簡単な修正の 1 つは、更新を外側のループに移動することです。

    for i in range(m):
        for j in range(n):
            jaro_winkler(str(i), str(j))

            # show task
            # print(i,j)

            step += step_val

        # update progressbar
        self.pbar.setValue(step)
        QApplication.processEvents()

しかし、より良い解決策は、計算を別のワーカー スレッドに移動し、定期的にカスタム シグナルを発行してプログレス バーを更新することです。

class Main_Window(QMainWindow):
    ...
    def initUI(self):
        ...
        self.btn.clicked.connect(self.doAction)
        self.thread = MyThread()
        self.thread.pbar_signal.connect(self.pbar.setValue)

    def doAction(self):
        if not self.thread.isRunning():
            self.thread.start()    

class MyThread(QtCore.QThread):
    pbar_signal = QtCore.pyqtSignal(int)

    def run(self):
        #for time measurement
        t = time.time()

        #setup variables
        m = 1000
        n = 500
        progress = step = 100 / m

        #make task
        for i in range(m):
            for j in range(n):
                jaro_winkler(str(i), str(j))
            progress += step
            self.pbar_signal.emit(progress)

        #measuring task time
        print(np.round_(time.time() - t, 3), 'sec elapsed')
于 2016-10-12T17:54:49.670 に答える