OCaml中的快速位数组
Fast bitarray in OCaml
另一个合成基准:埃拉托斯尼筛
C++
#include <vector>
#include <cmath>
void find_primes(int n, std::vector<int>& out)
{
std::vector<bool> is_prime(n + 1, true);
int last = sqrt(n);
for (int i = 2; i <= last; ++i)
{
if (is_prime[i])
{
for (int j = i * i; j <= n; j += i)
{
is_prime[j] = false;
}
}
}
for (unsigned i = 2; i < is_prime.size(); ++i)
{
if (is_prime[i])
{
out.push_back(i);
}
}
}
OCaml(使用Jane Street的Core和Res库)
open Core.Std
module Bits = Res.Bits
module Vect = Res.Array
let find_primes n =
let is_prime = Bits.make (n + 1) true in
let last = float n |! sqrt |! Float.iround_exn ~dir:`Zero in
for i = 2 to last do
if not (Bits.get is_prime i) then () else begin
let j = ref (i * i) in
while !j <= n; do
Bits.set is_prime !j false;
j := !j + i;
done;
end;
done;
let ar = Vect.empty () in
for i = 2 to n do
if Bits.get is_prime i then Vect.add_one ar i else ()
done;
ar
我很惊讶OCaml版本(本机)比C++慢13倍。我用Core_extended.Bitarray
代替了Res.Bits
,但速度慢了18倍。为什么这么慢?OCaml不提供比特操作的快速操作吗?有没有其他快速实现位阵列的方法?
需要明确的是:我来自C++世界,认为OCaml是编写性能关键代码的一种可能的替代方案。事实上,这样的结果让我有点害怕。
编辑:
分析结果
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
50.81 1.26 1.26 camlRes__pos_1113
9.72 1.50 0.24 camlRes__unsafe_get_1117
6.68 1.66 0.17 camlRes__unsafe_set_1122
6.28 1.82 0.16 camlNopres_impl__set_1054
6.07 1.97 0.15 camlNopres_impl__get_1051
5.47 2.10 0.14 47786824 0.00 0.00 caml_apply3
3.64 2.19 0.09 22106943 0.00 0.00 caml_apply2
2.43 2.25 0.06 817003 0.00 0.00 caml_oldify_one
2.02 2.30 0.05 1 50.00 265.14 camlPrimes__find_primes_64139
1.21 2.33 0.03 camlRes__unsafe_get_1041
...
在跳上复杂的数据结构之前,您是否先尝试使用简单的数据结构?
在我的机器上,以下代码只比C++版本慢4倍(请注意,我做了最小的更改,使用数组作为缓存,并使用列表来累积结果;您可以使用数组get/set语法糖):
let find_primes n =
let is_prime = Array.make (n + 1) true in
let last = int_of_float (sqrt (float n)) in
for i = 2 to last do
if not (Array.get is_prime i) then () else begin
let j = ref (i * i) in
while !j <= n; do
Array.set is_prime !j false;
j := !j + i;
done;
end;
done;
let ar = ref [] in
for i = 2 to n do
if Array.get is_prime i then ar := i :: !ar else ()
done;
ar
(慢4倍:计算10_000_0000个第一素数需要4秒,而不是1秒对于代码上的g++-O1或-O2)
意识到您的比特向量解决方案的效率可能来自经济内存布局,我更改了代码以使用字符串而不是数组:
let find_primes n =
let is_prime = String.make (n + 1) '0' in
let last = int_of_float (sqrt (float n)) in
for i = 2 to last do
if not (String.get is_prime i = '0') then () else begin
let j = ref (i * i) in
while !j <= n; do
String.set is_prime !j '1';
j := !j + i;
done;
end;
done;
let ar = ref [] in
for i = 2 to n do
if String.get is_prime i = '0' then ar := i :: !ar else ()
done;
ar
现在只需要2秒,这使它比你的C慢2倍++解决方案
看来Jeffrey Scofield是对的。这种可怕的性能下降是由于div
和mod
操作造成的。
我原型化了小型Bitarray
模块
module Bitarray = struct
type t = { len : int; buf : string }
let create len x =
let init = (if x = true then '255' else ' 00') in
let buf = String.make (len / 8 + 1) init in
{ len = len; buf = buf }
let get t i =
let ch = int_of_char (t.buf.[i lsr 3]) in
let mask = 1 lsl (i land 7) in
(ch land mask) <> 0
let set t i b =
let index = i lsr 3 in
let ch = int_of_char (t.buf.[index]) in
let mask = 1 lsl (i land 7) in
let new_ch = if b then (ch lor mask) else (ch land lnot mask) in
t.buf.[index] <- char_of_int new_ch
end
它使用字符串作为字节数组(每个字符8位)。最初,我使用x / 8
和x mod 8
进行比特提取。它比C++代码慢10倍。然后我用x lsr 3
和x land 7
替换了它们。现在,它只比C++慢4倍。
像这样比较微观基准测试通常并不有用,但基本结论可能是正确的。在这种情况下,OCaml处于明显的劣势。C++可以访问或多或少理想的表示(机器整数的向量)。OCaml可以生成向量,但不能直接获取机器整数。所以OCaml必须使用div和mod,而C++可以使用shift和mask。
我复制了这个测试(使用不同的位向量库),发现在OCaml中花费了相当长的时间来构建结果,这不是一个位数组。因此,测试可能无法准确测量您的想法。
更新
我尝试了一些快速测试,将32个布尔值打包成63位的int。这似乎确实让事情进展得更快,但只是一点点。这不是一个完美的测试,但它表明加斯切认为非2次幂效应很小是正确的。
请确保安装的Core包含.cmx文件(.cmxa还不够!),否则跨模块内联将无法工作。您的配置文件表明,某些调用可能没有内联,这将解释效率的巨大损失。
遗憾的是,许多OCaml项目使用的Oasis打包工具目前存在一个错误,无法安装.cmx文件。Core包也会受到此问题的影响,可能与您使用的包管理器(Opam、Godi)无关。
- Mongodb c++驱动程序:如何查询元素的数组
- 将数组的地址分配给变量并删除
- 从C++本机插件更新Vector3数组
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 数组索引的值没有增加
- 将对象数组的引用传递给函数
- 为char数组调整zlib-zpipe
- 2D数组来自文本输入,中间有空格
- std::向量与传递值的动态数组
- 在c++中用vector填充一个简单的动态数组
- 使用strcpy将char数组的元素复制到另一个数组
- 使用指针从C++中的数组中获取最大值
- C++使用整数的压缩数组初始化对象
- 告诉一个 const char 数组,除了编译时 C 样式的字符串外,它不以 '