QProgressBar вызывает плохую производительность в 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 реже. Прогресс все еще плавный, а производительность очень хорошая (около одной секунды с этой реализацией). PSB:
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)
2 ответа
Обновление QProgressBar
слишком часто, безусловно, приведет к проблемам с производительностью. Вам следует обновлять индикатор выполнения реже. Вы не хотите / не должны делать это для каждой итерации...365000 раз. Когда вы читаете одну строку из 365000, вы прогрессируете на 0,0002%, нет необходимости обновлять графический интерфейс для этого...
Отображение прогресса для пользователя всегда имеет цену... и мы принимаем это, потому что пользователь предпочитает немного больше ждать и получать информацию о прогрессе. Однако показ прогрессии не должен умножать время обработки на 100, как вы видели.
Вы можете либо подать сигнал на обновление индикатора выполнения, только когда прогрессия значительно изменилась (например, каждый раз, когда процентное значение приводится к int
изменилось, вы можете сохранить прогрессию как int
значение, чтобы проверить это... или проверить, если (line%(fileLines/100)==0)
например... это значительно снизит стоимость обновления индикатора выполнения).
Или вы могли бы начать QTimer
например, обновлять индикатор выполнения каждые 100 мс. Тогда вы не излучаете сигнал от for
цикл и просто сохранить значение прогрессии, которое будет использоваться, когда таймер истекает.
Если размер файла всегда равен 365000 строк, вы также можете принять решение испускать сигнал, например, каждые 1000 строк (if line%1000==0
). Но два более ранних решения предпочтительнее, потому что они исправят ваши проблемы с производительностью независимо от размера файла.
Это классическая проблема, у каждого опытного разработчика, которого я знаю, есть история о якобы длительном процессе, когда большую часть времени фактически занимало обновление индикатора прогресса (большинство этих историй заканчивалось полным удалением индикатора выполнения).
Дело в том, что очень часто "единица работы", которую вы обрабатываете (в вашем случае анализ строки), намного меньше, чем стоимость обновления индикатора выполнения - графические интерфейсы быстры по сравнению с пользовательскими рефлексами, но все еще достаточно тяжелый по сравнению, скажем, с разбором одной строки (особенно, если задействовано многозаходное оборудование).
По моему опыту, есть три общих решения:
- если вы заметили, что ваш процесс в целом "быстрый", вы просто отбрасываете индикатор выполнения (или заменяете его на эти бесполезные индикаторы "вперед и назад", просто чтобы показать, что ваша программа не зависла, если программа несколько раз загружалась файлами намного больше, чем обычно);
- вы просто обновляете его реже; вы можете испускать сигнал каждые 1/100 от вашего общего прогресса; продвижение будет по-прежнему плавным, и у вас не должно быть проблем с производительностью (100 обновлений не будут занимать много времени, хотя я предполагаю, что они все равно будут доминировать во времени, затрачиваемом вашим процессом, если это обычно занимает 0,40 секунды);
- Вы можете полностью отделить обновление индикатора выполнения от кода, который на самом деле выполняет эту функцию. Вместо того, чтобы излучать сигнал, обновите целочисленный член класса с текущим прогрессом (который должен быть довольно дешевым); в потоке GUI используйте таймер для обновления индикатора выполнения в соответствии с этим членом, скажем, каждые 0,5 секунды. Вы даже можете быть умнее и не показывать индикатор прогресса полностью, если процесс завершится до первого таймера.