我有一个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变量是包含整个文件内容的字符串。 从第一个循环内部删除所有内容不会对时间影响产生任何影响。 关于如何优化这个的任何想法?
答案 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());
在所有情况下都要确保