Boost C++ 库

...目前世界上备受推崇且设计精良的 C++ 库项目之一。 Herb SutterAndrei AlexandrescuC++ Coding Standards

实际考虑 - Boost C++ 函数库
PrevUpHomeNext

实际考虑

性能

理论上,使用 STL 算法和 lambda 仿函数相比手工编写的循环所带来的所有开销都可以被优化掉,就像标准 STL 函数对象和绑定器所带来的开销一样。根据编译器的不同,这在实践中也可能成立。我们在 1.5 GHz Intel Pentium 4 上使用 GCC 3.0.4 编译器进行了两项测试。使用了优化标志 -03。

在第一个测试中,我们将 lambda 仿函数与显式编写的函数对象进行了比较。我们使用了这两种风格来定义一元函数,该函数将参数自身反复相乘。我们从恒等函数开始,一直到 x5。这些表达式在 std::transform 循环中被调用,从一个 std::vector<int> 中读取参数并将结果放入另一个中。向量的长度为 100 个元素。运行时间列于表 17.3, “测试 1”。我们可以观察到这两种方法之间没有显著差异。

在第二个测试中,我们再次使用 std::transform 对一个 100 个元素长的向量中的每个元素执行一个操作。这次向量的元素类型是 double,我们从非常简单的算术表达式开始,然后转向更复杂的表达式。运行时间列于表 17.4, “测试 2”。在这里,我们还将经典的 STL 风格的匿名函数包含在测试中。我们不展示这些表达式,因为它们相当复杂。例如,表 17.4, “测试 2”中的最后一个表达式使用经典的 STL 工具编写,包含 7 次对 compose2 的调用,8 次对 bind1st 的调用,以及总共 14 次构造函数调用来创建 multipliesminusplus 对象。在此测试中,BLL 表达式比相应的动手编写的函数对象稍慢(平均慢约 10%,所有情况均慢不到 14%)。对于最简单的表达式,性能损失使用经典的 STL 表达式更大,高达 27%。

测试表明,BLL 与 STL 函数对象相比,不会带来性能损失。使用合理的优化编译器,性能特征应该与使用经典 STL 相当。此外,对于简单的表达式,性能预计将接近显式编写的函数对象的性能。但请注意,评估 lambda 仿函数由一系列对内联声明的小函数的调用组成。如果编译器未能实际内联展开这些函数,性能可能会受到影响。如果发生这种情况,运行时间可能会增加一倍以上。尽管上述测试不包括此类表达式,但我们已经遇到过一些看似简单的表达式出现这种情况。

表 17.3. 测试 1

整数乘法作为 lambda 表达式和作为传统手工编写的函数对象类所写表达式的 CPU 时间。运行时间以任意单位表示。
表达式 lambda 表达式 手工编写的函数对象
x 240 230
x*x 340 350
x*x*x 770 760
x*x*x*x 1180 1210
x*x*x*x*x 1950 1910


表 17.4. 测试 2

作为 lambda 表达式、经典 STL 匿名函数(使用 compose2bind1st 等)和传统手工编写的函数对象类编写的算术表达式的 CPU 时间。使用 BLL 术语,ab 是表达式中的绑定参数,而 x 是未绑定变量。所有变量类型均为 double。运行时间以任意单位表示。
表达式 lambda 表达式 经典 STL 表达式 手工编写的函数对象
ax 330 370 290
-ax 350 370 310
ax-(a+x) 470 500 420
(ax-(a+x))(a+x) 620 670 600
((ax) - (a+x))(bx - (b+x))(ax - (b+x))(bx - (a+x)) 1660 1660 1460


对库早期版本的其他一些性能测试描述在[Jär00]

关于编译

BLL 大量使用模板,并执行相同的模板的多次递归实例化。这(至少)有三个影响:

  • 虽然可以编写极其复杂的 lambda 表达式,但这可能不是一个好主意。编译这些表达式可能会在编译时消耗大量内存,并且编译速度很慢。

  • 即使是最简单的 lambda 表达式产生的 lambda 仿函数的类型也很晦涩。通常程序员根本不需要处理 lambda 仿函数的类型,但在 lambda 表达式出错的情况下,编译器通常会输出涉及的 lambda 仿函数的类型。这可能导致错误消息非常长且难以理解,特别是如果编译器输出整个模板实例化链。

  • C++ 标准建议模板嵌套深度为 17,以帮助检测无限递归。复杂的 lambda 模板很容易超出此限制。大多数编译器允许更多的嵌套模板,但通常需要通过命令行参数显式增加此限制。

可移植性

BLL 可与以下编译器配合使用,即这些编译器能够编译 BLL 附带的测试用例:

  • GCC 3.0.4
  • KCC 4.0f (EDG 2.43.1)
  • GCC 2.96(在一个测试用例中失败,exception_test.cpp 导致内部编译器错误。)

测试覆盖率

以下列表描述了包含的测试文件以及每个文件涵盖的功能:

  • bind_tests_simple.cpp:绑定不同元数和目标函数类型的表达式:函数指针、函数对象和成员函数。使用绑定表达式进行函数组合。

  • bind_tests_simple_function_references.cpp:重复bind_tests_simple.cpp中的所有测试,其中目标函数是函数指针,但使用函数引用代替。

  • bind_tests_advanced.cpp:包含嵌套绑定表达式、unlambdaprotectconst_parametersbreak_const 的测试。将 lambda 仿函数作为实际参数传递给其他 lambda 仿函数的测试、柯里化以及使用 sig 模板指定函数对象的返回类型的测试。

  • operator_tests_simple.cpp:使用为 lambda 表达式重载的所有运算符的测试,即一元和二元算术、按位、比较、逻辑、增量和减量、复合、赋值、下标、取地址、解引用和逗号运算符。测试移位运算符的流式性质,以及加减运算符的指针算术。

  • member_pointer_test.cpp:成员指针运算符足够复杂,需要单独的测试文件。

  • control_structures.cpp:循环和 if 构造的测试。

  • switch_construct.cpp:包含支持的所有元数的 switch 语句的测试,包括和不包括 default 情况。

  • exception_test.cpp:包含抛出异常和 try/catch 构造的测试,具有不同数量的 catch 块。

  • constructor_tests.cpp:包含对 constructordestructornew_ptrdelete_ptrnew_arraydelete_array 的测试。

  • cast_test.cpp:四种 cast 表达式的测试,以及 typeidsizeof

  • extending_return_type_traits.cpp:测试为用户定义类型扩展返回类型推导系统。包含几个用户定义的运算符以及返回类型推导模板的相应特化。

  • is_instance_of_test.cpp:包含用于内部使用的 traits 模板的测试,该模板可以检测给定类型是否是特定模板的实例。

  • bll_and_function.cpp:包含将 boost::function 与 lambda 仿函数一起使用的测试。


PrevUpHomeNext