Elixir 高性能な數の數え方（counters）

[Elixir Patterns book & Livebooks]という本を読んでElixirで效率よく數を數える方法について學んだので練習しながらメモします。

[Elixir Patterns book & Livebooks]: https://elixirpatterns.dev

ちなみにElixirとあわせて舊字體の漢字の練習もしているので、萬一見にくい場合は以下のウエブアプリで適宜お好みの漢字に變換してみてください。

https://www.benricho.org/moji_conv/13.html

## Erlangのcountersモジュール

- 效率よく數値の配列への加算および減算する目的專用に最適化されたモジュール
- 不可分操作で實行できるため、多數の同時アクセスが發生した場合でもデータの不整合が發生しない
- Elixirでは[Logger.Backends]で使用されている

[Logger.Backends]: https://github.com/elixir-lang/elixir/blob/b20ad8a1514008595bebe95ed9f8dfc67380a779/lib/logger/lib/logger/backends/internal.ex

詳しくは原典をご参照ください。

https://www.erlang.org/doc/man/counters.html

## カウンター數を決定する

- カウンターの數を`:counters`インスタンス生成時に指定することが必要
- インデックスは１から始まる數字 

```elixir
# how many numbers to manage
counter_size = 2

# one-based numbering 
autorace_index = 1
toukon_index = 2
```

## `:counters`インスタンスを生成

* options
  * `:atomics` (default)
  * `write_concurrency`

```elixir
my_counter = :counters.new(counter_size, [:write_concurrency])

get_state = fn ->
  %{
    autorace: :counters.get(my_counter, autorace_index),
    toukon: :counters.get(my_counter, toukon_index)
  }
end

get_state.()
# %{autorace: 0, toukon: 0}
```

## 加算

`:counters.add/3`

```elixir
:counters.add(my_counter, autorace_index, 1)
:counters.add(my_counter, toukon_index, 123)
get_state.(:counters.sub)
# %{autorace: 1, toukon: 123}
```

## 減算

`:counters.sub/3`

```elixir
:counters.sub(my_counter, autorace_index, 124)
:counters.sub(my_counter, toukon_index, 24)
get_state.()
# %{autorace: -123, toukon: 99}
```

## 上書き

`:counters.put/3`

```elixir
:counters.put(my_counter, autorace_index, 0)
:counters.put(my_counter, toukon_index, 0)
get_state.()
# %{autorace: 0, toukon: 0}
```

## 大量の數を數える

試しに１０萬個のデータを受け取って數を數えてみます。

```elixir
do_count = fn _ ->
  case Enum.random([:toukon, :autorace]) do
    :toukon -> :counters.add(my_counter, toukon_index, 1)
    :autorace -> :counters.add(my_counter, autorace_index, 1)
  end
end

1..100_000
|> Task.async_stream(&do_count.(&1), max_concurrency: 500)
|> Stream.run()

get_state.()
# %{autorace: 50099, toukon: 49901}
```

:tada: :tada: :tada:

何も計測していませんが、ドキュメントを信じているのでなんとなく高性能にみえます。:sweat_smile:

## 高性能のカラクリ

Elixirでは通常データは更新の度にコピーされるのですが、性能を重視した關數プログラミングっぽくない幾つかの逃げ道も用意されているようです。Erlangのcountersモジュールはその逃げ道の一つです。

他にもいくつか擧げられます。例えば、ラベル付き有向グラフを実装した[digraph]は[Mix.Deps.Converger]と[Mix.Tasks.Xref]で活用されています。

[Mix.Deps.Converger]: https://github.com/elixir-lang/elixir/blob/b395381b5ff66e57adcf3e19e0b8afcf9067b617/lib/mix/lib/mix/dep/converger.ex#L10
[Mix.Tasks.Xref]: https://github.com/elixir-lang/elixir/blob/b20ad8a1514008595bebe95ed9f8dfc67380a779/lib/mix/lib/mix/tasks/xref.ex

[digraph]: https://www.erlang.org/doc/man/digraph.html

讀み込み速度が最適化された[persistent_term]は書き込みの頻度が少ない設定關係で利用されています。高速で値を取得できます。[Mix.State]では[persistent_term]と汎用インメモリデータベース[ets]がうまく使い分けられています。

[Mix.State]: https://github.com/elixir-lang/elixir/blob/main/lib/mix/lib/mix/state.ex

[ets]: https://www.erlang.org/doc/man/ets.html
[persistent_term]: https://www.erlang.org/doc/man/persistent_term.html

https://github.com/elixir-lang/elixir/blob/main/lib/mix/lib/mix/state.ex

https://qiita.com/mnishiguchi/items/16f520f083c2487f93e1

https://qiita.com/mnishiguchi/items/ef710435778565b56287

## Elixirコミュニティ

本記事は以下のモクモク會での成果です。みなさんから刺激と元氣をいただき、ありがとうございました。

https://youtu.be/c0LP23SM7BU

https://okazakirin-beam.connpass.com/

https://autoracex.connpass.com

もしご興味のある方はお氣輕にご參加ください。

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

https://qiita.com/torifukukaiou/items/57a40119c9eefd056cae

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf

https://qiita.com/torifukukaiou/items/4481f7884a20ab4b1bea

https://note.com/awesomey/n/n4d8c355bc8f7

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/dc1ddba7-ab4c-5e20-1331-143c842be143.jpeg)

生產者の皆樣いつも美味しい食材をありがとうございます。おかげで健康に元氣にもくもく取り組むことができます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/43522317-8a9d-e936-f69e-8a7fc3048964.jpeg)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/3ba8f28d-0094-2178-56e4-13d383e3b3b1.jpeg)