47

视窗 XP SP3。酷睿 2 双核 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 毫秒

蟒蛇: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 毫秒

蟒蛇:30780毫秒

所以对于 doubles,c++ 实际上是 python 速度的一半,比 java 解决方案慢 20 倍!!。关于提高 boost::lexical_cast 性能的任何想法?这是否源于糟糕的字符串流实现,或者我们是否可以预期使用 boost 库会导致性能普遍下降 10 倍。

4

9 回答 9

77

编辑 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. 这意味着boost仍然越来越好

原始答案

只是为了添加有关 Barry's 和 Motti 出色答案的信息:

一些背景

请记住,Boost 是由这个星球上最优秀的 C++ 开发人员编写的,并由同样优秀的开发人员进行审查。如果lexical_cast错了,有人会用批评或代码入侵图书馆。

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

比较苹果和橘子。

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

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

正如其他帖子所说,本质上,您使用的是 Java 和 Python 等价物sprintf(或不太标准的itoa)。

在 C++ 中,您使用的是非常强大的强制转换。在原始速度性能方面并不强大(如果您想要速度,也许sprintf会更适合),但在可扩展性方面强大。

比较苹果。

如果要比较 JavaInteger.toString方法,则应将其与 Csprintf或 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 ;
}

Csprintf解决方案将比(在我的 g++ 上)快 8 倍,lexical_cast但安全性要低得多:

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()) ;

booleanSource 和 Target 可以是您想要的任何类型,包括像or这样的内置类型int,这在 C++ 中是可能的,因为模板。

可扩展性?这是脏话吗?

不,但它有一个众所周知的成本:当由同一编码人员编写时,针对特定问题的通用解决方案通常比针对特定问题编写的特定解决方案要慢。

在当前情况下,从幼稚的观点来看,lexical_cast将使用流工具将类型A转换为字符串流,然后从该字符串流转换为类型B

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

那么,有什么用途lexical_cast呢?

词法转换的主要用途是:

  1. 易于使用(嘿,一个适用于所有有价值的 C++ 转换!)
  2. 将它与模板繁重的代码相结合,您的类型是参数化的,因此您不想处理细节,也不想知道类型。
  3. 如果您有基本的模板知识,仍然可能相对有效,我将在下面演示

第 2 点在这里非常重要,因为这意味着我们只有一个接口/函数可以将一个类型的值转换为另一个类型的相等或相似的值。

这是您错过的真正要点,这是在性能方面付出代价的要点。

但它是如此slooooooowwww!

如果您想要原始速度性能,请记住您正在处理 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 分钟查看 boost 代码,并编写了一个远程高效且正确的 32 位版本。通过一些工作,它可能会更快更安全(std::string例如,如果我们可以直接写入内部缓冲区,我们可以避免使用临时外部缓冲区)。

于 2009-08-09T09:50:55.250 回答
20

您可以专注lexical_castintdouble类型。在您的专长中使用strtod和。strtol

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应该解析格式字符串。

于 2009-08-09T09:43:29.600 回答
14

lexical_cast比您在 Java 和 Python 中使用的特定代码更通用。毫不奇怪,一种适用于许多场景的通用方法(词法转换只不过是从一个临时流中流出然后返回到一个临时流)最终比特定例程慢。

(顺便说一句,使用静态版本,您可能会从 Java 中获得更好的性能,Integer.toString(int). [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

在客户端虚拟机中:

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

即使理论上,逃逸分析可能允许在堆栈上分配,并且内联可能会将所有代码(包括复制)引入本地方法,允许消除冗余复制,但这种分析可能需要相当长的时间并导致相当多的代码空间,与此处看到的微基准测试相反,代码缓存中的其他成本无法在实际代码中证明自己是合理的。

于 2009-08-09T06:23:06.323 回答
9

您的代码中的词法转换可以简化为:

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

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

  • 创建一个可能分配内存的字符串流
  • 运算符 << 用于整数 i 被调用
  • 结果存储在流中,可能分配内存
  • 从流中获取字符串副本
  • (可能)创建字符串的副本以返回。
  • 内存被释放

然后在您自己的代码中:

 s = Cast( i );

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

string s = Cast( i );

反而。

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

总而言之,lexical_cast 是一个方便且有用的特性,但这种便利性(总是必须的)伴随着其他领域的权衡。

于 2009-08-09T09:59:31.473 回答
8

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

lexical_cast主要不是慢,因为它是通用的(模板查找发生在编译时,因此不需要虚函数调用或其他查找/取消引用)。lexical_cast在我看来,它很慢,因为它建立在 C++ iostreams 之上,主要用于流式操作而不是单次转换,并且因为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。还有一个更详细的链接文章。

于 2010-06-24T16:06:35.497 回答
8

lexical_cast与 Java 和 Python 相关的速度可能会也可能不会像您的基准测试所显示的那样慢,因为您的基准测试可能存在一个微妙的问题。任何由词法转换或它使用的 iostream 方法完成的工作区分配/解除分配都由您的基准测试,因为 C++ 不会推迟这些操作。然而,在 Java 和 Python 的情况下,相关的释放实际上可能只是被推迟到未来的垃圾回收周期,而被基准测量遗漏了。(除非在基准测试正在进行时偶然发生 GC 循环,在这种情况下,您测量的次数过多)。因此,如果不检查 Java 和 Python 实现的细节,就很难确定最终可能(或可能不会)施加的延迟 GC 负担有多少“成本”。

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

于 2011-09-09T18:14:44.460 回答
2

正如 Barry 所说,lexical_cast非常笼统,您应该使用更具体的替代方案,例如查看itoa ( int->string) 和atoi ( string -> int)。

于 2009-08-09T06:48:22.060 回答
1

如果速度是一个问题,或者您只是对使用 C++ 进行此类转换的速度感兴趣,那么有一个关于它的感兴趣的线程。

Boost.Spirit 2.1(将与 Boost 1.40 一起发布)似乎非常快,甚至比 C 等效项(strtol()、atoi() 等)还要快。

于 2009-08-09T10:30:55.700 回答
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
于 2015-02-19T12:22:31.053 回答