0

我正在开发一个程序来解析一个文件(365000 行),我在读取每一行后尝试匹配一些关键字。此计算以及 my 的更新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)
4

2 回答 2

3

更新QProgressBar太频繁肯定会导致性能问题。您应该减少更新进度条的频率。您不想/不需要每次迭代都这样做...... 365000 次。当您从 365000 中读取一行时,您进步了 0.0002%,无需为此更新 GUI...

向用户显示进度总是要付出代价的......我们接受这一点,因为用户更愿意等待一段时间并获得进度信息。但是,显示进度不能像您所经历的那样将处理时间乘以 100。

您可以仅在进度发生显着变化时发出信号以更新进度条(例如,每次将百分比值转换为int更改时,您可以将进度存储为int值以检查...或测试(line%(fileLines/100)==0)例如...将显着降低进度条更新的成本)。

或者,您可以开始QTimer每隔 100 毫秒更新一次进度条。然后,您不会从for循环中发出任何信号,而只是保存要在计时器超时时使用的进度值。

如果文件大小始终为 365000 行,您还可以决定每 1000 行发送一次信号,例如 ( if line%1000==0)。但是较早的两个解决方案更可取,因为无论文件大小如何,它们都会解决您的性能问题。

于 2016-04-23T15:09:45.827 回答
2

这是一个经典问题,我认识的每个有经验的开发人员都有一个故事,讲述了一个所谓的漫长过程,其中大部分时间实际上都花在了进度条更新上(这些故事中的大多数最终都完全删除了进度条)。

关键是,通常情况下,您处理的“工作单元”(在您的情况下是解析一行)比进度条更新的成本要小得多 - 与用户反应相比,GUI 速度很快,但仍然与解析单行相比(尤其是在涉及跨线程机器的情况下),这是相当重量级的。

根据我的经验,有三种常见的解决方案:

  • 如果您注意到您的进程总体上是“快”的,您只需放下进度条(或将其替换为那些无用的“前进和后退”进度条,以表明如果程序有时会收到文件,您的程序没有挂起比平时大得多);
  • 你只是不那么频繁地更新它;您可以每 1/100 的总进度发出一次信号;进展仍然很顺利,你不应该有性能问题(100 次更新不会花费太多时间,虽然我猜如果通常需要 0.40 秒,它们仍然会主导你的进程所花费的时间);
  • 您可以将进度条更新与实际执行这些操作的代码完全分离。不要发出信号,而是用当前进度更新一个整数类成员(这应该很便宜);在 GUI 线程中,使用计时器根据该成员每 0.5 秒更新进度条。如果进程在第一个计时器滴答之前完成,您甚至可以更聪明并避免完全显示进度条。
于 2016-04-23T15:38:00.037 回答