数ヶ月前、Grand Theft Auto Onlineに関する驚くべき記事[Habréでの翻訳: 1と 2 ]がニュースに登場しました 。
記事全体を読むことをお勧めしますが、要するに、GTAオンライン は大きなJSONブロブを解析するときに突然2次パフォーマンスを示しました(複数の呼び出しのため
strlen
)。このエラーを修正した後、読み込み時間はほぼ70%減少しました。
これは活発な議論を引き起こしました :Cはこれのせいですか?または多分 「ウェブたわごと」?それとも 資本主義とそのインセンティブ?
しかし、誰もが1つのことに同意していました。 それは、そのようなナンセンスを書くことは決してなかったということです。
( あなたはすでに何が来るのかを感じることができますか?)
私のサイドプロジェクトの1つは、Erizoと呼ばれる高性能の3Dモデル ビューアです。
巧妙なコードで、2013 MacbookProでわずか165ミリ秒で97MBのSTLバイナリを開き ます。これは 驚くべき速度です。
互換性の理由から、ASCIISTL用の小さなパーサー も作成しました。
ASCII STLは、次のように指定されていないプレーンテキスト形式です。
solid cube_corner facet normal 0.0 -1.0 0.0 outer loop vertex 0.0 0.0 0.0 vertex 1.0 0.0 0.0 vertex 0.0 0.0 1.0 endloop endfacet facet normal 0.0 0.0 -1.0 outer loop vertex 0.0 0.0 0.0 vertex 0.0 1.0 0.0 vertex 1.0 0.0 0.0 endloop endfacet ... endsolid
私は次のようなコメントで非常に 堅牢なパーサーを作成しました。
/* ASCII STL: ,
* 'vertex', float. */
ASCII STLのロードは常に少し遅いように見えましたが、テキスト形式が非効率的であることが原因だと思いました。
( 雲が集まっています。)
数日でいくつかのイベントが発生しました。
- 数年ぶりに、古いErizoコードに戻って、macOSのフォーカスエラーを修正しました。
- GTAオンラインに関する記事を公開
- その後の議論から、私はそれを学びました
- sscanf
- ASCIISTLの読み込みが非常に遅いことに気づきました。
タイムスタンプが付けられた1.5MBASCII STLのダウンロードログは次のとおりです(秒単位)。
[erizo] (0.000000) main.c:10 | Startup! [erizo] (0.162895) window.c:91 | Created window [erizo] (0.162900) window.c:95 | Made context current [erizo] (0.168715) window.c:103 | Initialized GLEW [erizo] (0.178329) window.c:91 | Created window [erizo] (0.178333) window.c:95 | Made context current [erizo] (1.818734) loader.c:109 | Parsed ASCII STL [erizo] (1.819471) loader.c:227 | Workers have deduplicated vertices [erizo] (1.819480) loader.c:237 | Got 5146 vertices (7982 triangles) [erizo] (1.819530) loader.c:240 | Waiting for buffer... [erizo] (1.819624) loader.c:326 | Allocated buffer [erizo] (1.819691) loader.c:253 | Sent buffers to worker threads [erizo] (1.819883) loader.c:258 | Joined worker threads [erizo] (1.819887) loader.c:279 | Loader thread done [erizo] (1.821291) instance.c:32 | Showed window
打ち上げからウィンドウ表示まで1.8秒以上!
ASCIIパーサーを見直してみると、理由は明らかです。
/* The most liberal ASCII STL parser: Ignore everything except
* the word 'vertex', then read three floats after each one. */
const char VERTEX_STR[] = "vertex ";
while (1) {
data = strstr(data, VERTEX_STR);
if (!data) {
break;
}
/* Skip to the first character after 'vertex' */
data += strlen(VERTEX_STR);
for (unsigned i=0; i < 3; ++i) {
SKIP_WHILE(isspace);
float f;
const int r = sscanf(data, "%f", &f);
ABORT_IF(r == 0 || r == EOF, "Failed to parse float");
if (buf_size == buf_count) {
buf_size *= 2;
buffer = (float*)realloc(buffer, buf_size * sizeof(float));
}
buffer[buf_count++] = f;
SKIP_WHILE(!isspace);
}
}
コードには
sscanf
、データストリームの先頭から1つのfloat値を読み取り、 そのたびに文字列全体の長さをチェックするものがあることがわかります。
はい、GTAオンラインで働いていたプログラマーと同じ間違いをしました:突然二次パーサーを書きました!
通話
sscanf
を 通話に置き換えると
strtof
、読み込み時間が1.8秒から199ミリ秒にほぼ10分の1に短縮されました。
[erizo] (0.000000) main.c:10 | Startup! [erizo] (0.178082) window.c:91 | Created window [erizo] (0.178086) window.c:95 | Made context current [erizo] (0.184226) window.c:103 | Initialized GLEW [erizo] (0.194469) window.c:91 | Created window [erizo] (0.194472) window.c:95 | Made context current [erizo] (0.196126) loader.c:109 | Parsed ASCII STL [erizo] (0.196866) loader.c:227 | Workers have deduplicated vertices [erizo] (0.196871) loader.c:237 | Got 5146 vertices (7982 triangles) [erizo] (0.196921) loader.c:240 | Waiting for buffer... [erizo] (0.197013) loader.c:326 | Allocated buffer [erizo] (0.197082) loader.c:253 | Sent buffers to worker threads [erizo] (0.197303) loader.c:258 | Joined worker threads [erizo] (0.197306) loader.c:279 | Loader thread done [erizo] (0.199328) instance.c:32 | Showed window
何年もプログラミングをしていても、常に罠があることを忘れないでください 。で
sscanf
、それは非常に狡猾銃は足で自分自身を撮影しているので、その時の複雑さを満たし、それは誰もが私が無知の暗闇の中でさまよっていないように私には思いません。
あなた自身はこのように思い出されないかもしれませんが、悪いコードについての素晴らしい話を読むときはいつでも覚えておいてください-それ はあなたにも起こる可能性があります!
(明らかに、話の教訓はこれです:
sscanf
行の先頭から複数回の解析まで単一のトークンを使用しないで ください。それを避ければ大丈夫だと確信しています。)
広告
VDSinaは、強力で手頃な価格のVPSを毎日の支払いで提供します 。各サーバーのインターネットチャネルは500メガビットで、DDoS攻撃に対する保護が料金に含まれ、イメージからWindows、Linux、またはOSを一般的にインストールする機能、および非常に便利な専用サーバーのコントロールパネルも含まれてい ます。それを試してみてください!