2016-04-23 102 views
0

我正在开发一个解析文件(365000行)的程序,其中我尝试在读取每行后匹配一些关键字。此计算与我的QProgressBar的更新一起在另一个线程中使用QThread进行。一切工作正常,除了性能,尤其是当我更新QProgressBar。我使用解析器的计时器,结果就是STUNNING。当我发出的信号来更新QProgressBar程序占用约45秒,但我不发射信号的QProgressBar更新则程序约需0.40秒=/QProgressBar在QT5中导致性能不佳?

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和利玛窦非常好的建议。我不太经常更新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) 
+0

您使用的是哪个版本的Qt?也许你刚刚被https://bugreports.qt.io/browse/QTBUG-49655 – peppe

+0

Thx咬了你的回复。有趣的地方......它似乎确实是同一个问题。我正在使用V5.5.1。 – Trunks10

+0

你可以试试5.6,看看它是否解决了这个问题? – peppe

回答

3

更新一个QProgressBar太频繁肯定会导致性能问题。您应该不经常更新进度栏。你不需要/需要为每次迭代... 365000次。当您从365000中读取一行时,您的进度增加了0.0002%,无需为此更新GUI ...

向用户显示进度始终有成本......并且我们接受这种情况,因为用户更喜欢等待多一点,并有进度信息。但是,显示进展不能像您所经历的那样将处理时间乘以100。

您可以发出只更新进度条的信号时,进展显著变化(例如浇铸为int每次百分比值发生变化,您可以将进程存储为int值检查...或者测试,如果例如(line%(fileLines/100)==0) ...这将显着降低进度栏更新的成本)。

或者您可以开始QTimer以每100ms更新一次进度条。然后,您不会从for循环发出任何信号,只保存定时器超时时使用的进程值。

如果文件大小始终为365000行,则也可以决定每隔1000行发送一次信号(if line%1000==0)。但是前两种解决方案是可取的,因为无论文件大小如何,它们都可以解决性能问题。

+0

非常好的解释谢谢:)。我编辑了我的第一篇文章。 – Trunks10

+0

@ Trunks10。好。您可能更喜欢将一个带有isBig参数的parseFile函数分解为两个函数的代码。 – jpo38

+0

好主意thx! – Trunks10

2

这是一个典型的问题,我知道每个有经验的开发人员都有一个关于一个长时间过程的故事,其中大部分时间实际上是由进度条更新取得的(大多数故事最终会完全移除进度条)。

问题是,很多时候,您处理的“工作单元”(在您的情况下解析一条线)比进度条更新的成本要小 - 图形用户界面与用户反射,但与解析单行数据(尤其涉及跨线程机制)相比,仍然相当重量级。

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

  • 如果您发现您的一般过程是“快”,你刚落进度(或那些无益的“向前和向后”进步更换只是为了表明你的程序没有被挂起,如果程序有时被文件比平时更大的文件);
  • 你只是更新频率较低;你可以每完成一次你的信号发射你的信号;进展依然顺利,你不应该有性能问题(100次更新不会花费太多时间,但我猜他们仍然会支配你的进程花费的时间,如果它通常需要0.40秒);
  • 你可以从代码中完全去除进度条更新,而代码实际上完成了这些工作。用当前进度更新一个整数类成员(应该很便宜),而不是发出信号;在GUI线程中使用一个计时器来根据这个成员更新进度条 - 比如说0.5秒。如果过程在第一个计时器打勾之前完成,您甚至可以变得更聪明并避免完全显示进度条。
+0

非常好的解释,谢谢;)。我编辑了我的第一篇文章,并选择不经常更新QProgressBar。 – Trunks10