如何优化ASCII输出与QTextStream



我目前正在将数十亿条二进制记录写入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();

(这并没有回答分析器的问题。它试图回答最初的问题,即性能问题。

我建议在这种情况下避免使用QTextStream,看看是否有帮助。它可能有助于提高性能的原因是涉及到开销,因为文本在内部被编码为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();

如果您正在编写数十亿条记录,您可能会考虑使用boost karma库:

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

根据他们的基准测试,它比c++流运行得快得多,甚至比大多数编译器/库(包括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

这需要一些学习,但你会得到显著的加速。

使用多个内核(如果可用)!在我看来,你的数据中的每一点都是相互独立的。因此,您可以使用QtConcurrent:: mappereduced分割预处理。例如:

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

使用OrderedReduce | SequentialReduce作为选项

这可以在其他优化之外使用!

如果你没有合适的分析器,但有一个允许你破坏运行的应用程序的调试器,手动分析是一个选择:-在调试器中启动应用程序,调用慢速代码部分-执行缓慢部分时随机中断执行-查看调用堆栈并注意哪个子程序是活动的-重复几次(约10倍左右)

现在你很有可能在大多数情况下发现相同的过程——这就是你必须避免/更快地改进的过程

这里我使用标准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);
}

如果不是强制使用文本输出,您可能希望在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();
}

最新更新