【问题标题】:stream parsing algorithm speed issue流解析算法速度问题
【发布时间】:2023-03-19 18:01:03
【问题描述】:

我编写了一个波前 Obj 解析器类,用于将 obj 模型导入我的 OpenGL 项目。我在调试模式下测试了这个类,发现它慢得让人难以忍受。

代码有效,我做了明显的调整,以确保它在合理实用的范围内尽可能高效。

不过,加载我的测试文件,一个 12mb 的 obj 文件,运行大约 330,000 行文本,需要一分钟多的时间来解析。

很沮丧,我有一个谷歌,果然,I wasn't the first person to run into this problem

这个在 gamedev.net 上发布查询的人只是在发布模式下运行他的算法,在 Visual Studio IDE 和 whammo 之外运行,性能可以接受。这也对我有用,我的 ~70 秒减少到 ~3 秒。

我对算法进行了一些分析,瓶颈在于对 std::getline 的调用,以及以下内容:

sstream >> sToken;

其中 sstream 是一个 std::stringstream 而 sToken 是一个 std::string(预先保留空间)。

问题

为什么 IDE 在运行我的解析算法时速度如此之慢(即使在发布模式下) - 在通过 IDE 运行代码时我可以做些什么来加快这一速度(F5 - 运行项目)?这使得调试变得非常缓慢。 IDE 是否将代码/挂钩注入到可执行文件中以通过 IDE 运行,或者这是否可以归结为缓存未命中或其他原因?

优化

我对文件进行两次遍历,在遍历一次时,我只计算令牌类型 - 这样我就可以保留空间(而不是迭代增长存储顶点、法线、texcoords、面等的向量)

sLineBuffer.reserve( 100 );
sToken.reserve(10);

while( sstream.good() )
{
    sstream >> sToken;
    getline( sstream, sLineBuffer );

    if( sToken.compare("f") == 0 )
        nFaces ++;

    else if( sToken.compare("v") == 0 )
        nVertices ++;

    else if( sToken.compare("vn") == 0 )
        nNormals ++;

    else if( sToken.compare("vt") == 0 )
        nTextures ++;

    else if( sToken.compare("g") == 0 )
        nGroups ++;
}

m_Vertices.reserve( nVertices );
m_Normals.reserve( nNormals );
m_TexCoords.reserve( nTextures );
m_Faces.reserve( nFaces );
m_Groups.reserve( nGroups );

第一次通过的成本很低(在调试模式下约为 8 秒,在 IDE 外的发布模式下约为 0.3 秒),并且效率节省很大(将解析时间从调试模式下的约 180 秒减少到约 60 秒)。

我还将整个文件读入一个字符串流中,以便将磁盘访问排除在等式之外:

// Read entire file from disk into memory
fstream stream;
stringstream sstream;
stream.open( m_sFilename.c_str(), std::ios::in );
sstream << stream.rdbuf();
stream.close();

此外,在可能的情况下,在整个算法中,我会尝试提前为 std::strings 预留空间,以便它们不会根据每个字符调整大小:

sLineBuffer.reserve( 100 );
sToken.reserve(10);  // etc

【问题讨论】:

  • 也许映射文件会有所帮助。
  • “在 IDE 中运行”是指在调试器下运行,还是用 Ctrl-F5 启动也有同样的问题?此外,当您在 IDE 之外运行时,您使用的是相同的二进制文件还是不同的版本,即使两者都是“发布”版本,编译器选项也可能存在一些差异?
  • 这可能与您报告的减速无关,但代码 while(sstream.good()) 不是一个好主意。以这种方式测试.good().eof() 的I/O 循环是bad practice
  • 当然慢,是debug构建。在调试版本中转储一个大数据集,用一个小数据集测试它,依靠发布版本来处理一个大数据集是没有意义的。
  • Blastfurnace - .good() 不会成为使用 Glowcode 分析算法的瓶颈。你能解释一下为什么你认为这是一个坏主意吗?哦,刚刚找到你的链接,没关系

标签: c++ visual-studio-2010 optimization


【解决方案1】:

这个问题原来是对 Visual Studio IDE 操作方式的误解。

按 F5 可在调试模式下运行,无论您是在调试还是发布版本。

我了解到 Ctrl+F5 将调试器排除在等式之外(但只有在运行发布版本时,您才会看到速度提高)。

我还了解到,在这种情况下,stdio 可能是更好的解决方案。我将不得不重写我的算法以按照建议使用 fscanf,并在此处报告我的发现,尽管我对这个想法感到畏缩。

【讨论】:

    【解决方案2】:

    STL 的编写方式是期望编译器对许多小函数进行大量内联。虽然调试器允许您进入所有美妙的抽象层,但您在调试模式下为此付出了高昂的代价,因为它不能内联任何东西。

    通常我不会给出以下建议,但在解析 OBJ 文件的情况下,我建议只丢弃 STL 并依赖老式的 fscanf 语句。您会发现在调试过程中会有显着的提升,甚至在发布模式下速度也会有显着提高。

    【讨论】:

    • STL 技巧很有趣,虽然不得不使用 stdio 库重写让我叹息!计算令牌以保留向量大小可以显着提高速度。这意味着它们不必随着它们的增长而调整大小。结果不言自明,有不同意你的。如果我删除第一遍,第二遍需要三倍的时间。
    • 如果您删除第一次通过,在发布构建期间是否需要三倍的时间?您说在调试构建期间花费了 3 倍的时间。
    • 是的,发布构建仍然需要更长的时间。
    • 那我会编辑我的答案,谢谢你教我一些新东西!
    猜你喜欢
    • 1970-01-01
    • 2013-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-20
    • 1970-01-01
    相关资源
    最近更新 更多