非常糟糕的boost :: lexical_cast性能

Windows XP SP3。 酷睿2双核2.0 GHz。 我发现boost :: lexical_cast性能非常慢。 希望find加快代码的方法。 在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解决scheme慢20倍! 任何改善boost :: lexical_cast性能的想法? 这是否源自糟糕的stringstream实现,或者我们可以预期使用boost库会使性能降低10倍。

编辑2012-04-11

对lexical_cast的performance进行了正确的评论,提供了一个链接:

http://www.boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/performance.html

我现在没有访问权限来提高1.49,但是我确实记得在旧版本上使我的代码更快。 所以我猜:

  1. 以下答案仍然有效(如果仅用于学习目的)
  2. 这两个版本之间可能有一个优化介绍(我会search)
  3. 这意味着提振仍然越来越好

原始答案

只是添加巴里和Motti的优秀答案的信息:

一些背景

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

我想你错过了lexical_cast的真正价值点…

比较苹果和橘子。

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

在Python中,你或多或less都在做同样的事情。

正如其他职位所说,本质上,你是使用sprintf的Java和Python的等价物(或更less的标准itoa )。

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

比较苹果。

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

C ++stream解决scheme比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解决scheme比lexical_cast快8倍(在我的g ++上),但是安全性要低得多:

 inline void toString(const int value, char * output) { sprintf(output, "%i", value) ; } 

这两种解决scheme或者比Java解决scheme(根据您的数据)更快或更快。

比较桔子。

如果你想比较一个C ++ lexical_cast ,那么你应该把它和这个Java伪代码进行比较:

 Source s ; Target t = Target.fromString(Source(s).toString()) ; 

源和目标是你想要的任何types,包括像booleanint这样的内置types,这在C ++中可能是因为模板。

可扩展性? 这是一个肮脏的词?

不,但它有一个众所周知的成本:当由同一个编码器编写时,针对特定问题的一般解决scheme通常比为特定问题编写的具体解决scheme慢。

在目前的情况下,在一个天真的观点, lexical_cast将使用stream设施从一个typesA转换成一个stringstream,然后从这个stringstream转换成一个typesB

这意味着只要你的对象可以被输出到一个stream中,并从一个stream中input,你就可以在其上使用lexical_cast ,而不用触及任何一行代码。

那么, lexical_cast什么用途呢?

词法铸造的主要用途是:

  1. 易于使用(嘿,一个C ++的演员,一切都是有价值的!)
  2. 将它与模板大量代码相结合,在那里你的types被参数化,因此你不想处理细节,而且你不想知道types。
  3. 如果您拥有基本的模板知识,仍然可能相对高效,如下所示

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

这是你错过的真正的一点,这是性能方面的成本点。

但它是如此slooooooowwww!

如果你想要原始的速度性能,记住你正在处理C ++,并且你有很多设备来有效地处理转换,并且仍然保持lexical_cast易于使用的特性。

花了我几分钟的时间来看看lexical_cast的来源,并提出了一个可行的解决scheme。 添加到您的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 

通过为string和整数(通过定义macrosSPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT )启用lexical_cast的这种专门化,我的代码在g ++编译器上的执行速度提高了5倍,这意味着根据您的数据,其性能应该与Java类似。

我花了10分钟时间查看boost代码,并编写了一个远程高效且正确的32位版本。 而有了一些工作,它可能会更快,更安全(如果我们有直接的写访问std::string内部缓冲区,我们可以避免一个临时的外部缓冲区,例如)。

你可以专注于intdoubletypes的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; } 

这个变体比使用默认的实现更快,因为在默认的实现中,构造了大量的stream对象。 它应该比printf快一点,因为printf应该parsing格式化string。

lexical_cast比在Java和Python中使用的特定代码更普遍。 毫不奇怪,在许多情况下工作的一般方法(词汇转换只不过是stream出和stream出临时stream)最终比特定的例程慢。

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

最后,stringparsing和parsing通常不是性能敏感的,除非编写一个编译器,在这种情况下, lexical_cast可能是过于通用的,并且在扫描每个数字时会计算整数等。

[1]评论员“stepancheg”怀疑我的暗示,静态版本可能会有更好的performance。 这是我使用的来源:

 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 

即使在理论上,转义分析可能允许在栈上分配,并且内联可以将所有代码(包括复制)引入到本地方法中,允许消除冗余复制,这种分析可能花费相当多的时间并导致相当多的代码空间,在代码caching中有其他代价,这些代码在实际代码中不能certificate自己,而不是像这里所看到的微观基准。

你的代码中正在进行的词法转换可以简化为:

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

每次打电话给Cast()时都会有很多不幸的事情发生:

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

在你自己的代码中:

  s = Cast( i ); 

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

 string s = Cast( i ); 

代替。

但是,如果performance真的对你很重要,你应该考虑使用不同的机制。 你可以编写自己的Cast()版本,例如创build一个静态的stringstream。 这样的版本不会是线程安全的,但这可能不会影响您的具体需求。

总而言之,lexical_cast是一个方便而有用的function,但是这样的便利来自其他领域的权衡。

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

lexical_cast主要不是很慢,因为它是通用的(模板查找发生在编译时,所以虚函数调用或其他查找/取消引用是不必要的)。 在我看来, lexical_cast是缓慢的,因为它build立在C ++ iostream上,主要用于stream式操作,而不是单一转换,因为lexical_cast必须检查并转换iostream错误信号。 从而:

  • 一个stream对象必须被创build和销毁
  • 在上面的string输出的情况下,请注意,C ++编译器很难避免缓冲区副本(另一种方法是直接格式化为输出缓冲区,就像sprintf那样,尽pipesprintf不能安全地处理缓冲区溢出)
  • lexical_cast必须检查stringstream错误( ss.fail() ),以便在转换失败时抛出exception

lexical_cast是很好的,因为(IMO)exception允许捕获所有的错误,没有额外的努力,因为它有一个统一的原型。 虽然我不知道这些C ++函数是否快速(可能是Spirit或boost :: xpressive?),但我个人并不明白为什么这些特性中的任何一个都需要慢速操作(当没有发生转换错误时)。

编辑:我刚刚发现一条消息提到使用BOOST_LEXICAL_CAST_ASSUME_C_LOCALE启用“itoa”优化: http : BOOST_LEXICAL_CAST_ASSUME_C_LOCALE 。 还有一个链接的文章更详细一点。

lexical_cast可能会或可能不会像Java和Python那样慢,因为您的基准测量可能有一个微妙的问题。 由词法转换或使用iostream方法完成的任何工作空间分配/释放都是由您的基准测量的,因为C ++不推迟这些操作。 但是,在Java和Python的情况下,相关的释放实际上可能简单地推迟到将来的垃圾收集周期,并被基准测量所遗漏。 (除非在基准testing过程中偶然发生GC周期,在这种情况下,你会测量太多)。 所以如果没有研究Java和Python实现的具体情况,很难确定知道可能(或不可能)最终强加的延迟GC负担应该归因于多less“成本”。

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

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

如果速度是一个问题,或者你只是感兴趣的速度有多快,这种转换可以用C ++,有兴趣的线程 。

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

我使用这个非常快速的PODtypes的解决scheme…

 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