Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReasonML performance against imperative vanilla JavaScript

Disclaimer: I am ReasonML beginner.

I have started playing with ReasonML lately and I have noticed a big difference in performance in contrast to vanilla JavaScript. Here's my examples against a simple puzzle solving function (puzzle taken from: https://adventofcode.com/2015/day/1)

ReasonML

let head = str =>
  switch (str) {
  | "" => ""
  | _ => String.sub(str, 0, 1)
  };

let tail = str =>
  switch (str) {
  | "" => ""
  | _ => String.sub(str, 1, String.length(str) - 1)
  };

let rec stringToList = str =>
  switch (str) {
  | "" => []
  | _ => [[head(str)], tail(str) |> stringToList] |> List.concat
  };

let rec findFloor = code =>
  switch (head(code)) {
  | "" => 0
  | "(" => 1 + findFloor(tail(code))
  | ")" => (-1) + findFloor(tail(code))
  | _ => 0
  };

let findFloorFold = code => {
  let calc = (a, b) =>
    switch (b) {
    | "" => a
    | "(" => a + 1
    | ")" => a - 1
    | _ => a
    };

  code |> stringToList |> List.fold_left(calc, 0);
};

JavaScript

const solve = code => {
  let level = 0;
  for (let i = 0, l = code.length; i < l; i++) {
    const el = code[i];
    if (el === "(") {
      ++level;
      continue;
    }
    if (el === ")") {
      --level;
      continue;
    }
  }
  return level;
};

Results

  • JavaScript: 5ms
  • ReasonML (recursive): 470ms
  • ReasonML (non-recursive): 7185ms

Is this expected or are my ReasonML function implementations too slow?

Thanks in advance for clarifications/suggestions.

Admittedly the second solution (non-rec) is slow due to the string to array conversion but this is because in ReasonML string is not represented by a list of chars.

like image 842
skay- Avatar asked Dec 18 '22 21:12

skay-


1 Answers

You can write the same imperative code in Reason as you did in JS, and actually have it be faster (by 30% on my computer):

let findFloorImperative = code => {
  let level = ref(0);
  for (i in 0 to String.length(code) - 1) {
    switch (code.[i]) {
    | '(' => level := level^ + 1
    | ')' => level := level^ - 1
    | _   => failwith("invalid code")
    }
  };
  level^
};

And this recursive solution is almost as fast:

let findFloorRecursiveNoList = code => {  
  let rec helper = (level, i) =>
    if (i < String.length(code)) {
      switch (code.[i]) {
      | '(' => helper(level + 1, i + 1)
      | ')' => helper(level - 1, i + 1)
      | _   => failwith("invalid code")
      }
    } else {
      level
    };

  helper(0, 0)
};

Benchmark results:

Reason, imperative:            19,246,043 ops/sec
Reason, recursive, no list:    18,113,602 ops/sec
JavaScript, imperative:        13,761,780 ops/sec
Reason, recursive, list:       481,426 ops/sec
Reason, folding, list:         239,761 ops/sec

Source: re:bench

like image 93
glennsl Avatar answered Dec 20 '22 10:12

glennsl