QProgressBar 在 QT5 中导致性能不佳?
QProgressBar causing bad performance in QT5?
我正在开发一个程序来解析一个文件(365000 行),我在读取每一行后尝试匹配一些关键字。这个计算连同我的 QProgressBar
的更新是在另一个线程中使用 QThread
进行的。一切正常,除了性能,尤其是当我更新 QProgressBar
时。我使用计时器进行解析,结果令人惊叹。当我发出更新 QProgressBar
的信号时,程序大约需要 45 秒,但是当我不发出 QProgressBar
更新的信号时,程序大约需要 0.40 秒 =/
from PyQt5 import QtCore, QtWidgets, QtGui
import sys
import time
liste = ["failed", "exception"]
class ParseFileAsync(QtCore.QThread):
match = QtCore.pyqtSignal(str)
PBupdate = QtCore.pyqtSignal(int)
PBMax = QtCore.pyqtSignal(int)
def run(self):
cpt = 0
with open("test.txt", "r") as fichier:
fileLines = fichier.readlines()
lineNumber = len(fileLines)
self.PBMax.emit(lineNumber)
t0 = time.time()
for line in fileLines:
cpt+=1
self.PBupdate.emit(cpt)
for element in liste:
if element in line:
self.match.emit(line)
finalTime = time.time() - t0
print("over :", finalTime)
class Ui_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.thread = ParseFileAsync()
self.thread.match.connect(self.printError)
self.thread.PBupdate.connect(self.updateProgressBar)
self.thread.PBMax.connect(self.setMaximumProgressBar)
self.pushButton_GO.clicked.connect(self.startThread)
def printError(self, line):
self.textEdit.append(line)
def updateProgressBar(self, value):
self.progressBar.setValue(value)
def setMaximumProgressBar(self, value):
self.progressBar.setMaximum(value)
def startThread(self):
self.thread.start()
控制台输出:
over : 44.49321101765038 //QProgressBar updated
over : 0.3695987798147516 //QProgressBar not updated
我是不是遗漏了什么或者是预期的?
编辑:
我听从了 jpo38 和 Matteo 的非常好的建议。我更新 QProgressBar 的频率较低。进展仍然顺利,性能非常好(此实现大约一秒钟)。公安局:
class ParseFileAsync(QtCore.QThread):
match = QtCore.pyqtSignal(str)
PBupdate = QtCore.pyqtSignal(int)
PBMax = QtCore.pyqtSignal(int)
def run(self):
with open("test_long.log", "r") as fichier:
fileLines = fichier.readlines()
self.lineNumber = len(fileLines)
self.PBMax.emit(self.lineNumber)
if (self.lineNumber < 30):
self.parseFile(fileLines, False)
else:
self.parseFile(fileLines, True)
def parseFile(self, fileLines, isBig):
cpt = 0
if(isBig):
for line in fileLines:
cpt+=1
if(cpt % (int(self.lineNumber/30)) == 0):
self.PBupdate.emit(cpt)
for element in liste:
if element in line:
self.match.emit(line)
self.PBupdate.emit(self.lineNumber) #To avoid QProgressBar stopping at 99%
else:
for line in fileLines:
cpt+=1
self.PBupdate.emit(cpt)
for element in liste:
if element in line:
self.match.emit(line)
更新 QProgressBar
过于频繁肯定会导致性能问题。您应该减少更新进度条的频率。您不会 want/need 每次迭代都这样做......365000 次。当您阅读 365000 行中的一行时,您进步了 0.0002%,无需为此更新 GUI...
向用户显示进度总是要付出代价的...我们接受这一点,因为用户更愿意多等一会儿并获得进度信息。但是,显示进度不得像您所经历的那样将处理时间乘以 100。
您可以仅在进度发生显着变化时发出信号以更新进度条(例如,每次转换为 int
的百分比值发生变化时,您可以将进度存储为 int
值检查...或测试是否 (line%(fileLines/100)==0)
例如...这将显着降低进度条更新的成本)。
或者您可以启动一个 QTimer
以每 100 毫秒更新一次进度条。然后,您不会从 for
循环中发出任何信号,只需保存计时器超时时要使用的进度值。
如果文件大小始终为 365000 行,例如,您还可以决定每 1000 行发出一次信号 (if line%1000==0
)。但较早的两个解决方案更可取,因为无论文件大小如何,它们都会解决您的性能问题。
这是一个 classic 问题,我认识的每个有经验的开发人员都有一个故事,说的是一个据称很长的过程,其中大部分时间实际上都花在了进度条更新上(这些故事中的大多数最终都删除了进度条完全)。
关键是,您处理的 "unit of work" 通常(在您的情况下是一行的解析)比进度条更新的成本要小得多 - 与用户相比,GUI 更快反射,但与解析单行(尤其是涉及跨线程机制时)相比,它仍然是重量级的。
根据我的经验,常见的解决方案有以下三种:
- 如果您注意到您的进程总体上是 "fast" 您只需删除进度条(或将其替换为那些无用的 "forward and backwards" 进度条只是为了表明您的程序没有挂起,如果程序有时会收到比平时大得多的文件);
- 只是你更新的频率降低了;您可以每完成总进度的 1/100 发出信号;进展仍然会很顺利,你不应该有性能问题(100 次更新不会花费太多时间,但我想如果通常需要 0.40 秒,它们仍然会主导你的过程所花费的时间);
- 您可以将进度条更新与实际执行这些操作的代码完全分离。不是发出信号,而是用当前进度更新整数 class 成员(这应该很便宜);在 GUI 线程中,使用计时器根据该成员每隔 0.5 秒更新一次进度条。如果进程在第一个计时器滴答之前完成,您甚至可以更聪明地避免完全显示进度条。
我正在开发一个程序来解析一个文件(365000 行),我在读取每一行后尝试匹配一些关键字。这个计算连同我的 QProgressBar
的更新是在另一个线程中使用 QThread
进行的。一切正常,除了性能,尤其是当我更新 QProgressBar
时。我使用计时器进行解析,结果令人惊叹。当我发出更新 QProgressBar
的信号时,程序大约需要 45 秒,但是当我不发出 QProgressBar
更新的信号时,程序大约需要 0.40 秒 =/
from PyQt5 import QtCore, QtWidgets, QtGui
import sys
import time
liste = ["failed", "exception"]
class ParseFileAsync(QtCore.QThread):
match = QtCore.pyqtSignal(str)
PBupdate = QtCore.pyqtSignal(int)
PBMax = QtCore.pyqtSignal(int)
def run(self):
cpt = 0
with open("test.txt", "r") as fichier:
fileLines = fichier.readlines()
lineNumber = len(fileLines)
self.PBMax.emit(lineNumber)
t0 = time.time()
for line in fileLines:
cpt+=1
self.PBupdate.emit(cpt)
for element in liste:
if element in line:
self.match.emit(line)
finalTime = time.time() - t0
print("over :", finalTime)
class Ui_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.thread = ParseFileAsync()
self.thread.match.connect(self.printError)
self.thread.PBupdate.connect(self.updateProgressBar)
self.thread.PBMax.connect(self.setMaximumProgressBar)
self.pushButton_GO.clicked.connect(self.startThread)
def printError(self, line):
self.textEdit.append(line)
def updateProgressBar(self, value):
self.progressBar.setValue(value)
def setMaximumProgressBar(self, value):
self.progressBar.setMaximum(value)
def startThread(self):
self.thread.start()
控制台输出:
over : 44.49321101765038 //QProgressBar updated
over : 0.3695987798147516 //QProgressBar not updated
我是不是遗漏了什么或者是预期的?
编辑:
我听从了 jpo38 和 Matteo 的非常好的建议。我更新 QProgressBar 的频率较低。进展仍然顺利,性能非常好(此实现大约一秒钟)。公安局:
class ParseFileAsync(QtCore.QThread):
match = QtCore.pyqtSignal(str)
PBupdate = QtCore.pyqtSignal(int)
PBMax = QtCore.pyqtSignal(int)
def run(self):
with open("test_long.log", "r") as fichier:
fileLines = fichier.readlines()
self.lineNumber = len(fileLines)
self.PBMax.emit(self.lineNumber)
if (self.lineNumber < 30):
self.parseFile(fileLines, False)
else:
self.parseFile(fileLines, True)
def parseFile(self, fileLines, isBig):
cpt = 0
if(isBig):
for line in fileLines:
cpt+=1
if(cpt % (int(self.lineNumber/30)) == 0):
self.PBupdate.emit(cpt)
for element in liste:
if element in line:
self.match.emit(line)
self.PBupdate.emit(self.lineNumber) #To avoid QProgressBar stopping at 99%
else:
for line in fileLines:
cpt+=1
self.PBupdate.emit(cpt)
for element in liste:
if element in line:
self.match.emit(line)
更新 QProgressBar
过于频繁肯定会导致性能问题。您应该减少更新进度条的频率。您不会 want/need 每次迭代都这样做......365000 次。当您阅读 365000 行中的一行时,您进步了 0.0002%,无需为此更新 GUI...
向用户显示进度总是要付出代价的...我们接受这一点,因为用户更愿意多等一会儿并获得进度信息。但是,显示进度不得像您所经历的那样将处理时间乘以 100。
您可以仅在进度发生显着变化时发出信号以更新进度条(例如,每次转换为 int
的百分比值发生变化时,您可以将进度存储为 int
值检查...或测试是否 (line%(fileLines/100)==0)
例如...这将显着降低进度条更新的成本)。
或者您可以启动一个 QTimer
以每 100 毫秒更新一次进度条。然后,您不会从 for
循环中发出任何信号,只需保存计时器超时时要使用的进度值。
如果文件大小始终为 365000 行,例如,您还可以决定每 1000 行发出一次信号 (if line%1000==0
)。但较早的两个解决方案更可取,因为无论文件大小如何,它们都会解决您的性能问题。
这是一个 classic 问题,我认识的每个有经验的开发人员都有一个故事,说的是一个据称很长的过程,其中大部分时间实际上都花在了进度条更新上(这些故事中的大多数最终都删除了进度条完全)。
关键是,您处理的 "unit of work" 通常(在您的情况下是一行的解析)比进度条更新的成本要小得多 - 与用户相比,GUI 更快反射,但与解析单行(尤其是涉及跨线程机制时)相比,它仍然是重量级的。
根据我的经验,常见的解决方案有以下三种:
- 如果您注意到您的进程总体上是 "fast" 您只需删除进度条(或将其替换为那些无用的 "forward and backwards" 进度条只是为了表明您的程序没有挂起,如果程序有时会收到比平时大得多的文件);
- 只是你更新的频率降低了;您可以每完成总进度的 1/100 发出信号;进展仍然会很顺利,你不应该有性能问题(100 次更新不会花费太多时间,但我想如果通常需要 0.40 秒,它们仍然会主导你的过程所花费的时间);
- 您可以将进度条更新与实际执行这些操作的代码完全分离。不是发出信号,而是用当前进度更新整数 class 成员(这应该很便宜);在 GUI 线程中,使用计时器根据该成员每隔 0.5 秒更新一次进度条。如果进程在第一个计时器滴答之前完成,您甚至可以更聪明地避免完全显示进度条。