9

プロファイリングを通じて、ここでの sprintf には長い時間がかかることがわかりました。y/m/dh/m/s フィールドの先頭のゼロを引き続き処理する、より優れたパフォーマンスの代替手段はありますか?

SYSTEMTIME sysTime;
GetLocalTime( &sysTime );
char buf[80];
for (int i = 0; i < 100000; i++)
{

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d",
        sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
        sysTime.wHour, sysTime.wMinute, sysTime.wSecond);

}

注: OP はコメントで、これは簡略化された例であると説明しています。「実際の」ループには、データベースからのさまざまな時間値を使用する追加のコードが含まれています。sprintf()プロファイリングにより、犯人が特定されました。

4

12 に答える 12

20

ジョブを実行する独自の関数を作成している場合、0 .. 61 の文字列値のルックアップ テーブルを使用すると、年以外のすべての計算を行う必要がなくなります。

編集:うるう秒に対処する(および一致strftime()させる)には、60秒と61秒の値を出力できる必要があることに注意してください。

char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" };

あるいは、どうstrftime()ですか?パフォーマンスがどのように比較されるかはわかりませんが (単に sprintf() を呼び出している可能性があります)、一見の価値があります (上記のルックアップ自体を実行している可能性があります)。

于 2008-11-07T13:09:07.073 に答える
6

出力の各文字を順番に埋めてみることができます。

buf[0] = (sysTime.wYear / 1000) % 10 + '0' ;
buf[1] = (sysTime.wYear / 100) % 10 + '0';
buf[2] = (sysTime.wYear / 10) % 10 + '0';
buf[3] = sysTime.wYear % 10 + '0';
buf[4] = '-';

...など...

きれいではありませんが、あなたは絵を手に入れます。他に何もないとしても、sprintf がそれほど高速にならない理由を説明するのに役立つかもしれません。

OTOH、最後の結果をキャッシュできるかもしれません。そうすれば、毎秒 1 つ生成するだけで済みます。

于 2008-11-07T13:01:13.173 に答える
6

Printf は、さまざまな形式を処理する必要があります。確かに、printf のソースを取得し、それを基礎として使用して、特にsysTime構造を処理する独自のバージョンを展開することができます。そうすれば、引数を 1 つ渡すだけで、実行する必要がある作業だけが実行され、それ以上のことは何も行われません。

于 2008-11-07T13:01:19.850 に答える
3

Jaywalker が非常によく似た方法を提案しているようです (1 時間以内に私を打ち負かしました)。

既に提案されているルックアップ テーブル メソッド (以下の n2s[] 配列) に加えて、通常の sprintf の負荷が軽減されるように、フォーマット バッファーを生成してみてはどうでしょうか? 以下のコードは、年/月/日/時間が変更されていない限り、ループのたびに分と秒を入力するだけで済みます。明らかに、それらのいずれかが変更された場合、別の sprintf ヒットを取得しますが、全体としては、現在目にしている以上のものではない可能性があります (配列ルックアップと組み合わせた場合)。


static char fbuf[80];
static SYSTEMTIME lastSysTime = {0, ..., 0};  // initialize to all zeros.

for (int i = 0; i < 100000; i++)
{
    if ((lastSysTime.wHour != sysTime.wHour)
    ||  (lastSysTime.wDay != sysTime.wDay)
    ||  (lastSysTime.wMonth != sysTime.wMonth)
    ||  (lastSysTime.wYear != sysTime.wYear))
    {
        sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s",
                sysTime.wYear, n2s[sysTime.wMonth],
                n2s[sysTime.wDay], n2s[sysTime.wHour]);

        lastSysTime.wHour = sysTime.wHour;
        lastSysTime.wDay = sysTime.wDay;
        lastSysTime.wMonth = sysTime.wMonth;
        lastSysTime.wYear = sysTime.wYear;
    }

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]);

}
于 2008-11-07T14:00:31.337 に答える
3

「長い」時間とはどういう意味ですか?sprintf()ループ内の唯一のステートメントであり、ループの「配管」(インクリメント、比較) は無視できるため、最も多くの時間を消費する必要がありますsprintf()

ある夜、3 番街で結婚指輪をなくしてしまったが、5 番街のほうが明るかったので探したという古いジョークを覚えていますか? 非効率的な仮定を「証明」するように設計された例を作成しsprintf()ました。

sprintf()使用する他のすべての関数とアルゴリズムに加えて、「実際の」コードをプロファイリングすると、結果はより正確になります。または、必要な特定のゼロ埋め込み数値変換に対処する独自のバージョンを作成してみてください。

その結果に驚くかもしれません。

于 2008-11-07T13:07:03.363 に答える
2

私はいくつかのことをするでしょう...

  • 現在の時刻をキャッシュして、毎回タイムスタンプを再生成する必要がないようにする
  • 手動で時間変換を行います。ファミリ関数の最も遅い部分はprintfフォーマット文字列の解析であり、ループの実行ごとにその解析にサイクルを費やすのはばかげています。
  • すべての変換に 2 バイトのルックアップ テーブルを使用してみてください ( { "00", "01", "02", ..., "99" })。これは、剰余算術を避けたいためであり、2 バイトのテーブルは、その年に 1 つの剰余のみを使用する必要があることを意味します。
于 2008-11-07T13:48:35.450 に答える
2

結果をキャッシュするのはどうですか?その可能性はありませんか?この特定の sprintf() 呼び出しがコード内で頻繁に行われることを考えると、これらの連続した呼び出しのほとんどの間で、年、月、日は変わらないと思います。

したがって、次のようなものを実装できます。古い SYSTEMTIME 構造体と現在の SYSTEMTIME 構造体を宣言します。

SYSTEMTIME sysTime, oldSysTime;

また、日付と時刻を保持する別の部分を宣言します。

char datePart[80];
char timePart[80];

初めて、sysTime、oldSysTime、および datePart と timePart の両方を入力する必要があります。しかし、後続の sprintf() は、以下に示すように非常に高速にすることができます。

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
if (oldSysTime.wYear == sysTime.wYear &&
  oldSysTime.wMonth == sysTime.wMonth &&
  oldSysTime.wDay == sysTime.wDay)
  {
     // 日付部分を再利用できます
     strcpy (バフ、datePart);
     strcat (バフ、timePart);
  }
そうしないと {
     // 日付部分も再生成する必要があります
     sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay);
     strcpy (バフ、datePart);
     strcat (バフ、timePart);
}

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME));

上記のコードには、コードを理解しやすくするための冗長性があります。簡単に因数分解できます。ルーチンへの呼び出しよりも時間と分が速く変化しないことがわかっている場合は、さらに高速化できます。

于 2008-11-07T13:07:36.030 に答える
1

現在、同様の問題に取り組んでいます。

組み込みシステムで、タイムスタンプ、ファイル名、行番号などを含むデバッグ ステートメントをログに記録する必要があります。すでにロガーが配置されていますが、ノブを「完全なログ」に回すと、すべての proc サイクルが消費され、システムが悲惨な状態になり、コンピューティング デバイスが経験する必要はないと述べています。

「測定/観察しているものを変えずに、何かを測定/観察することはできない」と誰かが言いました。

だから私はパフォーマンスを向上させるために物事を変えています。現在の状態では、元の関数呼び出しよりも 2 倍高速です(そのログ システムのボトルネックは、関数呼び出しではなく、別の実行可能ファイルであるログ リーダーにあります。これは、独自のログ スタックを作成する場合に破棄できます。 )。

私が提供する必要があるインターフェースは、- のようなものvoid log(int channel, char *filename, int lineno, format, ...)です。チャネル名 (現在、リスト内で線形検索を行っています! すべてのデバッグ ステートメントに対して!) と、ミリ秒カウンターを含むタイムスタンプを追加する必要があります。これを高速化するために私が行っていることのいくつかを次に示します-

  • チャネル名を文字列化strcpyして、リストを検索せずに済むようにします。LOG(channel, ...etc)としてマクロを定義しますlog(#channel, ...etc)固定の10バイトのチャネル長を取得するようにmemcpy定義して、文字列の長さを修正する場合に使用できます。LOG(channel, ...) log("...."#channel - sizeof("...."#channel) + *11*)
  • タイムスタンプ文字列を 1 秒間に数回生成します。asctime などを使用できます。次に、固定長の文字列をすべてのデバッグ ステートメントに memcpy します。
  • タイムスタンプ文字列をリアルタイムで生成したい場合は、代入のあるルックアップ テーブル (memcpy ではありません!) が最適です。しかし、これは 2 桁の数字とおそらく年に対してのみ機能します。
  • 3 桁 (ミリ秒) と 5 桁 (lineno) はどうでしょうか。私は itoa が好きではなく、カスタムの itoa( digit = ((value /= value) % 10)) も好きではありません。div と mod は遅いからです。私は以下の関数を書きましたが、AMD の最適化マニュアル (アセンブリ内) に同様の記述があることを後で発見しました。これにより、これらが最速の C 実装であるという確信が持てます。

    void itoa03(char *string, unsigned int value)
    {
       *string++ = '0' + ((value = value * 2684355) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

    同様に、行番号については、

    void itoa05(char *string, unsigned int value)
    {
       *string++ = ' ';
       *string++ = '0' + ((value = value * 26844 + 12) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

全体として、私のコードはかなり高速になりました。使用vsnprintf()する必要がある時間は約 91% で、コードの残りの部分は 9% しかかかりません (コードの残りの部分、つまりvsprintf()以前は 54% かかっていました)。

于 2009-05-02T05:40:01.393 に答える
1

書式文字列を繰り返し解析することを避けることができ、sprintf が処理するより複雑なケースの多くに対処する必要がないため、戻り buf に数字をレイアウトするルーチンを手動でロールすることで、おそらく w perf の増加を得ることができます。ただし、実際にそれを行うことをお勧めするのは嫌いです。

これらの文字列を生成するために必要な量を何らかの形で減らすことができるかどうか、それらはオプションであるかどうか、キャッシュできるかどうかなどを把握することをお勧めします.

于 2008-11-07T13:03:05.290 に答える
0

StringStream は、Google から得た提案です。

http://bytes.com/forum/thread132583.html

于 2008-11-07T12:59:23.753 に答える
0

整数のフォーマットで sprintf に勝るとは想像しがたいです。本当に sprintf が問題ですか?

于 2008-11-07T13:05:55.487 に答える