I have written a code using genetic algorithm in OCaml and Rust, the performance measuring results show that the code in OCaml is 2.5 times slower than Rust, could someone give me some tips for optimizing my OCaml code?
Here is the code:
OCaml version 4.14.0
build command: ocamlopt -o gen.exe -O3 -unsafe gen.ml
execute command: ./gen.exe
let _MIN_X = 0.
let _MAX_X = 10.
(* [min, max] *)
let randInt ~min ~max =
let ratio = Random.float 1. in
let value =
(((max |> float_of_int) -. (min |> float_of_int)) *. ratio)
+. (min |> float_of_int)
in
value |> floor |> int_of_float
module Chrome = struct
type t = { mutable x : float; mutable fitness : float }
let init () = { x = Random.float (_MAX_X -. _MIN_X) +. _MIN_X; fitness = 0. }
let calculate c = c.fitness <- c.x *. c.x *. c.x
let mutate () =
let c = init () in
let () = c |> calculate in
c
let cross c1 c2 =
let median = (c1.x +. c2.x) /. 2. in
let newC1 = { x = 0.; fitness = 0. } in
let newC2 = { x = 0.; fitness = 0. } in
let () = newC1.x <- Random.float (median -. _MIN_X) +. _MIN_X in
let () = newC1 |> calculate in
let () = newC2.x <- Random.float (_MAX_X -. median) +. median in
let () = newC2 |> calculate in
(newC1, newC2)
end
module Gen = struct
type t = {
mutable chromes : Chrome.t array;
genSize : int;
genCount : int;
mutable bestChrome : Chrome.t option;
mutRatio : float;
crossRatio : float;
}
let init g =
g.chromes <-
Array.init g.genSize (fun _i ->
let c = Chrome.init () in
let () = c |> Chrome.calculate in
c)
let mutate g =
for i = 0 to g.genSize - 1 do
let ratio = Random.float 1. in
if ratio <= g.mutRatio then
let c = Chrome.mutate () in
(g.chromes |> Array.set) i c
done
let cross g =
for _i = 0 to g.genSize - 1 do
let ratio = Random.float 1. in
if ratio <= g.crossRatio then
let i1, i2 =
( randInt ~min:0 ~max:(g.genSize - 1),
randInt ~min:0 ~max:(g.genSize - 1) )
in
let c1, c2 = Chrome.cross g.chromes.(i1) g.chromes.(i2) in
let () = g.chromes.(i1) <- c1 in
g.chromes.(i2) <- c2
done
let updateGen g =
let () =
Array.sort
(fun (c1 : Chrome.t) (c2 : Chrome.t) ->
if c1.fitness < c2.fitness then -1
else if c1.fitness == c2.fitness then 0
else 1)
g.chromes
in
g.bestChrome <- Some g.chromes.(0)
let start g =
for _i = 0 to g.genCount - 1 do
let () = g |> mutate in
let () = g |> cross in
g |> updateGen
done
end
let start () =
let gen : Gen.t =
{
chromes = ([||] : Chrome.t array);
genSize = 10000;
genCount = 1000;
bestChrome = None;
mutRatio = 0.15;
crossRatio = 0.65;
}
in
let () = gen |> Gen.init in
let () = gen |> Gen.start in
match gen.bestChrome with
| None -> ()
| Some c ->
print_endline
("best value is "
^ (c.fitness |> string_of_float)
^ " and best x is " ^ (c.x |> string_of_float))
let main () =
let testCount = 10 in
let timeStart = Sys.time () in
let () =
for _i = 0 to testCount - 1 do
start ()
done
in
let timeEnd = Sys.time () in
print_endline
("mean time is "
^ (let timeUse =
(timeEnd -. timeStart) *. 1000. /. (testCount |> float_of_int)
in
timeUse |> Float.round |> int_of_float |> string_of_int)
^ "ms")
let () = main ()
running result
best value is 3.96616750408e-10 and best x is 0.00073472308226
best value is 3.2272451753e-11 and best x is 0.000318378686536
best value is 1.23686587687e-12 and best x is 0.000107343117167
best value is 3.68554344258e-10 and best x is 0.000716969220129
best value is 7.59396016819e-10 and best x is 0.000912338715952
best value is 1.88381205775e-10 and best x is 0.000573252368078
best value is 4.40679331038e-11 and best x is 0.000353216427623
best value is 3.53500191601e-09 and best x is 0.00152333896594
best value is 3.03752916138e-13 and best x is 6.72212863153e-05
best value is 1.52011916399e-10 and best x is 0.000533694275756
mean time is 2926ms
rust version v1.67.0
execute command: cargo run --release
dependencies: rand = â0.8.5â
use std::time;
use rand;
trait Fitness {
fn calculate(&mut self);
}
trait Chrome {
fn init() -> Box<Self>;
fn mutate() -> Box<Self>;
fn cross(c1: &Self, c2: &Self) -> (Box<Self>, Box<Self>);
}
trait Gen {
fn init(&mut self);
fn start(&mut self);
fn mutate(&mut self);
fn cross(&mut self);
fn update_gen(&mut self);
}
struct XChrome {
pub x: f32,
pub fitness: f32,
}
const MIN_X: f32 = 0.;
const MAX_X: f32 = 10.;
impl Chrome for XChrome {
fn init() -> Box<Self> {
Box::new(XChrome {
x: rand::random::<f32>() * (MAX_X - MIN_X),
fitness: 0.,
})
}
fn mutate() -> Box<Self> {
let mut c = XChrome::init();
c.calculate();
c
}
fn cross(c1: &Self, c2: &Self) -> (Box<Self>, Box<Self>) {
let median = (c1.x + c2.x) / 2.;
let mut new_c1 = XChrome {
x: MIN_X,
fitness: 0.,
};
let mut new_c2 = XChrome {
x: MIN_X,
fitness: 0.,
};
new_c1.x = rand::random::<f32>() * (median - MIN_X) + MIN_X;
new_c1.calculate();
new_c2.x = rand::random::<f32>() * (MAX_X - median) + median;
new_c2.calculate();
(Box::new(new_c1), Box::new(new_c2))
}
}
impl Fitness for XChrome {
fn calculate(&mut self) {
self.fitness = self.x * self.x * self.x;
}
}
struct XGen {
pub chromes: Box<Vec<Box<XChrome>>>,
pub gen_size: usize,
pub gen_count: usize,
pub best_chrome: Option<Box<XChrome>>,
pub mut_ratio: f32,
pub cross_ratio: f32,
}
fn rand_int(min: usize, max: usize) -> usize {
let ratio = rand::random::<f32>();
let value = (max as f32 - min as f32) * ratio;
value.floor() as usize
}
impl Gen for XGen {
fn init(&mut self) {
self.chromes = Box::new(Vec::with_capacity(self.gen_size));
for _i in 0..self.gen_size {
let mut chrome = XChrome::init();
chrome.calculate();
self.chromes.push(chrome);
}
}
fn start(&mut self) {
for _i in 0..self.gen_count {
self.mutate();
self.cross();
self.update_gen();
}
}
fn mutate(&mut self) {
for i in 0..self.gen_size {
let ratio = rand::random::<f32>();
if ratio <= self.mut_ratio {
let chrome = XChrome::mutate();
self.chromes[i] = chrome;
}
}
}
fn cross(&mut self) {
for _i in 0..self.gen_size {
let ratio = rand::random::<f32>();
if ratio <= self.cross_ratio {
let (i1, i2) = (rand_int(0, self.gen_size), rand_int(0, self.gen_size));
let (c1, c2) = unsafe {
XChrome::cross(
&self.chromes.get_unchecked(i1),
&self.chromes.get_unchecked(i2),
)
};
// let (c1, c2) = XChrome::cross(&self.chromes[i1], &self.chromes[i2]);
self.chromes[i1] = c1;
self.chromes[i2] = c2;
}
}
}
fn update_gen(&mut self) {
self.chromes
.sort_by(|c1, c2| c1.fitness.partial_cmp(&c2.fitness).unwrap());
self.best_chrome = Some(Box::new(XChrome {
x: self.chromes[0].x,
fitness: self.chromes[0].fitness,
}));
}
}
fn start() {
let mut gen = XGen {
chromes: Box::new(vec![]),
gen_size: 10000,
gen_count: 1000,
best_chrome: None,
mut_ratio: 0.15,
cross_ratio: 0.65,
};
gen.init();
gen.start();
match gen.best_chrome {
None => (),
Some(chrome) => println!(
"best value is {} and best x is {}",
chrome.fitness, chrome.x
),
}
}
fn main() {
let test_count = 10;
let time_start = time::Instant::now();
for _i in 0..test_count {
start()
}
let time_end = time::Instant::now();
let ms = time_end.duration_since(time_start).as_millis();
println!("mean time is {}ms", ms / test_count);
}
running result
best value is 0.00000000000045630557 and best x is 0.00007698721
best value is 0.0000000001450359 and best x is 0.00052540214
best value is 0.000000000076834546 and best x is 0.00042512716
best value is 0.00000000000028120017 and best x is 0.000065514665
best value is 0.0000000028171288 and best x is 0.001412328
best value is 0.000000000003870766 and best x is 0.0001570118
best value is 0.00000000042730455 and best x is 0.0007532038
best value is 0.000000000035633937 and best x is 0.00032906974
best value is 0.000000000028005924 and best x is 0.0003036803
best value is 0.0000000043604453 and best x is 0.0016337174
mean time is 889ms