web-dev-qa-db-ja.com

非常に悪いboost :: lexical_castのパフォーマンス

Windows XPSP3。Core2 Duo 2.0GHz。boost:: lexical_castのパフォーマンスが非常に遅いことがわかりました。コードを高速化する方法を見つけたいと思っていました。/O2最適化の使用Visual C++ 2008と比較してJava 1.6およびpython 2.6.2と比較します。次の結果が表示されます。

整数キャスト:

c++: 
std::string s ;
for(int i = 0; i < 10000000; ++i)
{
    s = boost::lexical_cast<string>(i);
}

Java:
String s = new String();
for(int i = 0; i < 10000000; ++i)
{
    s = new Integer(i).toString();
}

python:
for i in xrange(1,10000000):
    s = str(i)

私が見ている時間は

c ++:6700ミリ秒

Java:1178ミリ秒

python:6702ミリ秒

c ++は、pythonと同じくらい遅く、Javaより6倍遅いです。

ダブルキャスティング:

c++:
std::string s ;
for(int i = 0; i < 10000000; ++i)
{
    s = boost::lexical_cast<string>(d);
}

Java:
String s = new String();
for(int i = 0; i < 10000000; ++i)
{
    double d = i*1.0;
    s = new Double(d).toString();
}

python:
for i in xrange(1,10000000):
    d = i*1.0
    s = str(d)

私が見ている時間は

c ++:56129ミリ秒

Java:2852ミリ秒

python:30780ミリ秒

したがって、ダブルスの場合、c ++は実際にはpythonの半分の速度であり、Javaソリューションよりも20倍遅い!!)。ブースト:: lexical_castパフォーマンスを改善するためのアイデア?これは文字列ストリームの実装が不十分なためか、ブーストライブラリを使用するとパフォーマンスが一般的に10倍低下することが予想されますか?.

45
Naveen

2012-04-11を編集

rve lexical_castのパフォーマンスについてかなり正しくコメントし、リンクを提供します。

http://www.boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/performance.html

現在1.49をブーストするためのアクセス権はありませんが、以前のバージョンでコードを高速化したことを覚えています。だから私は推測します:

  1. 次の回答は引き続き有効です(学習目的の場合のみ)
  2. おそらく、2つのバージョンの間のどこかに導入された最適化がありました(私はそれを検索します)
  3. つまり、ブーストはまだどんどん良くなっています

元の答え

バリーとモッティの優れた答えに関する情報を追加するだけです。

いくつかの背景

Boostはこの惑星で最高のC++開発者によって書かれ、同じ最高の開発者によってレビューされていることを覚えておいてください。 _lexical_cast_が非常に間違っていた場合、誰かが批判またはコードのいずれかでライブラリをハッキングしたでしょう。

_lexical_cast_の真の価値のポイントを逃したと思います...

リンゴとオレンジの比較。

Javaでは、整数をJava文字列にキャストします。文字の配列やユーザー定義の文字列について話しているのではないことに注意してください。また、ユーザー定義の整数ではなく、厳密なJava整数と厳密なJava文字列です。

Pythonでは、多かれ少なかれ同じことをしています。

他の投稿で述べたように、あなたは本質的に、JavaおよびPython sprintfと同等のもの(または標準的ではないitoa))を使用しています。

C++では、非常に強力なキャストを使用しています。本来の速度パフォーマンスの意味では強力ではありませんが(速度が必要な場合は、おそらくsprintfの方が適しています)、拡張性の意味で強力です。

りんごの比較。

Java _Integer.toString_メソッドを比較する場合は、C sprintfまたはC++ ostream機能と比較する必要があります。

C++ストリームソリューションは、(私のg ++​​では)_lexical_cast_の6倍高速で、拡張性はかなり低くなります。

_inline void toString(const int value, std::string & output)
{
   // The largest 32-bit integer is 4294967295, that is 10 chars
   // On the safe side, add 1 for sign, and 1 for trailing zero
   char buffer[12] ;
   sprintf(buffer, "%i", value) ;
   output = buffer ;
}
_

C sprintfソリューションは_lexical_cast_より8倍高速です(私のg ++​​では)が、安全性ははるかに劣ります。

_inline void toString(const int value, char * output)
{
   sprintf(output, "%i", value) ;
}
_

どちらの解決策も、あなたのJava解決策(データによると))と同じかそれより速いです。

オレンジの比較。

C++の_lexical_cast_を比較する場合は、次のJava擬似コードと比較する必要があります。

_Source s ;
Target t = Target.fromString(Source(s).toString()) ;
_

ソースおよびターゲットは、booleanintなどの組み込みタイプを含む、任意のタイプです。これは、C++ではテンプレートのために可能です。

拡張性?それは汚い言葉ですか?

いいえ、しかし、それはよく知られているコストがあります。同じコーダーによって書かれた場合、特定の問題に対する一般的なソリューションは、特定の問題に対して書かれた特定のソリューションよりも通常遅くなります。

現在の場合、単純な観点では、_lexical_cast_はストリーム機能を使用して、A型から文字列ストリームに変換し、次にこの文字列ストリームからB型に変換します。

つまり、オブジェクトをストリームに出力したり、ストリームから入力したりできる限り、コードを1行も変更することなく、そのオブジェクトに対して_lexical_cast_を使用できます。

では、_lexical_cast_の用途は何ですか?

字句キャスティングの主な用途は次のとおりです。

  1. 使いやすさ(ねえ、C++キャストはすべての値で機能します!)
  2. これをテンプレートの重いコードと組み合わせると、型がパラメータ化されるため、詳細を処理したくなく、型を知りたくありません。
  3. 以下に示すように、テンプレートの基本的な知識がある場合は、比較的効率的である可能性があります。

ここでポイント2は非常に重要です。これは、型の値を別の型の同等または類似の値にキャストするためのインターフェース/関数が1つしかないことを意味するためです。

これはあなたが見逃した本当のポイントであり、これはパフォーマンスの点でコストがかかるポイントです。

しかし、それはとてもすごいですwww!

本来の速度のパフォーマンスが必要な場合は、C++を扱っていること、および変換を効率的に処理するための多くの機能があり、しかも_lexical_cast_の使いやすさを維持することを忘れないでください。

Lexical_castのソースを見て、実行可能な解決策を見つけるまでに数分かかりました。 C++コードに次のコードを追加します。

_#ifdef SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT

namespace boost
{
   template<>
   std::string lexical_cast<std::string, int>(const int &arg)
   {
      // The largest 32-bit integer is 4294967295, that is 10 chars
      // On the safe side, add 1 for sign, and 1 for trailing zero
      char buffer[12] ;
      sprintf(buffer, "%i", arg) ;
      return buffer ;
   }
}

#endif
_

文字列と整数のlexical_castのこの特殊化を有効にすることで(マクロ_SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT_を定義することにより)、私のコードは私のg ++​​コンパイラーで5倍速くなりました。

そして、ブーストコードを確認するのに10分かかり、リモートで効率的で正しい32ビットバージョンを記述しました。そして、いくつかの作業により、おそらくより速く安全になります(たとえば、_std::string_内部バッファーへの直接書き込みアクセス権がある場合、一時的な外部バッファーを回避できます)。

77
paercebal

あなたはlexical_cast for intおよびdoubleタイプ。専門分野ではstrtodstrtolを使用してください。

namespace boost {
template<>
inline int lexical_cast(const std::string& arg)
{
    char* stop;
    int res = strtol( arg.c_str(), &stop, 10 );
    if ( *stop != 0 ) throw_exception(bad_lexical_cast(typeid(int), typeid(std::string)));
    return res;
}
template<>
inline std::string lexical_cast(const int& arg)
{
    char buffer[65]; // large enough for arg < 2^200
    ltoa( arg, buffer, 10 );
    return std::string( buffer ); // RVO will take place here
}
}//namespace boost

int main(int argc, char* argv[])
{
    std::string str = "22"; // SOME STRING
    int int_str = boost::lexical_cast<int>( str );
    std::string str2 = boost::lexical_cast<std::string>( str_int );

    return 0;
}

デフォルトの実装では重いストリームオブジェクトの構築があるため、このバリアントはデフォルトの実装を使用するよりも高速になります。また、printfはフォーマット文字列を解析する必要があるため、printfよりも少し高速になるはずです。

20

_lexical_cast_は、JavaおよびPythonで使用している特定のコードよりも一般的です。多くのシナリオで機能する一般的なアプローチ(字句キャストはストリーミングよりも少しだけ多い)であることは当然のことです一時的なストリームとの間でやり取りを繰り返すと、特定のルーチンよりも遅くなります。

(ところで、静的バージョンInteger.toString(int)を使用すると、Javaからパフォーマンスが向上する可能性があります。[1])

最後に、コンパイラーを作成していない限り、文字列の解析と解析は通常パフォーマンスに影響されません。その場合、_lexical_cast_は多目的になりすぎて、各桁がスキャンされるときに整数などが計算されます。

[1]コメンター「stepancheg」は、静的バージョンの方がパフォーマンスが向上するかもしれないという私のヒントを疑いました。これが私が使用したソースです:

_public class Test
{
    static int instanceCall(int i)
    {
        String s = new Integer(i).toString();
        return s == null ? 0 : 1;
    }

    static int staticCall(int i)
    {
        String s = Integer.toString(i);
        return s == null ? 0 : 1;
    }

    public static void main(String[] args)
    {
        // count used to avoid dead code elimination
        int count = 0;

        // *** instance

        // Warmup calls
        for (int i = 0; i < 100; ++i)
            count += instanceCall(i);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; ++i)
            count += instanceCall(i);
        long finish = System.currentTimeMillis();
        System.out.printf("10MM Time taken: %d ms\n", finish - start);


        // *** static

        // Warmup calls
        for (int i = 0; i < 100; ++i)
            count += staticCall(i);

        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; ++i)
            count += staticCall(i);
        finish = System.currentTimeMillis();
        System.out.printf("10MM Time taken: %d ms\n", finish - start);
        if (count == 42)
            System.out.println("bad result"); // prevent elimination of count
    }
}
_

JDK 1.6.0-14を使用するランタイム、サーバーVM:

_10MM Time taken: 688 ms
10MM Time taken: 547 ms
_

そしてクライアントVMでは:

_10MM Time taken: 687 ms
10MM Time taken: 610 ms
_

理論的には、エスケープ分析はスタックへの割り当てを許可し、インライン化はすべてのコード(コピーを含む)をローカルメソッドに導入し、冗長なコピーの排除を許可する場合があります。このような分析にはかなりの時間がかかり、かなりの量になる可能性があります。ここに見られるようなマイクロベンチマークとは対照的に、コードスペースには、実際のコードで正当化されないコードキャッシュの他のコストがあります。

14
Barry Kelly

コードで字句キャストが行っていることは、次のように簡略化できます。

string Cast( int i ) {
    ostringstream os;
    os << i;
    return os.str();
}

残念ながら、Cast()を呼び出すたびに多くのことが行われています。

  • 文字列ストリームが作成され、メモリが割り当てられる可能性があります
  • 演算子<<整数iが呼び出されます
  • 結果はストリームに格納され、メモリが割り当てられる可能性があります
  • 文字列のコピーがストリームから取得されます
  • 文字列のコピーが(おそらく)作成されて返されます。
  • メモリが解放されます

あなた自身のコードでThn:

 s = Cast( i );

割り当てにはさらに割り当てが含まれ、割り当て解除が実行されます。以下を使用することで、これを少し減らすことができる場合があります。

string s = Cast( i );

代わりに。

ただし、パフォーマンスが本当に重要な場合は、別のメカニズムの使用を検討してください。たとえば、静的な文字列ストリームを作成する独自のバージョンのCast()を記述できます。このようなバージョンはスレッドセーフではありませんが、特定のニーズには関係ない場合があります。

要約すると、lexical_castは便利で便利な機能ですが、そのような便利さは(常にそうでなければならない)他の領域でのトレードオフを伴います。

9
anon

残念ながら、コメントするのに十分な担当者がいません...

_lexical_cast_は汎用的であるため、主に低速ではありません(テンプレートの検索はコンパイル時に行われるため、仮想関数呼び出しや他の検索/逆参照は必要ありません)。 _lexical_cast_は、単一の変換ではなくストリーミング操作を主な目的とするC++ iostreamに基づいて構築されており、_lexical_cast_がiostreamエラー信号をチェックして変換する必要があるため、遅いと思います。したがって:

  • ストリームオブジェクトを作成して破棄する必要があります
  • 上記の文字列出力の場合、C++コンパイラーはバッファーのコピーを回避するのに苦労していることに注意してください(sprintfのように出力バッファーに直接フォーマットする方法もありますが、sprintfは安全ではありませんバッファオーバーランの処理)
  • _lexical_cast_は、変換の失敗時に例外をスローするために、stringstreamエラー(ss.fail())をチェックする必要があります

(IMO)例外は余計な手間をかけずにすべてのエラーをトラップできるため、プロトタイプが統一されているため、_lexical_cast_はすばらしいです。高速なC++関数(Spiritまたはboost :: xpressive?)については知りませんが、これらのプロパティのいずれかが(変換エラーが発生しないときに)低速な操作を必要とする理由を個人的には知りません。

編集:「itoa」最適化を有効にするための_BOOST_LEXICAL_CAST_ASSUME_C_LOCALE_の使用について言及しているメッセージを見つけました: http://old.nabble.com/lexical_cast-optimization-td20817583.html 。リンクされた article もあり、もう少し詳細があります。

8
dhardy

lexical_castは、ベンチマークの測定値が次のようになるため、JavaおよびPython微妙な問題。字句キャストまたはそれが使用するiostreamメソッドによって行われるワークスペースの割り当て/割り当て解除は、C++がこれらの操作を遅延しないため、ベンチマークによって測定されます。ただし、JavaおよびPython 、関連する割り当て解除は、実際には単に将来のガベージコレクションサイクルまで延期され、ベンチマーク測定によって見落とされている可能性があります(ベンチマークの進行中に偶然にGCサイクルが発生し、その場合、測定が多すぎる場合を除く)。 。したがって、JavaおよびPython実装の詳細を調べない限り、どれだけの「コスト」が遅延GCの負担に起因するかを確認することなく、確実に知ることは困難です。 (またはそうでない場合もあります)最終的に課せられます。

この種の問題は、明らかに他の多くのC++とガベージコレクションされた言語のベンチマークに当てはまる可能性があります。

8
jeff slesinger

バリーが言ったように、lexical_castは非常に一般的です。たとえば、チェックアウト itoaint->string)および atoistring -> int)。

2
Motti

速度が気になる場合、またはC++でのそのようなキャストの速度に興味がある場合は、それに関連する thread があります。

Boost.Spirit 2.1(Boost 1.40でリリース予定)は非常に高速で、Cの同等物(strtol()、atoi()など)よりも高速であるようです。

1
t.g.

私はこの非常に高速なソリューションをPODタイプに使用しています...

namespace DATATYPES {

    typedef std::string   TString;
    typedef char*         TCString;
    typedef double        TDouble;
    typedef long          THuge;
    typedef unsigned long TUHuge;
};

namespace boost {

template<typename TYPE>
inline const DATATYPES::TString lexical_castNumericToString(

                                const TYPE& arg, 
                                const DATATYPES::TCString fmt) {

    enum { MAX_SIZE = ( std::numeric_limits<TYPE>::digits10 + 1 )  // sign
                                                            + 1 }; // null
    char buffer[MAX_SIZE] = { 0 };

    if (sprintf(buffer, fmt, arg) < 0) {
        throw_exception(bad_lexical_cast(typeid(TYPE),
                                         typeid(DATATYPES::TString)));
    }
    return ( DATATYPES::TString(buffer) );
}

template<typename TYPE>
inline const TYPE lexical_castStringToNumeric(const DATATYPES::TString& arg) {

    DATATYPES::TCString end = 0;
    DATATYPES::TDouble result = std::strtod(arg.c_str(), &end);

    if (not end or *end not_eq 0) {
        throw_exception(bad_lexical_cast(typeid(DATATYPES::TString),
                                         typeid(TYPE)));
    }
    return TYPE(result);
}

template<>
inline DATATYPES::THuge lexical_cast(const DATATYPES::TString& arg) {
    return (lexical_castStringToNumeric<DATATYPES::THuge>(arg));
}

template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::THuge& arg) {
    return (lexical_castNumericToString<DATATYPES::THuge>(arg,"%li"));
}

template<>
inline DATATYPES::TUHuge lexical_cast(const DATATYPES::TString& arg) {
    return (lexical_castStringToNumeric<DATATYPES::TUHuge>(arg));
}

template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::TUHuge& arg) {
    return (lexical_castNumericToString<DATATYPES::TUHuge>(arg,"%lu"));
}

template<>
inline DATATYPES::TDouble lexical_cast(const DATATYPES::TString& arg) {
    return (lexical_castStringToNumeric<DATATYPES::TDouble>(arg));
}

template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::TDouble& arg) {
    return (lexical_castNumericToString<DATATYPES::TDouble>(arg,"%f"));
}

} // end namespace boost
1
David Larner