如何使用QTextStream优化ASCII输出

时间:2013-06-14 19:08:45

标签: qt optimization qt4

我目前正在向ASCII文件(ugh)写出数十亿的二进制记录。我的工作做得很好,但如果可以,我想优化性能。问题是,允许用户选择任意数量的字段进行输出,因此我无法在编译时知道它们将包含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();

6 个答案:

答案 0 :(得分:3)

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

我建议在这种情况下完全避免使用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();

答案 1 :(得分:1)

鉴于您要写出数十亿条记录,您可以考虑使用boost karma库:

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

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

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

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

答案 2 :(得分:1)

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

  1. 将您的数据划分为序列,每个块包含N个(例如1000个),
  2. 然后让你的 mapFunction 将每个块处理成一个内存缓冲区
  3. reduceFunction 将缓冲区写入文件。
  4. 使用OrderedReduce | SequentialReduce作为选项。

    除了其他优化之外,还可以使用它!

答案 3 :(得分:0)

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

现在你在大多数情况下找到相同程序的概率很高 - 这是为了改善事情而必须避免/加快的程序

答案 4 :(得分:0)

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

请注意getTriple()函数的返回类型 - 如果它不是float,则必须更改前面格式规范中的%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);
}

答案 5 :(得分:0)

如果不是必须使用文本输出,则可能要对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();
}