写一个有效的字符串替换函数?

时间:2013-07-12 16:38:44

标签: performance ocaml

为了学习的目的,我试图想出一个字符串替换函数,这就是我现在所拥有的:

let string_replace where what replacement =
  let wlength = (String.length what - 1) in
  let length = (String.length where - 1) in
  let rec loop i shift =
    let [shift, i] =
      if wlength == shift then
        (String.blit replacement 0 where (i - wlength) (wlength + 1); [0, i])
      else if (String.get where i == String.get what shift) then
        [shift + 1, i]
      else [0, i - shift]
    in if i < length then loop (i + 1) shift
  in loop 0 0; where;;

现在,这里有一个测试:http://raid6.com.au/~onlyjob/posts/arena/所以我想知道我是如何相对做的。而且,在这里,我写了测试:

let test =
  let initial = "abcdefgh" ^ "efghefgh" in
  let initial_time = Sys.time() in
  let initial_length = String.length initial in
  let imax = (1024 / (String.length initial)) * 1024 * 4 in
  let rec loop s i =
    if i < imax then
      let ns = string_replace (s ^ initial) "efgh" "____" in
      let sl = initial_length * i in
      if (sl mod 1024) * 256 == 0 then
        Printf.printf "%f s | %d Kb |\n" (Sys.time() -. initial_time) (sl / 1024);
      loop ns (i + 1)
  in loop initial 0;;
test;;

我试图尽可能密切地关注JavaScript测试,以便能够比较结果(我在这里做了一些格式化,也让你无需搜索):

function test() {
    var str = "abcdefgh" + "efghefgh",
    imax = 1024 / str.length * 1024 * 4,
    time = new Date(), gstr = "", i = 0, lngth;

    console.log("exec.tm.sec\tstr.length");

    while (i++ < imax + 1000) {
        gstr += str;
        gstr = gstr.replace(/efgh/g, "____");
        lngth = str.length * i;
        if (lngth % 1024 * 256 === 0) {
            var curdate = new Date();
            console.log((curdate.getTime() - time.getTime()) / 1000 +
                        "sec\t\t" + lngth / 1024 + "kb");
        }
    }
}

以下是我得到的结果......

| JavaScript | OCaml        | String size |
|------------+--------------+-------------|
| 0.78 s     | 14.576784 s  | 256 Kb      |
| 3.266 s    | 58.468111 s  | 512 Kb      |
| 7.618 s    | 132.954788 s | 768 Kb      |
| 13.849 s   | 238.793697 s | 1024 Kb     |
| 46.243 s   | 379.175356 s | 1280 Kb     |
| 86.046 s   | 550.838260 s | 1536 Kb     |

所以,我的问题是:我的字符串替换功能有什么特别的错误吗?或者这种速度是预期的?我用ocamlopt编译了代码。


这是我的另一次尝试:

let string_index_of where what =
  let length = (String.length where - 1) in
  let pattern = (1 lsl (String.length what + 1)) - 1 in
  let rec loop i j mask =
    if i == length + 1 then (-1)
    else if mask == pattern then i - j
    else if (where.[i]) == (what.[j]) then
      loop (i + 1) (j + 1) ((mask lsl 1) lor 1)
    else loop (i + 1 - j) 0 1
  in loop 0 0 1;;

let test =
  let initial = "abcdefgh" ^ "efghefgh" in
  let replacement = "____" in
  let replacement_length = String.length replacement in
  let initial_time = Sys.time() in
  let initial_length = String.length initial in
  let imax = (1024 / (String.length initial)) * 1024 * 4 in
  let rec loop s i =
    if i < imax then
      let concatenated = s ^ "efgh" in
      let sl = initial_length * i in
      let rec replace_loop st =
        let index = string_index_of st "efgh" in
        if index >= 0 then
          ((String.blit replacement 0 st index replacement_length);
           replace_loop st)
      in replace_loop concatenated;
      if sl mod (1024 * 256) == 0 then
        Printf.printf "%f s | %d Kb |\n" (Sys.time() -. initial_time) (sl / 1024);
      loop concatenated (i + 1)
  in loop initial 0;;

test;;

我还制作了一个更大的表格(包括一些其他语言用于比较):

| JavaScript V8 | ocamlopt     | ocamlopt bitup | SBCL      | C gcc 4 -O3 | String size |
|---------------+--------------+----------------+-----------+-------------+-------------|
| 0.78 s        | 14.576784 s  | 4.615298 s     | 17.463 s  | 1 s         | 256 Kb      |
| 3.266 s       | 58.468111 s  | 18.474191 s    | 68.484 s  | 4 s         | 512 Kb      |
| 7.618 s       | 132.954788 s | 41.673664 s    | 153.99 s  | 10 s        | 768 Kb      |
| 13.849 s      | 238.793697 s | 74.652651 s    | 275.204 s | 17 s        | 1024 Kb     |
| 46.243 s      | 379.175356 s | 116.517287 s   | 431.768 s | 27 s        | 1280 Kb     |
| 86.046 s      | 550.838260 s | 166.662663 s   | 624.559 s | 38 s        | 1536 Kb     |

我仍希望能做得更好。

2 个答案:

答案 0 :(得分:3)

你的字符串搜索复杂性很差,你应该尝试一种更有效的字符串搜索算法(here is list,我推荐KMP

您似乎是OCaml的新手,所以这里与您习惯的语言存在一些差异:

  • 测试结构相等性的运算符是=而不是==,它测试物理相等性(对于int没有区别,但它会给你带来一些其他类型的麻烦)。
  • 使用()创建了一个元组,而不是[]用于列表,因此[shift, i]是一个包含一个元素的列表这是一对。而且我猜Ocaml抱怨这种非执行模式匹配。
  • String.get s i可以写成s.[i]

答案 1 :(得分:2)

我发现很难匹配Javascript性能,大多数引擎(js)都使用绳索作为字符串的内部数据结构(https://en.wikipedia.org/wiki/Rope_%28data_structure%29)。

因此,每次执行子字符串,concat或任何应该创建新字符串的函数(或在内部调用)时,ocaml构造一个新字符串,而javascript使用绳索引用前一个字符串。

如果您想查看它,请在每次使用创建时检查:(https://github.com/ocaml/ocaml/blob/trunk/stdlib/string.ml)。我想向您展示V8如何优化它重复使用字符串块,但无法轻松找到它,所以将它作为练习给你。

希望它有助于理解差异,以及为什么javscript在不需要处理太多绳索的情况下仍然比C表现更好。