2013-01-14 99 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會導致大量的複製和堆分配。爲了獲得最佳性能,您可以嘗試僅使用索引遍歷字符串,從原始字符串本身而不是從提取的片段中解析東西,等等。當然,你需要從標準庫中替換一些功能。