2

operator<<いくつかのログを見ていると、プロファイラーでint の書式設定などに多くの時間を費やしていることに気付きました。ostream::operator<<int(およびおそらくdouble)をフォーマットするときに呼び出されるたびに使用される共有ロックがあるようです。さらに調査した結果、次の例に絞り込みました。

フォーマットを行うために使用する Loop1 ostringstream:

DWORD WINAPI doWork1(void* param)
{
    int nTimes = *static_cast<int*>(param);
    for (int i = 0; i < nTimes; ++i)
    {
        ostringstream out;
        out << "[0";
        for (int j = 1; j < 100; ++j)
            out << ", " << j; 
        out << "]\n";
    }
    return 0;
}

ostringstream同じことを使用して int 形式以外のすべてを行うLoop2 は、次のように行われitoaます。

DWORD WINAPI doWork2(void* param)
{
    int nTimes = *static_cast<int*>(param);
    for (int i = 0; i < nTimes; ++i)
    {
        ostringstream out;
        char buffer[13];
        out << "[0";
        for (int j = 1; j < 100; ++j)
        {
            _itoa_s(j, buffer, 10);
            out << ", " << buffer;
        }
        out << "]\n";
    }
    return 0;
}

テストでは、各ループを 1、2、3、および 4 スレッドで何度も実行しました (4 コアのマシンを使用しています)。試行回数は一定です。出力は次のとおりです。

doWork1: all ostringstream
n       Total
1         557
2        8092
3       15916
4       15501

doWork2: use itoa
n       Total
1         200
2         112
3         100
4         105

ご覧のとおり、ostringstream を使用したときのパフォーマンスはひどいものです。スレッドを追加すると 30 倍悪化しますが、itoa は約 2 倍速くなります。

1 つのアイデアは、この記事で M$ が_configthreadlocale(_ENABLE_PER_THREAD_LOCALE)推奨するように使用することです。それは私を助けないようです。同様の問題を抱えていると思われる別のユーザーがいます。

アプリケーションで並行して実行される複数のスレッドで int をフォーマットできる必要があります。この問題を考えると、これを機能させる方法を理解するか、別のフォーマット ソリューションを見つける必要があります。整数型と浮動小数点型に対してオーバーロードされた operator<< を使用して単純なクラスをコーディングし、基になるストリームで operator<< を呼び出すだけのテンプレート バージョンを作成することができます。operator<<(ostream&,T)少し醜いですが、私はそれを機能させることができると思いますostream.

また、これが Microsoft Visual Studio 2005 でビルドされていることも明確にする必要があります。この制限は、標準ライブラリの実装に起因すると考えています。

4

3 に答える 3

1

問題はメモリ割り当てである可能性があります。「new」が使用する malloc には内部ロックがあります。中に入ってみればわかります。スレッド ローカル アロケーターを使用して、パフォーマンスの低下が解消されるかどうかを確認してください。

于 2009-09-02T07:11:46.840 に答える
1

Visual Studio 2005 の標準ライブラリの実装にバグがある場合、他の実装を試してみませんか? お気に入り:

または、Visual Studio 2005 標準ライブラリのベースとなっているDinkumwareでさえ、2005 年以降に問題が修正されている可能性があります。

編集:あなたが言及した他のユーザーはVisual Studio 2008 SP1を使用していました。つまり、Dinkumwareはおそらくこの問題を修正していません。

于 2009-09-01T19:44:10.837 に答える
1

驚くことではありませんが、MS はかなりの数の共有リソースに「グローバル」ロックを設定しました。私たちにとって最大の頭痛の種は、数年前の BSTR メモリ ロックでした。

最善の方法は、コードをコピーして、ostream ロックと共有変換メモリを独自のクラスに置き換えることです。私は、printf スタイルのロギング システムを使用してストリームを書き込む場所でそれを行いました (つまり、printf ロガーを使用し、それをストリーム オペレーターでラップする必要がありました)。それをアプリにコンパイルしたら、itoa と同じくらい速くなるはずです。オフィスにいるときに、コードの一部を取得して貼り付けます。

編集:約束どおり:

CLogger& operator<<(long l)
{
    if (m_LoggingLevel < m_levelFilter)
        return *this;

    // 33 is the max length of data returned from _ltot
    resize(33);

    _ltot(l, buffer+m_length, m_base);
    m_length += (long)_tcslen(buffer+m_length);

    return *this;
};

static CLogger& hex(CLogger& c)
{
    c.m_base = 16;
    return c;
};

void resize(long extra)
{
    if (extra + m_length > m_size)
    {
        // resize buffer to fit.
        TCHAR* old_buffer = buffer;
        m_size += extra;
        buffer = (TCHAR*)malloc(m_size*sizeof(TCHAR));
        _tcsncpy(buffer, old_buffer, m_length+1);
        free(old_buffer);
    }
}

static CLogger& endl(CLogger& c)
{
    if (c.m_length == 0 && c.m_LoggingLevel < c.m_levelFilter)
        return c;

    c.Write();
    return c;
};

申し訳ありませんが、すべてを取得することはできませんが、これらの 3 つの方法は基本を示しています。バッファーを割り当て、必要に応じてサイズを変更し (m_size はバッファー サイズ、m_length は現在のテキストの長さ)、ログの間保持します。物体。バッファーの内容は、endl メソッドでファイル (または OutputDebugString、またはリストボックス) に書き込まれます。また、実行時の出力を制限するためのロギング「レベル」もあります。したがって、ostringstream への呼び出しをこれに置き換えるだけで、Write() メソッドがバッファーをファイルに送り、長さをクリアします。お役に立てれば。

于 2009-09-01T18:13:58.257 に答える