前言
在做到个别项目对日志要求较高,要求并行写入的数据较多,尽管写入数据的线程放在子线程,仍然会造成界面程序的假死(实际上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的性能本身并不高。