2013-01-14 30 views
1

我有一个Wavefront .obj文件分析器,它使用getline和stringstream分析数据。起初,当模型很小时,没有问题,但现在,当我尝试加载大约207000行的模型时,仅在第一次统计所有元素的时候,它花费了大量的时间(〜4.7s)第二次传球需要半分钟。另一方面,搅拌机仅在2秒左右就可以加载整个模型。我使用visual studio 2012,目前处于调试模式。使用getline缓慢的istringstream

我计数元素代码如下所示:

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变量为包含整个文件内容的字符串。 删除第一个循环内的所有内容都不会影响时间。 关于如何优化这个的任何想法?

+1

值得注意的是流趋于缓慢,如果你需要的性能,实现自己的解决方案。 – lsalamon

回答

1

Zeroth,简介!

首先,如果你正在使用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); 

避免这些长链或至少对链排序,以便最频繁的实体位于链的顶部。更好的是,只使用switch-case区块中的第一个字母和全部比较开关。编译器可以将开关语句优化为平衡决策树或跳转表。

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. 做一个chan是应该提高的东西
  3. 基准GE再次
+0

谢谢。我会尝试你所有的解决方案。只是一个通知,没有第一遍,我将无法获得精确数量的面,顶点,法线等,所以我将无法指定我的向量应分配多少内存。没有分配推动向量会慢得多,对吧?我不确定我是否理解你在面部表达的意思。你建议在循环之前调整矢量大小,并且只对parseFace方法使用循环元素的引用? – SMart

+0

@SMGhost:已编辑。 – wilx

+0

很难衡量正确,但无论是否保留记忆,加载模型所需的时间似乎都保持不变〜41秒。 – SMart

4

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

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