【问题标题】:How to optimize ASCII output with QTextStream如何使用 QTextStream 优化 ASCII 输出
【发布时间】:2013-06-14 19:08:44
【问题描述】:

我目前正在将数十亿的二进制记录写入 ASCII 文件(呃)。我的工作正常,但如果可以的话,我想优化性能。问题是,用户可以选择任意数量的字段来输出,所以我无法在编译时知道它们将包含 3-12 个字段中的哪一个。

有没有更快的方法来构造 ASCII 文本行?如您所见,字段的类型变化很大,我想不出绕过一系列 if() 语句的方法。输出的 ASCII 文件每条记录一行,所以我尝试使用由 arg 构造的模板 QString,但这只会减慢大约 15% 的速度。

更快的解决方案不必使用 QTextStream,也不必直接写入文件,但输出太大而无法将整个内容写入内存。

这里有一些示例代码:

QFile outfile(outpath);
if(!outfile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
{
    qWarning("Could not open ASCII for writing!");
    return false;
} else
{
    /* compute XYZ precision */
    int prec[3] = {0, 0, 0}; //these non-zero values are determined programmatically

    /* set up the writer */
    QTextStream out(&outfile);
    out.setRealNumberNotation(QTextStream::FixedNotation);
    out.setRealNumberPrecision(3);
    QString del(config.delimiter); //the user chooses the delimiter character (comma, tab, etc) - using QChar is slower since it has to be promoted to QString anyway

    /* write the header line */
    out << "X" << del << "Y" << del << "Z";
    if(config.fields & INTFIELD)
        out << del << "IntegerField";
    if(config.fields & DBLFIELD)
        out << del << "DoubleField";
    if(config.fields & INTFIELD2)
        out << del << "IntegerField2";
    if(config.fields & TRIPLEFIELD)
        out << del << "Tri1" << del << "Tri2" << del << "Tri3";
    out << "\n";

    /* write out the points */
    for(quint64 ptnum = 0; ptnum < numpoints; ++ptnum)
    {
        pt = points.at(ptnum);
        out.setRealNumberPrecision(prec[0]);
        out << pt->getXYZ(0);
        out.setRealNumberPrecision(prec[1]);
        out << del << pt->getXYZ(1);
        out.setRealNumberPrecision(prec[2]);
        out << del << pt->getXYZ(2);
        out.setRealNumberPrecision(3);
        if(config.fields & INTFIELD)
            out << del << pt->getIntValue();
        if(config.fields & DBLFIELD)
            out << del << pt->getDoubleValue();
        if(config.fields & INTFIELD2)
            out << del << pt->getIntValue2();
        if(config.fields & TRIPLEFIELD)
        {
            out << del << pt->getTriple(0);
            out << del << pt->getTriple(1);
            out << del << pt->getTriple(2);
        }
        out << "\n";
    } //end for every point
outfile.close();

【问题讨论】:

  • 您需要对您的应用程序进行分析,以找出减慢速度的原因。 Qt 类已经过优化,如果使用得当,可以快速运行。而且您的代码是正确的,我没有看到任何明显缓慢的地方。在您的情况下,确实需要进行分析。也许你的磁盘很慢,也许是 QTextStream,也许是 QString。
  • 我还没有为 64 位窗口找到一个好的 Qt 分析器。 VerySleepy 有潜力,但它的输出是如此神秘,我几乎无法理解。有什么建议吗?
  • 这取决于您使用的编译器。我喜欢 gprof 的 gcc。对于 MSVC 编译器,可以使用标准的 vsprofiler。

标签: qt optimization qt4


【解决方案1】:

(这不回答探查器问题。它试图回答原始问题,即性能问题。)

我建议在这种情况下完全避免使用 QTextStream 看看是否有帮助。它可能有助于提高性能的原因是涉及开销,因为文本是 encoded internally to UTF-16 用于存储,然后在写出时再次解码为 ASCII 或 UTF-8。您有两个不需要的转换。

尝试仅使用标准 C++ std::ostringstream 类。它与 QTextStream 非常相似,您的代码只需要进行微小的更改。例如:

#include <sstream>

// ...

QFile outfile(outpath);
if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text
                | QIODevice::Truncate))
{
    qWarning("Could not open ASCII for writing!");
    return false;
}

/* compute XYZ precision */
int prec[3] = {0, 0, 0};

std::ostringstream out;
out.precision(3);
std::fixed(out);
// I assume config.delimiter is a QChar.
char del = config.delimiter.toLatin1();

/* write the header line */
out << "X" << del << "Y" << del << "Z";
if(config.fields & INTFIELD)
    out << del << "IntegerField";
if(config.fields & DBLFIELD)
    out << del << "DoubleField";
if(config.fields & INTFIELD2)
    out << del << "IntegerField2";

if(config.fields & TRIPLEFIELD)
    out << del << "Tri1" << del << "Tri2" << del << "Tri3";
out << "\n";

/* write out the points */
for(quint64 ptnum = 0; ptnum < numpoints; ++ptnum)
{
    pt = points.at(ptnum);
    out.precision(prec[0]);
    out << pt->getXYZ(0);
    out.precision(prec[1]);
    out << del << pt->getXYZ(1);
    out.precision(prec[2]);
    out << del << pt->getXYZ(2);
    out.precision(3);
    if(config.fields & INTFIELD)
        out << del << pt->getIntValue();
    if(config.fields & DBLFIELD)
        out << del << pt->getDoubleValue();
    if(config.fields & INTFIELD2)
        out << del << pt->getIntValue2();
    if(config.fields & TRIPLEFIELD)
    {
        out << del << pt->getTriple(0);
        out << del << pt->getTriple(1);
        out << del << pt->getTriple(2);
    }
    out << "\n";

    // Write out the data and empty the stream.
    outfile.write(out.str().data(), out.str().length());
    out.str("");
}
outfile.close();

【讨论】:

  • 这是个好建议。不幸的是,此时我的标准 C++ 流使用非常生疏。鉴于我的示例代码,您能否提供一些关于代码的指导?
  • @Phlucious 我突然想到你不需要放弃 QFile。您只需将 QTextStream 替换为 std::ostringstream 以避免不必要的文本编码/解码。我更新了答案。
  • 我终于有时间对此进行测试了。不幸的是,我使用 std 流和char* 的某种组合运行的每个测试都不可避免地导致速度下降多达 80%。 (是的,我确定我使用的是\n 而不是endl。)最后我还是坚持使用原来的 QTextStream 实现。
  • @Phlucious 废话,两年半后:-D 无论如何,你最后的赌注是使用 fprintf()。这应该尽可能快,而无需在汇编中进行内核调用......
  • 我还会看到 ostringstream 比 QTextStream 慢了大约 25%。
【解决方案2】:

鉴于您正在写出数十亿条记录,您可能会考虑使用 boost karma 库:

http://www.boost.org/doc/libs/1_54_0/libs/spirit/doc/html/spirit/karma.html

根据他们的基准测试,它在大多数编译器/库(包括 Visual C++ 2010)上的运行速度比 C++ 流甚至 sprintf 快得多:

http://www.boost.org/doc/libs/1_54_0/libs/spirit/doc/html/spirit/karma/performance_measurements/numeric_performance/format_performance.html

这需要一些学习,但您将获得显着加速的回报。

【讨论】:

  • 我倾向于回避 Boost 库,因为我已经对 Qt 有很强的依赖,而且我认为避免大量依赖很有用。不过,这看起来很有趣。谢谢!
【解决方案3】:

使用多个内核(如果可用)! 在我看来,您数据的每个点都独立于其他点。 因此,您可以使用 QtConcurrent::mappedReduced 拆分预处理。 例如:

  1. 将您的数据分成一个序列块,每个块由 N(例如 1000)个点组成,
  2. 然后让您的 mapFunction 将每个块处理到内存缓冲区中
  3. reduceFunction 将缓冲区写入文件。

使用 OrderedReduce | SequentialReduce 作为选项。

这可以与其他优化一起使用!

【讨论】:

  • 我最终使用了大约 100k 点的块来执行此操作。 mapFunction 写入内存缓冲区,reduceFunction 写入磁盘。在 6 个线程(4 个内核)上,我获得了大约 50% 的速度提升。
  • @Phlucious:太好了。结果似乎是合理的,因为实际写入文件不是并行的,并且取决于输出媒体。
【解决方案4】:

如果您没有合适的分析器,但有一个调试器可以让您中断正在运行的应用程序,则可以选择手动分析: - 在调试器中启动应用程序,调用慢代码部分 - 在执行慢速部分时随机中断执行 - 查看调用堆栈并注意哪个子程序处于活动状态 - 重复几次(大约 10 倍左右)

现在您在大多数情况下找到相同程序的可能性很高 - 这是您必须避免/加快速度以改进事情的程序

【讨论】:

    【解决方案5】:

    在这里,我使用标准 C 库重写了您的一段代码 - 也许这样更快。我没有测试,所以您可能需要阅读一些 fprintf 格式规范文档 - 根据您的编译器格式标志可能会有所不同。

    注意 getTriple() 函数的返回类型 - 如果它不是浮点数,则必须更改前面格式规范中的 %f。

    #include <stdio.h>
    
    FILE* out;
    
    out = fopen(outpath, "w");
    if (out == NULL)
    {
        qWarning("Could not open ASCII for writing!");
        return false;
    } else {
        /* compute XYZ precision */
        int prec[3] = {0, 0, 0}; //these non-zero values are determined programmatically
    
        /* set up the writer */
        char del = config.delimiter;
    
        char s[255];        // or more if needed..
        /* write the header line */
        sprintf(s, "X%cY%cZ%c", del, del, del);
        fputs(s, out);
        if(config.fields & INTFIELD)
            fputs("IntegerField", out);
        if(config.fields & DBLFIELD)
            fputs("DoubleField", out);
        if(config.fields & INTFIELD2)
            fputs("IntegerField2", out);
        if(config.fields & TRIPLEFIELD) {
            sprintf(s, "%cTri1%cTri2%cTri3", del, del, del);
            fputs(s, out);
        }
        fputs("\n", out);
    
        /* write out the points */
        for(quint64 ptnum = 0; ptnum < numpoints; ++ptnum)
        {
            pt = points.at(ptnum);
            sprintf(s, "%.*f%c%.*f%c%.*f%c", prec[0], pt->getXYZ(0), del, prec[1], pt->getXYZ(1), del, prec[2], pt->getXYZ(2), del);
            fputs(s, out);            
            if(config.fields & INTFIELD)
                sprintf(s, "%d", pt->getIntValue());
            if(config.fields & DBLFIELD)
                sprintf(s, "%f", pt->getDoubleValue());
            if(config.fields & INTFIELD2)
                sprintf(s, "%d", pt->getIntValue2());
            fputs(s, out);
            if(config.fields & TRIPLEFIELD)
            {
                sprintf(s, "%c%f%c%f%c%f", del, pt->getTriple(0), del, pt->getTriple(1), del, pt->getTriple(2));    // assuming the getTriples() return double - need to adjust the %f to the real type
                fputs(s, out);
            }
            fputs("\n", out);
        } //end for every point
        fclose(out);
    }
    

    【讨论】:

      【解决方案6】:

      如果不强制使用文本输出,您可能希望将二进制输出与 QDataStream 一起使用。 由于无需执行格式化,写入或读取文件的时间将大大减少。

      void saveData(const QString & filename, const QVector<double> & iVect){
         QFile file(filename);
         if( !file.open(QIODevice::WriteOnly) )
            return;
         QDataStream out(file);
         for(int i=0;i<iVect.count();i++){
            out << iVect[i];
         file.close();
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-23
        • 2011-05-19
        • 1970-01-01
        • 2010-12-15
        • 2017-03-09
        • 1970-01-01
        • 2016-05-30
        • 1970-01-01
        • 2019-11-27
        相关资源
        最近更新 更多