1

getline と stringstream を使用してデータを解析する Wavefront .obj ファイル パーサーがあります。最初はモデルが小さかったときは問題ありませんでしたが、今では 207000 行までのモデルを読み込もうとすると、すべての要素をカウントする最初のパスだけで、高速で途方もない時間 (4.7 秒) がかかります。 PC を終了し、2 回目のパスには 30 分かかります。一方、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

まず、リリース ビルドを試します。デバッグ ビルドは、高速ではなく、デバッグ可能にすることを目的としています。

もう 1 つのことは、stringstream と getline を使用すると、多くのコピーとヒープ割り当てが発生することです。最大のパフォーマンスを得るには、インデックスのみを使用して文字列をトラバースしたり、抽出されたフラグメントではなく元の文字列自体から解析したりすることができます。もちろん、標準ライブラリの一部の機能を置き換える必要があります。

于 2013-01-14T10:28:48.653 に答える
1

ゼロス、プロフィール!

istringstreamまず、文字列から行を取得するために呼び出すだけの場合はgetline()、代わりに、単純に next を前方検索して文字列を取得する独自の関数を作成します'\n'。そうすれば、多くのオーバーヘッドを回避できます。

次に、マルチパス アルゴリズムを避けます。オブジェクトを事前にカウントする必要があるのはなぜですか?

第 3 に、不必要にメモリの割り当て/構築と解放/破棄を繰り返さないようにします。

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()スタイルを維持する場合):

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 に答える