【问题标题】:Faster Alternative to std::ofstreamstd::ofstream 的更快替代方案
【发布时间】:2011-06-08 02:29:42
【问题描述】:

我生成一组数据文件。由于文件应该是可读的,它们是文本文件(与二进制文件相反)。

为了向我的文件输出信息,我使用了非常舒服的 std::ofstream 对象。

一开始,当要导出的数据较小时,写入文件所需的时间并不明显。但是,由于要导出的信息已经积累,现在生成它们需要 大约 5 分钟

当我开始因等待而烦恼时,我的问题很明显:有没有比 std::ofstream 更快的替代方法?如果有更快的替代方案,是否值得重写我的应用程序?换句话说,可以节省 +50% 的时间吗?谢谢。


更新:

我被要求向您展示生成上述文件的我的代码,所以您在这里 - 最耗时的循环

ofstream fout;
fout.open(strngCollectiveSourceFileName,ios::out);

fout << "#include \"StdAfx.h\"" << endl;
fout << "#include \"Debug.h\"" << endl;
fout << "#include \"glm.hpp\"" << endl;
fout << "#include \"" << strngCollectiveHeaderFileName.substr( strngCollectiveHeaderFileName.rfind(TEXT("\\")) + 1) << "\"" << endl << endl;

fout << "using namespace glm;" << endl << endl << endl;


for (unsigned int nSprite = 0; nSprite < vpTilesetSprites.size(); nSprite++ )
{
    for(unsigned int nFrameSet = 0; nFrameSet < vpTilesetSprites[nSprite]->vpFrameSets.size(); nFrameSet++)
    {

        // display index definition
        fout << "// Index Definition: " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetLongDescription() << "\n";
        string strngIndexSignature = strngIndexDefinitionSignature;
        strngIndexSignature.replace(strngIndexSignature.find(TEXT("#aIndexArrayName#")), strlen(TEXT("#aIndexArrayName#")), TEXT("a") + vpTilesetSprites[nSprite]->GetObjectName() + vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetFrameSetName() + TEXT("IndexData") );
        strngIndexSignature.replace(strngIndexSignature.find(TEXT("#ClassName#")), strlen(TEXT("#ClassName#")), strngCollectiveArrayClassName );        
        fout << strngIndexSignature << "[4] = {0, 1, 2, 3};\t\t" << "// " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetShortDescription() << ": Index Definition\n\n";


        // display vertex definition
        fout << "// Vertex Definition: " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetLongDescription() << "\n";

        string strngVertexSignature = strngVertexDefinitionSignature;
        strngVertexSignature.replace(strngVertexSignature.find(TEXT("#aVertexArrayName#")), strlen(TEXT("#aVertexArrayName#")), TEXT("a") + vpTilesetSprites[nSprite]->GetObjectName() + vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetFrameSetName() + TEXT("VertexData") );
        strngVertexSignature.replace(strngVertexSignature.find(TEXT("#ClassName#")), strlen(TEXT("#ClassName#")), strngCollectiveArrayClassName );
        fout << strngVertexSignature << "[" << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetFramesCount() << "] =\n";
        fout << "{\n";

        for (int nFrameNo = 0; nFrameNo < vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetFramesCount(); nFrameNo++)
        {
            fout << "\t" << "{{ vec4(" << fixed << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[0].vPosition.fx << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[0].vPosition.fy << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[0].vPosition.fz << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[0].vPosition.fw << "f), vec2(" << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[0].vTextureUV.fu << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[0].vTextureUV.fv << "f) },  // " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetShortDescription() << " vertex 1: vec4(x, y, z, w), vec2(u, v) \n";
            fout << "\t" << " { vec4(" << fixed << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[1].vPosition.fx << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[1].vPosition.fy << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[1].vPosition.fz << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[1].vPosition.fw << "f), vec2(" << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[1].vTextureUV.fu << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[1].vTextureUV.fv << "f) },  // " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetShortDescription() << " vertex 2: vec4(x, y, z, w), vec2(u, v) \n";
            fout << "\t" << " { vec4(" << fixed << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[2].vPosition.fx << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[2].vPosition.fy << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[2].vPosition.fz << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[2].vPosition.fw << "f), vec2(" << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[2].vTextureUV.fu << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[2].vTextureUV.fv << "f) },  // " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetShortDescription() << " vertex 3: vec4(x, y, z, w), vec2(u, v) \n";
            fout << "\t" << " { vec4(" << fixed << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[3].vPosition.fx << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[3].vPosition.fy << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[3].vPosition.fz << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[3].vPosition.fw << "f), vec2(" << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[3].vTextureUV.fu << "f, " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->vpFrames[nFrameNo]->aVertices[3].vTextureUV.fv << "f) }},  // " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetShortDescription() << " vertex 4: vec4(x, y, z, w), vec2(u, v) \n\n";
        }
        fout << "};\n\n\n\n";
    }
}

fout.close();

【问题讨论】:

  • 根据我的经验,问题很少出在所使用的特定 IO 接口上,但更常见的是,问题在于通过串联构建 字符串s1 += "foo"; s1 += "bar"; s1 += "baz"; print(s1);。向我们展示您的代码,不要急于得出 输出 是问题的结论。至少没有良好的分析数据来支持它...... :)
  • @sarnold:+1,您好,感谢您的评论。尽管我经常使用上述串联,但目前我认为这不是我的情况。我按照你的建议做了,并在生成最大文件的最消耗循环的代码上方发布。
  • 附带说明;这是我最近看到的一些最难阅读的代码。换行符和变量是你的朋友。
  • "在这个时候,我不认为这是我的情况。" - 你怎么知道?我在您的问题中没有看到任何分析数据。
  • 这些行在做什么呢? TEXT("a") + vpTilesetSprites[nSprite]-&gt;GetObjectName() + vpTilesetSprites[nSprite]-&gt;vpFrameSets[nFrameSet]-&gt;GetFrameSetName() + TEXT("VertexData")。你也有很多对像strlen这样的函数的调用,这在一个紧密的循环中可能会出现问题。

标签: c++ std fstream ofstream


【解决方案1】:

如果您不想使用 C 文件 I/O,那么您可以尝试一下; FastFormat。查看比较了解更多信息。

【讨论】:

  • +1,您好,感谢您的精彩提示。我浏览了 FastFormat 的主页,其中一项关键改进是速度比 std::iostream 提高了 100-900%。但是,正如其他人指出的那样,在我的情况下速度变慢的原因可能是其他地方,而不是 std::ofstream 的使用。
【解决方案2】:

vpTilesetSpritesvpTilesetSprites[nSprite] 是如何存储的?它们是用列表还是数组实现的?有很多对它们的索引访问,如果它们是类似列表的结构,您将花费大量额外的时间来跟踪不必要的指针。 Ed S. 的评论是正确的:提供长索引临时变量和换行符可以使其更易于阅读,并且也许也更快:

fout << "// Index Definition: " << vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetLongDescription() << "\n";
string strngIndexSignature = strngIndexDefinitionSignature;
strngIndexSignature.replace(strngIndexSignature.find(TEXT("#aIndexArrayName#")), strlen(TEXT("#aIndexArrayName#")), TEXT("a") + vpTilesetSprites[nSprite]->GetObjectName() + vpTilesetSprites[nSprite]->vpFrameSets[nFrameSet]->GetFrameSetName() + TEXT("IndexData") );
strngIndexSignature.replace(strngIndexSignature.find(TEXT("#ClassName#")), strlen(TEXT("#ClassName#")), strngCollectiveArrayClassName );        

string idxsig = strngIndexDefinitionSignature;
sprite sp = vpTilesetSprites[nSprite];
frameset fs = sp->vpFrameSets[nFrameSet];

fout << "// Index Definition: " << fs->GetLongDescription() << "\n";
idxsig.replace(idxsig.find(TEXT("#aIndexArrayName#")), strlen(TEXT("#aIndexArrayName#")),
    TEXT("a") + sp->GetObjectName() + fs->getFrameSetName() + TEXT("IndexData"));
idxsig.replace(idxsig.find(TEXT("#ClassName#")), strlen(TEXT("#ClassName#")),
    strngCollectiveArrayClassName);

但是,更大的问题是如何将字符串用作模板;您正在寻找给定的文本字符串(并在每次需要时计算 needle 字符串的长度!)一遍又一遍

考虑一下:您正在执行查找和替换操作nSprite * nFrameSet 次。每次通过,这个循环:

  • 复制strngIndexDefinitionSignature
  • 在连接静态和动态字符串时创建四个临时字符串对象
  • 计算strlen(TEXT("#ClassName#"))
  • 计算strlen(TEXT("#aIndexArrayName#"))
  • 找到两者的起点
  • 用新文本替换这两个文本

这只是循环的前四行。

你能用格式字符串替换你的strngIndexDefinitionSignature吗?我假设它目前看起来像这样:

"flubber #aIndexArrayName# { blubber } #ClassName# blorp"

如果你像这样重写它:

"flubber a %s%sIndexData { blubber } %s blorp"

那么你的两个查找和替换行可以替换为:

sprintf(out, index_def_sig, sp->GetObjectName(), fs->getFrameSetName(),
    strngCollectiveArrayClassName);

这将删除两个find() 操作、两个replace() 操作、创建和销毁四个临时字符串对象、一个立即被两个replace() 调用覆盖的字符串副本,以及两个返回的strlen() 操作每次都得到相同的结果(但实际上并不需要)。

然后您可以像往常一样使用&lt;&lt; 输出您的字符串。或者,您可以将sprintf(3) 更改为fprintf(3),甚至避免使用临时C 字符串。

【讨论】:

  • @sarnold:+1,你好,sarnold。绝对精彩的答案。感谢您愿意分析我的代码。这个答案对我总体上提高我的编程技巧很有帮助。回答您的第一个问题:所有数据都存储在std::vector&lt;&gt; 容器中,包括vpTileSprites[]。我喜欢使用 Hungarian notation,前缀 vp 表明它是指针向量的对象。我会听从你的建议,并更多地使用变量来增加代码的可读性,尽管对我来说,我的代码是完全可读的,因为它是。 :-)
  • @sarnold:我明白你的意思。关于您的第二个问题:我从 .ini 文件中获取的大量数据,包括strngIndexDefinitionSignature。我使用DWORD WINAPI GetPrivateProfileString() 获取信息。所以我只有一个 char 数组。我不知道我可以将您的建议应用到 .ini 文件中的文本中,其格式如下:Index Definition = const unsigned short #ClassName#::#aIndexArrayName#。如果您有任何好的实用建议,那真的很有帮助。
  • 虽然这里的所有建议都非常好,但在我看来,this 是最详细和最有价值的答案。因此,我希望将此答案标记为我的问题的可接受的答案
  • @sarnold:为了澄清我所做的:我组织了一组要处理到目录中的自定义文件。每个目录都包含 .ini 文件,其中说明了特定目录中的内容应该如何处理。 然后,我的应用程序同时获取 .ini 文件信息和包含的文件。 它处理它们,此外,它还输出大量 .txt 数据文件。我的问题与那些 .txt 文件有关。
  • @Bunkai,非常好的反馈,我很高兴有你加入 Stack Overflow。 :) cplusplus claims constant time access by index,没关系。它可能昂贵恒定时间,但至少它是恒定的。如果我更了解 C++,我早就知道了。 :) 您可以更改ini 文件的格式吗? Index Definition = const unsigned short %s::%s。如果订单发生变化,这将失败。
【解决方案3】:

假设您以足够大的块执行此操作,直接调用 write() 可能会更快;也就是说,您最大的瓶颈很可能与 std::ofstream 没有任何直接关系。最明显的事情是确保您没有使用 std::endl (因为频繁刷新流会降低性能)。除此之外,我建议对您的应用进行分析,以了解其实际花费的时间。

【讨论】:

  • +1,您好 servn,感谢您提供的替代解决方案。这可能是我提出的基本问题,但是因为我从未做过,分析是如何执行的?是通过应用一些专门的分析工具来衡量瓶颈,还是简单的观察分析,并手动找出哪些代码区域可能存在问题?
  • 分析器是一种专门的工具;在 Mac、Shark 或 Instruments 上,在 Linux 上,oprofile(或 callgrind)。您还可以将调试器用作一种探查器;只需手动反复闯入调试器,看看堆栈跟踪是什么样的。
【解决方案4】:

ostream 的性能可能不是您的实际问题;我建议使用分析器来确定你真正的瓶颈在哪里。如果ostream 是您的实际问题,您可以下拉到&lt;cstdio&gt; 并使用fprintf(FILE*, const char*, ...) 格式化输出到文件句柄。

【讨论】:

  • +1,嗨,乔恩。您的建议基本上与其他一些建议一致,即我的速度问题可能在其他地方。如果我理解正确,我应该使用 software profiler 来分析我的应用程序。 你能给个建议吗,一个好的分析器,好吗?
  • @Bunkai.Satori:@sarnold 给出的答案涵盖了您当前的情况,但为了您将来的参考,gprof(GNU 分析器)是与 GCC 一起使用的一个很好的分析器。
  • 再次您好,非常感谢。我会看看gprof。很高兴这个网站提供了从世界上最好的程序员那里获得建议的机会:-)
【解决方案5】:

最佳答案取决于您生成的文本类型以及生成方式。 C++ 流可能很慢,但这主要是因为它们还可以为您做更多事情,例如依赖于语言环境的格式化等。

您可以通过绕过某些格式(例如ostream::write)或将字符直接写入流缓冲区(streambuf::sputn)来发现流的加速。有时增加相关流缓冲区的缓冲区大小会有所帮助(通过streambuf::pubsetbuf)。

如果这还不够好,您可能想尝试 C 样式的 stdio 文件,例如 fopenfprintf 等。 '不习惯这种方法,但性能通常相当不错。

为了获得绝对的最佳性能,您通常必须使用特定于操作系统的例程。有时直接的低级文件例程明显优于 C stdio,但有时则不然——例如,我看到有人说 Win32 上的 WriteFile 是 Windows 上最快的方法,而一些谷歌点击报告它比标准输出。另一种方法可能是内存映射文件,例如。 mmap + msync - 这基本上使用您的系统内存作为磁盘并将实际数据以大块的形式写入磁盘,这可能接近最佳状态。但是,如果由于某种原因在中途发生崩溃,您将面临丢失所有数据的风险,这对您来说可能是问题,也可能不是问题。

【讨论】:

  • +1,您好,感谢您提供的非常好的建议。您提供了更多的解决方案,并建议在 Windows 平台上哪个应该是最优化和最快的选择。最初,我一直在寻找这样的信息,直到有人建议 我的问题可能在其他地方:在我的循环中使用耗时的操作,如 字符串替换、搜索、 strlen() 等等..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-11
  • 2012-07-05
  • 2012-01-23
  • 2013-07-13
  • 2011-02-27
  • 2010-09-22
相关资源
最近更新 更多