Pelemay 0.0.6 で String.replace に対応しました。

Pelemay 0.0.6 をリリースしました。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Pelemay 0.0.6 has been released!<br>New feature: support String.replace<br>Much faster than original Elixir! <a href="https://t.co/5MJxGIce71">https://t.co/5MJxGIce71</a></p>&mdash; Susumu Yamazaki (@zacky1972) <a href="https://twitter.com/zacky1972/status/1233343548378025984?ref_src=twsrc%5Etfw">February 28, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

今回の目玉は，String.replace をサポートしたことです。

```elixir
defmodule M do
  require Pelemay
  import Pelemay

  defpelemay do
    def string_replace(subject) do
      String.replace(&1, "Fizz", "Buzz")
    end

    def enum_map_string_replace(list) do
      list
      |> Enum.map(& String.replace(&1, "Fizz", "Buzz"))
    end
  end
end
```

このように書くと，Elixir で普通に実行したときに比べて，4倍程度高速化されます。
手元で雑に計測した感じです: ソースコードはこちら https://github.com/zeam-vm/pelemay_sample

```
## StringReplaceBench
benchmark name          iterations   average time 
Pelemay String.replace     1000000   1.20 µs/op
Enum String.replace         500000   3.92 µs/op
Flow String.replace           5000   678.27 µs/op
```

Flow がここまで遅いのは，Enum.sort を挟まざるを得ないからだと思います。

String.replace は #pragma を随所に埋め込んで SIMD 命令を最大限活用するように実装しました。アルゴリズムは現状では，普通の線形探索です。今後，実証実験を重ねてBM法などの採用も検討していきます。

中核となるソースコードはこんな感じです。

```c
int string_replace_binary(ErlNifBinary subject, ErlNifBinary pattern, ErlNifBinary replacement, bool global, ErlNifBinary *object)
{
  if(__builtin_expect(!enif_alloc_binary(subject.size, object), false)) {
    return false;
  }
  unsigned subject_i = 0, object_i = 0;
#pragma clang loop vectorize_width(loop_vectorize_width)
  while(subject_i < subject.size) {
    while(subject_i < subject.size && subject.data[subject_i] != pattern.data[0]) {
      object->data[object_i++] = subject.data[subject_i++];
    }
    if(__builtin_expect(subject_i >= subject.size, false)) {
      return true;
    }
    unsigned pattern_i = 0;
    while(subject_i + pattern_i < subject.size
      && pattern_i < pattern.size 
      && subject.data[subject_i + pattern_i] == pattern.data[pattern_i]) {
      pattern_i++;
    }
    if(__builtin_expect(pattern_i == pattern.size, true)) {
      if(__builtin_expect(pattern.size != replacement.size, false)) {
        if(__builtin_expect(!enif_realloc_binary(object, object->size - pattern.size + replacement.size), false)) {
          return false;
        }
      }
      subject_i += pattern.size;
      for(unsigned replacement_i = 0; replacement_i < replacement.size; replacement_i++) {
        object->data[object_i++] = replacement.data[replacement_i];
      }
      if(__builtin_expect(!global, false)) {
        while(subject_i < subject.size) {
          object->data[object_i++] = subject.data[subject_i++];
        }
        return true;
      }
    } else if(__builtin_expect(subject_i < subject.size, true)) {
      object->data[object_i++] = subject.data[subject_i++];
    } else {
      return true;
    }
  }
  return true;
}

ERL_NIF_TERM string_replace(ErlNifEnv *env, ERL_NIF_TERM subject, ERL_NIF_TERM pattern, ERL_NIF_TERM replacement, bool global)
{
  ErlNifBinary subject_binary;
  if(__builtin_expect(!enif_inspect_binary(env, subject, &subject_binary), false)) {
    return enif_make_badarg(env);
  }
  ErlNifBinary pattern_binary;
  if(__builtin_expect(!enif_inspect_binary(env, pattern, &pattern_binary), false)) {
    return enif_make_badarg(env);
  }
  ErlNifBinary replacement_binary;
  if(__builtin_expect(!enif_inspect_binary(env, replacement, &replacement_binary), false)) {
    return enif_make_badarg(env);
  }
  ErlNifBinary object_binary;
  if(__builtin_expect(!string_replace_binary(subject_binary, pattern_binary, replacement_binary, global, &object_binary), false)) {
    return enif_make_badarg(env);
  }
  return enif_make_binary(env, &object_binary);
}
```