檔案IO操作開發筆記(一):使用Qt的QFile對磁碟檔案儲存進行效能測試以及測試工具

21497936發表於2023-01-03

前言

  在做到個別專案對日誌要求較高,要求並行寫入的資料較多,儘管寫入資料的執行緒放在子執行緒,仍然會造成介面程式的假死(實際上Qt還是在跑,只是磁碟消耗超過瓶頸,造成假死(注意:控制檯還能看到列印輸出,linux則能看到列印輸出)。
  本篇開發了測試工具,並且測試了QFile在USB3.0和M.2SSD上的寫入效能。

補充

  在海思 Hi3559AV100,Hi3516DV300以及海思的開發過程中,也發現Qt會假死,後臺仍然在繼續列印,海思板上的Qt介面假死的原因並不是因為磁碟效能問題,可以解決但涉及到一些關鍵技術了,此處不提。

第一版本測試v1.0.0

  日誌的操作,多半寫入都是幾十上百位元組一條,特殊的專案要求寫入不同的檔案,分類儲存,於是產出了第一版本的,用於測試Qt的。
   在這裡插入圖片描述

關於對於“檔案開啟次數屬性”的忽略

  理論上也可以忽略,測試跟理論結果一致,因為本身程式的檔案開啟次數,是新建一個然後寫入操作完成後關閉,然後另外新建一個繼續重複操作,是流水線排序的,所以這個對單執行緒寫入影響不大。

  因為測試是獲取了系統時間,次數少了測不出,次數多了越來越小,偶爾增大,所以可以判斷,主要影響時間的還是QDateTime獲取時間,然後計算的過程。

   在這裡插入圖片描述

   在這裡插入圖片描述

   在這裡插入圖片描述

  選取1000次作為標準,測試檔案開啟次數的影響:

   在這裡插入圖片描述

   在這裡插入圖片描述
  開啟次數基本無影響,但是一次測試可以利用這個來一次性測多次每個檔案單獨寫入的耗時。

使用QFile測試結果

   在這裡插入圖片描述

   在這裡插入圖片描述

  太小了看不出:
   在這裡插入圖片描述

  修改程式至v1.0.1版本,只看最終結果(為了模擬日誌多執行緒寫入不同檔案),下面開始測試。

USB3.0行動硬碟測試結果

   在這裡插入圖片描述

    在這裡插入圖片描述

  所以,執行緒越開越多,在某一個閾值執行緒數(實際開啟操作的檔案數)會導致效能大幅下降,而且會持續有多個閾值類似的。

M.2主機板上SSD測試結果

   在這裡插入圖片描述

   在這裡插入圖片描述

使用QFile(每次寫後用flush)測試結果

USB3.0行動硬碟測試結果

   在這裡插入圖片描述

   在這裡插入圖片描述

M.2主機板上SSD測試結果

   在這裡插入圖片描述

   在這裡插入圖片描述

  結論:這個明顯收到硬碟資料傳輸的影響。

關鍵原始碼

void FileIoTestManager::slot_optFileUseQtQFile(int loopTime, int loopWrite, int dataSize, bool flush){
    QDir dir;
    QString dirPath = QString("%1/%2")
                            .arg(QApplication::applicationDirPath())
                            .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm_ss_zzz"));
    if(dir.mkpath(dirPath))
    {
        message(QString("建立資料夾成功: %1").arg(dirPath));
    }else{
        message(QString("建立資料夾失敗: %1").arg(dirPath));
    }
    // 生成資料
    message(QString("生成測試資料,資料長度: %1").arg(dataSize));
    QByteArray byteArray;
    byteArray.append(dataSize, 0xFF);
    message(QString("==========================測試開始=============================="));
    double totalTime = 0;           // 總計時間
    double fileTotalTime = 0;       // 操作單個檔案總時間
    double writeFileTime = 0;       // 單個檔案單詞寫入時間
    totalTime = QDateTime::currentDateTime().toMSecsSinceEpoch() * 1.0f;
    for(int loopIndex = 0; loopIndex < loopTime; loopIndex++)
    {
        QString filePath = QString("%1/%2_%3")
                .arg(dirPath)
                .arg(QDateTime::currentDateTime().toString("hh_mm_ss_zzz"))
                .arg(loopIndex, 6, 10, QChar('0'));
        QFile file(filePath);
        if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
        {
            message(QString(" 第%1次建立檔案失敗").arg(loopIndex + 1));
            continue;
        }
        writeFileTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
        for(int writeIndex = 0; writeIndex < loopWrite; writeIndex++)
        {//            message(QString("  第%1次寫入檔案,寫入長度%2位元組").arg(writeIndex + 1).arg(dataSize));
            int size = 0;
            while(size < byteArray.size())
            {
                int len = file.write(byteArray.mid(size));
                if(len < 0)
                {
                    message(QString("  第%1次寫入檔案,寫入失敗").arg(writeIndex + 1));
                    message(QString("==========================測試失敗=============================="));
                    break;
                }//                message(QString("  第%1次寫入檔案,寫入成功").arg(writeIndex + 1));
                if(flush)
                {
                    file.flush();
                }
                size += len;
                if(_stop)
                {
                    file.close();
                    message(QString("==========================測試手動停止==========================="));
                    _stop = false;
                    emit signal_finished();
                    return;
                }
            }
            if(_stop)
            {
                file.close();
                message(QString("==========================測試手動停止==========================="));
                _stop = false;
                emit signal_finished();
                return;
            }
        }
        writeFileTime = QDateTime::currentDateTime().toMSecsSinceEpoch() - writeFileTime;
        writeFileTime = writeFileTime / loopWrite;
        message(QString("每次寫入資料平均耗時(不包含開啟關閉檔案): %1ms").arg(writeFileTime));//        message(QString(" 第%1次關閉檔案").arg(loopIndex + 1));
        file.close();
    }
    message(QString("==========================測試結果=============================="));
    totalTime = QDateTime::currentDateTime().toMSecsSinceEpoch() - totalTime;
    fileTotalTime = totalTime * 1.0f / loopTime;
    message(QString("操作建立檔案次數: %1, 單個檔案迴圈寫入次數: %2, 每次寫入固定資料長度: %3, %4")
            .arg(loopTime)
            .arg(loopWrite)
            .arg(dataSize)
            .arg(flush ? "每次使用flush" : "不使用flush"));
    message(QString("總耗時: %1ms").arg(totalTime));
    message(QString("單個檔案迴圈寫入平均總耗時(包括開啟關閉檔案): %1ms").arg(fileTotalTime));
    message(QString("每次寫入資料平均耗時(包括開啟關閉檔案: %1ms").arg(fileTotalTime * 1.0f / loopWrite));
    message(QString("==========================測試結束=============================="));
    emit signal_finished();
    return;}

工程模板

   在這裡插入圖片描述

後續

  會持續補充測試其他方式,QFile的效能本身並不高。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010283/viewspace-2930589/,如需轉載,請註明出處,否則將追究法律責任。

相關文章