【问题标题】:Very poor boost::lexical_cast performance非常差的 boost::lexical_cast 性能
【发布时间】:2010-11-18 01:24:22
【问题描述】:

Windows 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 毫秒

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 毫秒

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

【问题讨论】:

  • -1 表示不成熟的社论和缺乏视角
  • 我删除了“可怜”的评论,我同意这是不必要的。有趣的是,在修复 lexical_cast accu.org/index.php/journals/1375 方面似乎已经完成了一些工作。所以这意味着如果你在 2006 年人们停止维护它时使用这个库,你会看到性能额外下降 3-6 倍。毫无疑问,库的编写者是出色的 C++ 程序员,但不妨退后一步,问一下提供这种通用性的库是否会使效率变得不可能。对比一下 STL,它保留了效率和通用性。
  • @Barry Kelly:作为一名资深的 C++ 程序员,我会说“这太可悲了!”如果我也看到了这些结果! @Burkhard:我希望有一种方法可以拒绝像你这样毫无意义的头发分裂 cmets。
  • 下面的文章有一组详细的结果对比boost lexical_cast和其他各种方法,结果不言而喻。 codeproject.com/KB/recipes/Tokenizer.aspx
  • 当前版本的boost并不慢:boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/…也许你可以重做你的测试并更新问题?

标签: c++ boost lexical-cast


【解决方案1】:

编辑 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's 和 Motti 出色答案的信息:

一些背景

请记住,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()) ;

Source 和 Target 可以是您想要的任何类型,包括像 booleanint 这样的内置类型,由于模板,这在 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 内部缓冲区具有直接写访问权限,我们可以避免使用临时外部缓冲区)。

【讨论】:

  • lexical_cast 内部到底发生了什么,所以它这么慢?
  • 为什么不阅读源代码或 Neil 的解释,然后自己找出答案?
  • @paercebal:感谢 C 解决方案的建议和详细解释。虽然没有强制性的性能保证,但它并没有让我对这个库以通用方式进行强制转换的效率充满信心(谁知道它可能会慢 100 倍)。你能想出 lexical_cast 在特殊案例的范围内(~2x)的例子吗?如果我告诉你 std::vector 可能比特定情况慢 5 到 20 倍,我敢打赌你对使用这个库的信心会很低。
  • 我没有对此进行分析,所以我不能肯定地说,但我预计 lexical_cast 的成本来自两个来源:1)字符串复制的需要(std::stringstream维护自己的动态分配缓冲区),以及 2)需要动态分配(在 C/C++ 中比在托管语言中慢得多)IOStreams(包括字符串流)有很多缺陷。 lexical_cast 只是将其包装起来,因此总体而言几乎无法改善这种情况。但是你执行了多少lexical_casts?性能真的重要吗?
  • 我不得不有点不同意。是的,lexical_cast 比必要的慢。这与它的灵活性和可扩展性无关。相反,这是由于不幸的事实,字符串流需要被复制到字符串,而不是将其内容的所有权传递给字符串对象。这是不幸的,不必要的,可以做得更好。它可以说是糟糕的设计 - 不一定是 Boost 人,而是那些设计 stringstream 类但没有选择放弃其内部字符缓冲区所有权的人,这确实是一个糟糕的选择。
【解决方案2】:

您可以将lexical_cast 专门用于intdouble 类型。在您的专业中使用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 应该解析格式字符串。

【讨论】:

  • 将 int 添加到字符串特化中。
  • Boost lexical_cast 极其缓慢且效率极低。
  • @Beh Tou Cheh,这是因为默认 boost::lexical_cast 使用流,而我的解决方案不是。
  • 当一个共享库具有此类特化(并实例化它们),而另一个共享库具有“原始”Boost 模板的实例化并且我的程序同时使用这两个库时,我遇到了这种方法的问题。 (在一定程度上)将使用哪种实现是不可预测的。所以我将它们排除在 boost 命名空间之外,并给它们一个不同的名称。
【解决方案3】:

lexical_cast 比您在 Java 和 Python 中使用的特定代码更通用。在许多情况下都有效的通用方法(词法转换只不过是从一个临时流中流出然后再从一个临时流中返回)最终会比特定例程慢,这并不奇怪。

(顺便说一句,使用静态版本,Integer.toString(int).[1],您可能会从 Java 中获得更好的性能)

最后,字符串解析和反解析通常对性能没有那么敏感,除非正在编写编译器,在这种情况下,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

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

【讨论】:

  • 使用静态 Integer.toString(int) 不会获得更好的性能,因为 JIT 运行良好。
  • stepancheg - 不一定是真的。请参阅我添加的小基准测试。
  • 巴里,在“静态”测试之后运行“实例”测试,您的“实例” toString 工作得更快。
  • @Barry。感谢您对静态案例的建议,在我的案例中它快了大约 20%。
  • 这是 9 次迭代的累积时间,即 3x 循环与 3x 交替对,首先静态运行。采样时间:总计:inst:6174 ms,stat:5483 ms
【解决方案4】:

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

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

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

  • 创建一个字符串流可能分配内存
  • 运算符
  • 结果存储在流中,可能会分配内存
  • 从流中获取字符串副本
  • (可能)创建了要返回的字符串副本。
  • 内存被释放

在你自己的代码中:

 s = Cast( i );

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

string s = Cast( i );

改为。

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

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

【讨论】:

  • +1,静态 ostringstream 是个好主意。通过从使用固定大小、预分配内存的 stringbuf 派生一个新的流缓冲区类并使用 rdbuf() 将其作为 ostringstream 的缓冲区提供,可能会挤出更多的性能——但是你会做一些工作为自己;)
【解决方案5】:

很遗憾,我还没有足够的代表发表评论...

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,其中包含更多详细信息。

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 你的回答没有解释,为什么 lexical_cast 慢。
      【解决方案8】:

      如果速度是一个问题,或者你只是对 C++ 的这种转换有多快感兴趣,有一个感兴趣的 thread 对此感兴趣。

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

      【讨论】:

        【解决方案9】:

        我将这种非常快速的解决方案用于 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-06-28
          • 1970-01-01
          • 1970-01-01
          • 2012-08-02
          • 1970-01-01
          • 2023-03-03
          相关资源
          最近更新 更多