使用getline缓慢的istringstream

时间:2013-01-14 09:58:55

标签: c++

我有一个Wavefront .obj文件解析器,它使用getline和stringstream解析数据。起初当模型很小时,没有问题,但是现在,当我尝试加载一个~207000行的模型时,只有第一遍我计算所有元素时才需要花费大量时间(~4.7秒)终端PC和第二次通过需要半分钟。另一方面,Blender仅需2秒左右即可加载整个模型。我使用visual studio 2012,目前处于调试模式。

我的计算元素的代码如下所示:

istringstream input(obj);
string line;
while (getline(input, line)) {
    if (line.find("# ") != string::npos) {
        // Comments.
    }
    else if (line.find("f ") != string::npos) {
        faces++;
    }
    else if (line.find("v ") != string::npos) {
        vertices += 3;
    }
    else if (line.find("vn ") != string::npos) {
        normals += 3;
    }
    else if (line.find("vt ") != string::npos) {
        uvCoordinates += 2;
    }
    else if (line.find("o ") != string::npos) {
        // Count here, if needed.
    }
}

实际加载需要大约30秒的整个数据的代码:

istringstream input(obj);
string line;
if (faces.capacity() > UINT_MAX / 3) {
    LOGE("Model cannot have more faces than: %d", UINT_MAX / 3);
    return false;
}
while (getline(input, line)) {
    vector<string> arr = stringSplit(line, ' ');
    string param = arr[0];
    int params = arr.size();
    if (line.length() == 0) {
        continue;
    }

    if (arr[0] == "v") { // Vertices.
        vertices.push_back(stringToFloat(arr[1].c_str()));
        vertices.push_back(stringToFloat(arr[2].c_str()));
        vertices.push_back(stringToFloat(arr[3].c_str()));
    }
    else if (arr[0] == "vn") { // Normals.
        normals.push_back(stringToFloat(arr[1].c_str()));
        normals.push_back(stringToFloat(arr[2].c_str()));
        normals.push_back(stringToFloat(arr[3].c_str()));
    }
    else if (arr[0] == "f") { // Faces.
        if (params < 4) {
            //LOGI("LINE: %s", line.c_str());
            continue;
        }
        else if (params > 4) {
            LOGI("Line: %s", line.c_str());
            LOGE("Obj models must only contain triangulated faces.");
            return false;
        }
        Face face;
        parseFace(face, line);
        faces.push_back(face);
    }
    else if (arr[0] == "vt") { // UV coordinates.
        uvCoordinates.push_back(stringToFloat(arr[1].c_str()));
        uvCoordinates.push_back(stringToFloat(arr[2].c_str()));
    }
    else if (arr[0] == "mtllib") { // Material.
        material = arr[1];
    }
    else if (arr[0] == "o") { // Sub-model.
        // Separate models here, if needed.
    }
}

obj变量是包含整个文件内容的字符串。 从第一个循环内部删除所有内容不会对时间影响产生任何影响。 关于如何优化这个的任何想法?

2 个答案:

答案 0 :(得分:4)

首先,尝试发布版本。调试版本可以调试,而不是快速。

另一件事是使用stringstream和getline导致大量复制和堆分配。为了获得最佳性能,您可以尝试仅使用索引遍历字符串,从原始字符串本身而不是从提取的片段中解析内容,依此类推。当然,您需要从标准库中替换一些功能。

答案 1 :(得分:1)

Zeroth,个人资料!

首先,如果您只是使用istringstream来调用getline()来获取字符串中的一行,而是创建您自己的函数,只需搜索下一个'\n'和给你字符串。你会避免这么多的开销。

其次,避免多次通过算法。为什么需要提前计算对象?

第三,避免不必要的重复内存分配/构建和释放/破坏。

arr变量移出循环。返工stringSplit()以拆分现有向量的现有元素,以避免重新分配向量及其中的字符串:

vector<string> arr = stringSplit(line, ' ');

除非您正在修改向量的元素,并且您确实需要此处的字符串副本,否则请避免复制,而是使用对const字符串的引用:

string param = arr[0];

此处,初始化push_back()代替变量,首先调整向量的大小,然后在其最后一个元素上调用parseFace()

Face face;
parseFace(face, line);
faces.push_back(face);

避免使用这些长if/else if个链或至少对它们进行排序,以便最常见的实体位于链的顶部。更好的是,仅使用switch-case块中的第一个字母和完整比较进行切换。编译器可以将switch语句优化为平衡决策树或跳转表。

if (arr[0] == "v") { // Vertices.
//...
}
else if (arr[0] == "vn") { // Normals.
//...
}
else if (arr[0] == "f") { // Faces.
//...
}
else if (arr[0] == "vt") { // UV coordinates.
//...
}
else if (arr[0] == "mtllib") { // Material.
//...
}
else if (arr[0] == "o") { // Sub-model.
//...
}

修改

至于第一遍,如果没有它,它会如何影响性能并且矢量正在调整大小?

如果您预先在矢量中预留空间,例如1000个面,1000个法线,3000个顶点(假设1:1:3是这些实体之间的典型比例)等,那么您的矢量将增长得更快并且将会与从空向量开始相比,避免调整大小上的大部分复制开销。

至于面孔,我的意思是改变这个:

Face face;
parseFace(face, line);
faces.push_back(face);

进入此(如果你保持push_back() apprach的风格):

std::size_t const faces_size = faces.size();
faces.resize(faces_size + 1);
parseFace(faces.back());

在所有情况下都要确保

  1. 基准至少3次运行
  2. 做一个应该改善事情的改变
  3. 再次
  4. 基准