非常差的boost :: lexical_cast性能

时间:2009-08-09 06:17:07

标签: c++ boost lexical-cast

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倍。

9 个答案:

答案 0 :(得分:78)

编辑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. 这意味着提升仍然越来越好
  4. 原始答案

    只是添加关于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转换为字符串流,然后从此字符串流转换为类型{{1 }}

    这意味着只要您的对象可以输出到流中,并从流中输入,您就可以在其上使用B,而无需触及任何一行代码。

    那么,lexical_cast的用途是什么?

    词法铸造的主要用途是:

    1. 易于使用(嘿,一个适合所有东西的C ++演员!)
    2. 将它与模板重码相结合,您的类型将被参数化,因此您不想处理细节,也不想知道类型。
    3. 如果你有基本的模板知识,仍然可能相对有效,我将在下面演示
    4. 第2点在这里非常重要,因为它意味着我们只有一个接口/函数可以将类型的值转换为另一种类型的相等或相似值。

      这是你错过的真正意义,这就是绩效方面的成本。

      但它真是太棒了!

      如果您想要原始速度性能,请记住您正在处理C ++,并且您有很多设施可以有效地处理转换,并且仍然保留lexical_cast易用性功能。

      我花了几分钟时间查看lexical_cast源代码,并提供了一个可行的解决方案。将以下代码添加到C ++代码中:

      lexical_cast

      通过为字符串和整数启用lexical_cast的这种特化(通过定义宏#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 ),我的代码在g ++编译器上的速度提高了5倍,这意味着,根据您的数据,其性能应该类似于Java的。

      我花了10分钟时间查看了增强代码,编写了一个远程高效且正确的32位版本。通过一些工作,它可能会更快更安全(如果我们对SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT内部缓冲区有直接写访问权限,我们可以避免使用临时外部缓冲区)。

答案 1 :(得分:20)

您可以为lexical_castint类型专门设置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应该解析格式字符串。

答案 2 :(得分:14)

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

在客户端虚拟机中:

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

即使理论上,转义分析可能允许在堆栈上进行分配,并且内联可能会将所有代码(包括复制)引入本地方法,允许消除冗余复制,这样的分析可能需要花费很多时间并导致相当一些代码空间,在代码缓存中有其他成本,在实际代码中不能证明自己是正确的,而不像这里看到的微基准测试。

答案 3 :(得分:9)

您的代码中的词汇演员可以简化为:

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

不幸的是,每次调用Cast()时都会发生很多事情:

  • 创建了一个可能分配内存的字符串流
  • operator&lt;&lt;对于整数i,我称之为
  • 结果存储在流中,可能分配内存
  • 从流
  • 中获取字符串副本
  • 创建一个字符串的副本(可能)以便返回。
  • 内存已取消分配

在您自己的代码中:

 s = Cast( i );

分配涉及进一步的分配和解除分配。您可以使用以下方法稍微减少这一点:

string s = Cast( i );

代替。

但是,如果性能对您来说真的很重要,那么您应该考虑使用不同的机制。您可以编写自己的Cast()版本(例如)创建静态字符串流。这样的版本不是线程安全的,但这可能对您的特定需求无关紧要。

总而言之,lexical_cast是一个方便实用的功能,但在其他方面需要权衡这种便利(一如既往)。

答案 4 :(得分:8)

不幸的是我还没有足够的代表发表评论......

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”优化:http://old.nabble.com/lexical_cast-optimization-td20817583.html。还有一个链接的article,其中包含更多细节。

答案 5 :(得分:8)

lexical_cast 与您的bencharks相比,可能会或可能不会像Java和Python一样慢,因为您的基准测量可能会有一个微妙的问题。由词法转换或它使用的iostream方法完成的任何工作空间分配/解除分配都是由您的基准测量的,因为C ++不会推迟这些操作。但是,在Java和Python的情况下,相关的解除分配实际上可能只是被推迟到未来的垃圾收集周期并被基准测量所遗漏。 (除非在基准测试进行过程中偶然发生GC循环,在这种情况下,您将测量太多)。因此,如果不仔细研究Java和Python实现的细节,很难确定应该将多少“成本”归因于可能(或可能不)最终强加的延迟GC负担。

这类问题显然可能适用于许多其他C ++与垃圾收集语言基准测试。

答案 6 :(得分:2)

正如Barry所说,lexical_cast非常笼统,您应该使用更具体的替代方案,例如结帐itoaint->string)和atoi({{1 }})。

答案 7 :(得分:1)

如果速度是一个问题,或者你只是对这种演员阵容在C ++中的速度感兴趣,那么就会对它感兴趣thread

Boost.Spirit 2.1(将与Boost 1.40一起发布)似乎非常快,甚至比C等价物(strtol(),atoi()等更快。)

答案 8 :(得分:1)

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