簡體   English   中英

非常差的boost :: lexical_cast性能

[英]Very poor boost::lexical_cast performance

Windows XP SP3。 Core 2 Duo 2.0 GHz。 我發現boost :: lexical_cast性能非常慢。 想找出加速代碼的方法。 在visual c ++ 2008上使用/ O2優化並與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倍!! 有關改進boost :: lexical_cast性能的任何想法? 這是源於糟糕的字符串流實現還是我們可以預期使用boost庫會導致性能降低10倍。

編輯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. 可能在兩個版本之間介紹了一個優化(我會搜索)
  3. 這意味着提升仍然越來越好

原始答案

只是為了添加關於Barry和Motti的優秀答案的信息:

一些背景

請記住,Boost是由這個星球上最好的C ++開發人員編寫的,並由相同的最佳開發人員審核。 如果lexical_cast是如此錯誤,有人會批評或使用代碼攻擊圖書館。

我猜你錯過了lexical_cast的實際價值......

比較蘋果和橘子。

在Java中,您將整數轉換為Java String。 你會注意到我不是在談論一個字符數組或一個用戶定義的字符串。 你也會注意到,我不是在談論你的用戶定義的整數。 我在談論嚴格的Java Integer和嚴格的Java String。

在Python中,您或多或少都在做同樣的事情。

正如其他帖子所說,從本質上講,你使用的是sprintf的Java和Python等價物(或者標准較低的itoa )。

在C ++中,您使用的是非常強大的演員。 在原始速度性能方面並不強大(如果你想要速度,也許sprintf會更合適),但在可擴展性方面卻很強大。

比較蘋果。

如果要比較Java Integer.toString方法,則應將其與C sprintf或C ++ ostream工具進行比較。

C ++流解決方案比lexical_cast快6倍(在我的g ++上),並且可擴展性更低:

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

這意味着只要您的對象可以輸出到流中,並從流中輸入,您就可以在其上使用lexical_cast ,而無需觸及任何單行代碼。

那么, lexical_cast的用途是什么?

詞法鑄造的主要用途是:

  1. 易於使用(嘿,一個適合所有東西的C ++演員!)
  2. 將它與模板繁重的代碼相結合,您的類型被參數化,因此您不想處理細節,並且您不想知道類型。
  3. 如果你有基本的模板知識,仍然可能相對有效,我將在下面演示

第2點在這里非常重要,因為它意味着我們只有一個接口/函數將類型的值轉換為另一種類型的相等或相似值。

這是您錯過的真正意義,而這就是性能方面的成本。

但它真是太棒了!

如果你想要原始速度性能,記住你正在處理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倍,這意味着,根據您的數據,其性能應該與Java類似。

我花了10分鍾看看增強代碼,並編寫了一個遠程高效且正確的32位版本。 通過一些工作,它可能會更快更安全(如果我們對std::string內部緩沖區有直接寫訪問權限,我們可以避免使用臨時外部緩沖區)。

你可以為intdouble類型專門化lexical_cast 在你的專業中使用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應該解析格式字符串。

lexical_cast比您在Java和Python中使用的特定代碼更通用。 毫無疑問,在許多情況下(詞法轉換只是流出然后返回到臨時流中)的一般方法最終會比特定例程慢。

(順便說一句,你可以使用靜態版本Integer.toString(int)從Java中獲得更好的性能。[1])

最后,字符串解析和deparsing通常不是性能敏感的,除非正在編寫編譯器,在這種情況下, 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

盡管從理論上講,轉義分析可能允許在堆棧上進行分配,並且內聯可能會將所有代碼(包括復制)引入本地方法,從而允許消除冗余復制,這種分析可能需要花費大量時間並導致相當多的代碼空間,在代碼緩存中有其他成本,在實際代碼中不能證明自己是正確的,而不像這里看到的微基准測試那樣。

lexical演員在你的代碼中做了什么可以簡化為:

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

不幸的是,每次調用Cast()時都會發生很多事情:

  • 創建字符串流可能分配內存
  • 調用operator <<表示整數i
  • 結果存儲在流中,可能分配內存
  • 從流中獲取字符串副本
  • 創建一個字符串的副本以便返回。
  • 內存被釋放

在你自己的代碼中:

 s = Cast( i );

該任務涉及進一步的分配和解除分配。 您可以使用以下方法稍微減少這一點:

string s = Cast( i );

代替。

但是,如果性能對您來說真的很重要,那么您應該考慮使用不同的機制。 您可以編寫自己的Cast()版本(例如)創建靜態字符串流。 這樣的版本不是線程安全的,但這可能對您的特定需求無關緊要。

總而言之,lexical_cast是一個方便實用的功能,但在其他方面需要權衡這種便利(一如既往)。

不幸的是我還沒有足夠的代表評論......

lexical_cast主要不是很慢,因為它是通用的(模板查找在編譯時發生,因此不需要虛函數調用或其他查找/解引用)。 在我看來, lexical_cast很慢,因為它建立在C ++ iostream之上,它主要用於流操作而不是單個轉換,並且因為lexical_cast必須檢查並轉換iostream錯誤信號。 從而:

  • 必須創建和銷毀流對象
  • 在上面的字符串輸出案例中,請注意C ++編譯器很難避免緩沖區副本(另一種方法是直接格式化輸出緩沖區,就像sprintf一樣,盡管sprintf不能安全地處理緩沖區溢出)
  • lexical_cast必須檢查stringstream錯誤( ss.fail() )以便在轉換失敗時拋出異常

lexical_cast很好,因為(IMO)異常允許捕獲所有錯誤而無需額外的努力,因為它有一個統一的原型。 我個人不明白為什么這些屬性中的任何一個都需要慢速操作(當沒有發生轉換錯誤時),雖然我不知道這些快速的C ++函數(可能是Spirit或boost :: xpressive?)。

編輯:我剛剛發現一條消息,提到使用BOOST_LEXICAL_CAST_ASSUME_C_LOCALE來啟用“itoa”優化: httpBOOST_LEXICAL_CAST_ASSUME_C_LOCALE 還有一篇鏈接文章 ,內容更詳細。

lexical_cast可能會或可能不會像你的benchark所指出的那樣與Java和Python相關,因為你的基准測量可能有一個微妙的問題。 由詞法轉換或它使用的iostream方法完成的任何工作空間分配/解除分配都是由您的基准測量的,因為C ++不會推遲這些操作。 但是,在Java和Python的情況下,相關的解除分配實際上可能只是被推遲到未來的垃圾收集周期並被基准測量所遺漏。 (除非在基准測試進行過程中偶然發生GC循環,在這種情況下,您將測量太多)。 因此,如果不仔細研究Java和Python實現的細節,很難確定應該將多少“成本”歸因於可能(或可能不)最終強加的延遲GC負擔。

這種問題顯然可能適用於許多其他C ++與垃圾收集語言基准測試。

正如Barry所說, lexical_cast非常通用,你應該使用更具體的替代方案,例如檢查itoaint->string )和atoistring -> int )。

如果速度是一個問題,或者你只是對C ++的演員陣容感興趣,那么就會有一個感興趣的話題

Boost.Spirit 2.1(將與Boost 1.40一起發布)似乎非常快,甚至比C等效(strtol(),atoi()等更快)。

我使用這種非常快速的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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM