我有一个Wavefront .obj文件解析器,它使用getline和stringstream解析数据。起初,当模型很小的时候,没有问题,但是现在,当我尝试加载一个有大约207000行的模型时,只有在第一次计算所有元素的时候,在高端PC上需要花费大量的时间(大约4.7秒),第二次需要半分钟。另一方面,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变量是包含整个文件内容的字符串。删除第一个循环内部的所有内容不会对时间影响产生任何影响。对如何优化这个有什么想法吗?
发布于 2013-01-14 19:23:43
第零号,侧写!
首先,如果您使用istringstream只是为了调用getline()从字符串中取出一行,那么可以创建您自己的函数,该函数将简单地向前搜索下一个'\n'并给出字符串。这样你就可以避免很多开销了。
第二,避免多通道算法。为什么需要提前清点对象?
第三,避免不必要的重复内存分配/构造和释放/销毁。
将arr变量移出循环。修改stringSplit()以拆分成现有向量的现有元素,以避免重新分配向量和其中的字符串:
vector<string> arr = stringSplit(line, ' ');除非您正在修改向量的元素,并且在这里确实需要字符串的副本,否则请避免复制,而使用对常量字符串的引用:
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);进入这个(如果你保持apprach的push_back()风格):
std::size_t const faces_size = faces.size();
faces.resize(faces_size + 1);
parseFace(faces.back());在所有情况下,请确保
的更改
发布于 2013-01-14 18:28:49
首先,尝试一个发布版本。调试版本应该是可调试的,而不是快速的。
另一件事是,使用stringstream和getline会导致大量的复制和堆分配。为了获得最佳性能,您可以尝试仅使用索引遍历字符串,从原始字符串本身解析内容,而不是从提取的片段中解析内容,等等。当然,您需要替换标准库中的一些功能。
https://stackoverflow.com/questions/14316264
复制相似问题