我还处于学习OCaml的早期阶段,我很想知道在OCaml中从通用代码中提取最大性能的最佳方法是什么。
作为一个小型实验,我编写了两个多态函数:一个在C ++中,另一个在OCaml中,找到给定数组中最大的元素。
我观察到的是,在C ++中,你不会为这种抽象付出代价,OCaml中的惩罚是性能下降了一个数量级。另外,我快速编写的C ++解决方案比OCaml更通用,但我主要是因为我对语言缺乏经验。
我的问题如下:如何编写和使用OCaml中的多态函数而不付出我刚刚观察到的巨大性能损失?
我在这个特定问题上观察到的另一件事是我在OCaml中的功能解决方案比命令式解决方案慢,而C ++中的“功能”解决方案与模拟命令方法相比没有受到任何惩罚。
使用gcc-4.6.3使用g++ -std="c++0x" -O3 -o max_cpp max.cpp
编译C ++代码
和OCaml代码:ocamlopt -o max_ml max.ml
使用ocamlopt版本3.12.1。
两个生成的可执行文件都是32位,并在Ubuntu x64 11.04上运行
我提交了两个程序的代码。
C ++代码(当然不完全安全;))
#include <iostream>
#include <vector>
#include <numeric>
template <typename T> T max (T a, T b) {
return (a>b)? a : b;
}
template <typename I> typename I::value_type find_max (I begin, I end) {
auto max = *begin;
for (begin++;begin!=end;begin++)
if (*begin > max) max = *begin;
return max;
}
template <typename I> typename I::value_type find_max1(I begin, I end) {
return std::accumulate(begin, end, *begin, max< typename I::value_type> );
}
int main(int argc, char ** argv) {
const size_t nElem = atoi(argv[1]);
const size_t nIter = atoi(argv[2]);
std::vector<int> intA(nElem);
std::vector<double> dubA(nElem);
for (int i=0;i<nIter;i++) {
auto res1 = find_max(intA.begin(), intA.end());
auto res2 = find_max(dubA.begin(), dubA.end());
std::cout << "Max int: " << res1 << " " << "Max dub: " << res2 << std::endl;
}
}
OCaml代码
let max a b = if a > b then a else b
(* functional version *)
let find_max vector =
Array.fold_right max vector vector.(0)
(* imperative version *)
let find_max1 vector =
let length = Array.length vector in
let max = ref (vector.(0)) in
for i=1 to length-1 do
if vector.(i) > !max then max := vector.(i)
done; max
let nElems = int_of_string Sys.argv.(1)
let nIter = int_of_string Sys.argv.(2)
let _ = let intA = Array.make nElems 0 in
let dubA = Array.make nElems 0.0 in
for i=1 to nIter do
let res1 = find_max intA in
let res2 = find_max dubA in
print_int !res1;
print_newline ();
print_float !res2;
done
但是,如果我“重载”函数来区分整数和浮点数,那么程序的性能甚至超过我的C ++代码的50%!我想知道为什么。
let find_max_int (vector : int array) =
let length = Array.length vector in
let max = ref (vector.(0)) in
for i=1 to length-1 do
if vector.(i) > !max then max := vector.(i)
done; max
let find_max_float (vector : float array) =
let length = Array.length vector in
let max = ref (vector.(0)) in
for i=1 to length-1 do
if vector.(i) > !max then max := vector.(i)
done; max
时间表相当粗糙:
time ./max_cpp 1000000 100
和time ./max_ml 1000000 100
答案 0 :(得分:8)
在OCaml中,比较运算符(<)
是类型'a -> 'a -> bool
的通用函数(同样用于相等等)。这意味着它由运行时系统中的特殊原语实现,可以有效地对任何类型的数据结构进行临时比较。类型检查器能够优化整数和浮点数的单形比较到专门的比较操作,而不是在多态情况下在运行时进行类型检查。如果仅测试此操作,效率的十倍差异就不足为奇了。
请注意,为了获得最大的灵活性,您应该在find_max
中对比较操作进行抽象。然而,这将阻止单态化优化,因此内联版本仍将表现得更好。
我认为你的方法(做微观基准并希望学习有关真实程序性能的有趣事情)是有缺陷的。你遇到了一个高度特殊的OCaml性能行为,并从那里错误地得出结论,多态函数的性能“很差”。由于过早优化,这显然是一个糟糕的结论。写下您打算运行的计算的实际代表性示例,然后推断这个真实世界的代码片段的性能。你将从这种微基准测试和很多不相关的细节中学到很少的真理。
(然而,C ++代码专业化方法通常可以产生比OCaml编译技术更高效的代码,它只为所有类型编译函数的一个版本,但必须使相应的数据表示妥协;在OCaml中可以是一个与多态相关的开销。但是,只有在相当具体的情况下才能观察到这种情况,你通常可以在代码的小关键部分进行特定的内联传递。你获得的回报是更快的编译(没有重复)和实际上是可读的错误消息 - 就像在C ++中集成概念一样。)
编辑:事实上我说错误地说比较抽象会阻止类型导向优化。以下代码虽然仍然没有内联版本那么快,但仍然明显快于使用多态比较的版本:
let find_max1 comp vector =
let length = Array.length vector in
let max = ref (vector.(0)) in
for i=1 to length-1 do
if comp !max vector.(i) then max := vector.(i)
done;
!max
let find_max_int (vector : int array) = find_max1 (fun x y -> x < y) vector
let find_max_float (vector : float array) = find_max1 (fun x y -> x < y) vector
答案 1 :(得分:2)
实际上,您正在比较编译时专用函数与运行时调度。在ocaml端更适当的代码将使用仿函数,理论上可以将间接调用的数量减少到零,但我想它仍然会受到欠优化的影响。另一个问题是数据表示是统一的,在任何情况下都不是专用/内联用户类型。