1

我有一个 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 变量是一个包含整个文件内容的字符串。从第一个循环内部删除所有内容对时间影响没有任何影响。关于如何优化它的任何想法?

4

2 回答 2

4

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

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

于 2013-01-14T10:28:48.653 回答
1

零,简介!

首先,如果您只是使用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. 再次进行基准测试
于 2013-01-14T11:23:43.013 回答