非常差的boost :: lexical_cast性能

Nav*_*een 45 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)
Run Code Online (Sandbox Code Playgroud)

我看到的时间是

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)
Run Code Online (Sandbox Code Playgroud)

我看到的时间是

c ++:56129毫秒

java:2852毫秒

python:30780毫秒

所以对于双打,c ++实际上是python速度的一半,比java解决方案慢20倍!! 有关改进boost :: lexical_cast性能的任何想法?这是源于糟糕的字符串流实现还是我们可以预期使用boost库会导致性能降低10倍.

pae*_*bal 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. 这意味着提升仍然越来越好

原始答案

只是为了添加关于Barry和Motti的优秀答案的信息:

一些背景

请记住,Boost是由这个星球上最好的C++开发人员编写的,并由相同的最佳开发人员审核.如果lexical_cast错了,有人会批评或用代码攻击图书馆.

我猜你错过了lexical_cast真正的价值......

比较苹果和橘子.

在Java中,您将整数转换为Java String.你会注意到我不是在谈论一个字符数组或一个用户定义的字符串.你也会注意到,我不是在谈论你的用户定义的整数.我在谈论严格的Java Integer和严格的Java String.

在Python中,您或多或少都在做同样的事情.

正如其他帖子所说,从本质上讲,您使用的是Java和Python等价物sprintf(或者标准较低itoa).

在C++中,您使用的是非常强大的演员.在原始速度性能方面并不强大(如果你想要速度,也许sprintf更适合),但在可扩展性方面却很强大.

比较苹果.

如果要比较Java Integer.toString方法,则应将其与C sprintf或C++ ostream工具进行比较.

C++流解决方案比我的g ++快6倍lexical_cast,而且可扩展性更低:

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 ;
}
Run Code Online (Sandbox Code Playgroud)

C sprintf解决方案比我的g ++快8倍,lexical_cast但安全性要低得多:

inline void toString(const int value, char * output)
{
   sprintf(output, "%i", value) ;
}
Run Code Online (Sandbox Code Playgroud)

这两种解决方案都比Java解决方案快(或快)(根据您的数据).

比较橙子.

如果要比较C++ lexical_cast,则应将其与此Java伪代码进行比较:

Source s ;
Target t = Target.fromString(Source(s).toString()) ;
Run Code Online (Sandbox Code Playgroud)

源和目标都是你想要的任何类型,包括类似的内置类型,boolean或者int由于模板而在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
Run Code Online (Sandbox Code Playgroud)

通过为字符串和整数启用lexical_cast的这种特化(通过定义宏SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT),我的代码在我的g ++编译器上的速度提高了5倍,这意味着,根据您的数据,它的性能应该类似于Java.

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

  • 我有些不同意.是的,`lexical_cast`*比必要慢.这与其灵活性和可扩展性无关.相反,这是由于不幸的事实,字符串流需要复制到字符串而不是将其内容的所有权传递给字符串对象.这是不幸的,不必要的,可以做得更好.它*可能是糟糕的设计 - 不一定是Boost家伙,而是那些设计`stringstream`类而没有放弃其内部字符缓冲区所有权的选项,可以说它确实是一个糟糕的选择. (15认同)
  • lexical_cast里面究竟发生了什么,所以它太慢了? (6认同)
  • 我没有对此进行分析,所以我不能肯定地说,但我希望lexical_cast的成本来自两个来源:1)需要字符串复制(std :: stringstream维护自己动态分配的缓冲区), 2)动态分配的需求(在C/C++中比在托管语言中慢得多)IOStreams(包括stringstream)有很多缺陷.`lexical_cast`只是包装它,所以它几乎无法改善**.但是你表演了多少个"lexical_casts"?表现真的很重要*? (6认同)
  • 为什么不阅读消息来源,或Neil的解释,并亲自了解一下? (3认同)
  • @paercebal:感谢您对C解决方案的建议和详细解释.虽然没有性能保证是强制性的,但它并没有让我对这个库的效率有一定的信心,因为它以通用的方式进行投射(谁知道它可能比其他东西慢100倍).你能想到lexical_cast在专业案例的大概(~2x)范围内的例子吗?如果我告诉你std :: vector可能比特定情况慢5到20倍,我打赌你对使用该库的信心非常低. (2认同)
  • 警告!如果您使用此特化,并且从您正在使用的某个库链接了一个非专业的`lexical_cast <std :: string,int>`实例化,您可能会也可能不会使用您的专业化! (2认同)

Kir*_*sky 20

你可以专注lexical_castintdouble类型.使用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;
}
Run Code Online (Sandbox Code Playgroud)

此变体将比使用默认实现更快,因为在默认实现中存在重流对象的构造.它应该比它快一点printf,因为printf应该解析格式字符串.

  • 提升lexical_cast是非常缓慢和痛苦的低效率. (6认同)
  • 很好的主意!+1. (2认同)

Bar*_*lly 14

lexical_cast比您在Java和Python中使用的特定代码更通用.在许多场景中运行的一般方法(词法转换只是流出然后返回到临时流中)并不比特定例程慢,这并不奇怪.

(顺便说一下,使用静态版本可以从Java中获得更好的性能,Integer.toString(int).[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
    }
}
Run Code Online (Sandbox Code Playgroud)

运行时,使用JDK 1.6.0-14,服务器VM:

10MM Time taken: 688 ms
10MM Time taken: 547 ms
Run Code Online (Sandbox Code Playgroud)

在客户端VM中:

10MM Time taken: 687 ms
10MM Time taken: 610 ms
Run Code Online (Sandbox Code Playgroud)

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

  • stepancheg - 不一定是真的.看看我添加的小基准测试. (2认同)
  • @Barry.感谢关于静态情况的建议,在我的情况下它快了大约20%. (2认同)

小智 9

lexical演员在你的代码中做了什么可以简化为:

string Cast( int i ) {
    ostringstream os;
    os << i;
    return os.str();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 创建字符串流可能分配内存
  • 调用operator <<表示整数i
  • 结果存储在流中,可能分配内存
  • 从流中获取字符串副本
  • 创建一个字符串的副本以便返回.
  • 内存被释放

在你自己的代码中:

 s = Cast( i );
Run Code Online (Sandbox Code Playgroud)

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

string s = Cast( i );
Run Code Online (Sandbox Code Playgroud)

代替.

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

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


dha*_*rdy 8

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

lexical_cast主要是因为它是通用的(模板查找在编译时发生,因此不需要虚函数调用或其他查找/解引用).lexical_cast在我看来,它很慢,因为它建立在C++ iostream之上,它主要用于流操作而不是单个转换,因为lexical_cast必须检查和转换iostream错误信号.从而:

  • 必须创建和销毁流对象
  • 在上面的字符串输出案例中,请注意C++编译器很难避免缓冲区副本(另一种方法是直接格式化到输出缓冲区,就像sprintf这样,但sprintf不会安全地处理缓冲区溢出)
  • lexical_cast必须检查stringstreamerrors(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.还有一篇链接文章,内容更详细.


jef*_*ger 8

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

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