ElixirでIoT#4.4：Nervesの動作周波数を制御する

どうもこんにちわ．
この記事は[ Seeed UG Advent Calendar 2020 ](https://qiita.com/advent-calendar/2020/seeed_ug)の20日目の記事です．

# はじめに

[NervesJP ](https://nerves-jp.connpass.com)というコミュニティをやっとります．
れっつじょいなす！！

[![NCJP_Logo3_V2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/5ef22bb9-f357-778c-1bff-b018cce54948.png)](https://nerves-jp.connpass.com)

昨年の[Advent Calendar](https://qiita.com/advent-calendar/2019/seeed_ug)では，23日目に[『ElixirでIoT#4.2：BeagleBone GreenでNerves/Elixirを動かす』](https://qiita.com/takasehideki/items/e7cc1a2d0a4a7140c1cf)という記事をお届けしました．

ところでNervesってなんやねん！？？という方は，:arrow_up:の記事に加えて，このあたりの情報をご参照ください．

- [ElixirでIoT#2.1：Nervesって何者？ラズパイでLチカできんの！？](https://qiita.com/takasehideki/items/94820516ec95b85bae32)
- SlideShare：[ElixirでIoT！？ナウでヤングでcoolなNervesフレームワーク](https://www2.slideshare.net/takasehideki/elixiriotcoolnerves-236780506)
- YouTube動画：[ALGYAN主催！【オンライン】Elixir/Nerves入門！堅牢なIoT Edgeデバイスプログラミングをお手軽に](https://www.youtube.com/watch?v=UpJ4VewZdRY)

今年のAdvent Calendarでは，レペゼンNervesJPのAdvent Calendar芸人！？な @torifukukaiou さんより，こんな記事をお届けしております．

- 13日目：[『Grove Base HAT for RasPiは真っ直ぐグイっとさす』](https://qiita.com/torifukukaiou/items/81f2a75bee0f919224ad)
- 14日目：[『Grove - Buzzer をNervesで鳴らす』](https://qiita.com/torifukukaiou/items/19fecf95b9313b8a2b8a)
- 18日目：[『GrovePi+ Starter Kit for Raspberry Pi A+,B,B+&2,3,4 (CE certified) 〜Nervesならできるもん！〜』](https://qiita.com/torifukukaiou/items/0b1faacfdaa1cf217bec)

<font color="purple">$\huge{Nervesならできるもん！}$</font>
って言いたいだけですのやつね〜 30回くらい笑わせてもらいました^^;

ということでこの記事では，@torifukukaiou さんを見習って，日頃よりNervesJPでお世話になっております[Seeed K.K.](https://www.seeed.co.jp/)さんへの**感謝の恩返し！！**ということで，[BeagleBone Green](https://jp.seeedstudio.com/Seeed-Studio-BeagleBone-Green.html)で遊べるヒトネタをご紹介したいと思います．

**[PR] Seeed K.K.さん，太っ腹！！ [広告]**
※プレゼント枠は瞬殺でした,,, でもまだ見学枠でぜひ！！

[【オンライン】豪華プレゼント付！Elixir/Nerves(ナーブス)体験ハンズオン！](https://algyan.connpass.com/event/197306/)
[![ALGYAN](https://connpass-tokyo.s3.amazonaws.com/thumbs/60/61/6061fb754047df79ede0fb9d678e16eb.png)](https://algyan.connpass.com/event/197306/)

# Nerves開発環境の準備方法

おぉっなんかNerves楽しそうだ！すぐに試してみたい！！という方向けに，VS Code dev-containerを使ったお手軽な開発環境を用意しています．ぜひどうぞです^^;

- [『ElixirでIoT#4.1.2：[使い方篇] Docker(とVS Code)だけ！でNerves開発環境を整備する』](https://qiita.com/takasehideki/items/27005ba9c0d9eb693ea9)

いやっガッツリNervesやっていくぜ！という方は，コチラをどうぞです．

- Mac,Linux向け：[『ElixirでIoT#4.1：Nerves開発環境の準備（2020年11月版）』](https://qiita.com/takasehideki/items/88dda57758051d45fcf9)
- Windows(WSL 2)向け：[『ElixirでIoT#4.1.1：WSL 2でNerves開発環境を整備する』](https://qiita.com/takasehideki/items/b8ea8b3455c70398178a)

# 動作周波数の制御？？

いわゆるDVFS（Dynamic Voltage and Frequency Scaling）というやつです．
Linuxが普通に動くような最近のSoCでは，そのプロセッサの動作周波数を何段階かに変更することができます．アプリの処理が忙しいときは高い周波数で速く動いて，ヒマなときは周波数を低くしてゆっくり動かすことができます．

なにが嬉しいかというと，消費エネルギーの節約に繋がります．
動作周波数と供給電圧は比例関係にあり，電圧を下げることによって消費電力を削減することができます．周波数を低くしたらアプリの実行時間は大きくなりますが，消費エネルギー的には小さく抑えられることが多いです（正確にはもうちょいありますが，ザックリ説明ということで^^;

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/6adfce31-1825-acf2-76cb-39b42f5b3b54.png)

Linux v3.4以降では `cpufreq` というモジュールが標準で搭載されています．
Nervesでは（というかLinux）では，このモジュールを使ってプロセッサの周波数を変更することができます．

BeagleBone Green対応のNerves（本記事執筆時点の最新版 `nerves_system_bbb v2.8.2`）では，組み込みLinuxである[Buildroot](https://buildroot.org/) v2020.08.2が動作しています．Linux kernelのバージョンは5.4です．
要するに，NervesでもDVFSできるぜっ！てことです．

# cpufreqの構成

`cpufreq` というモジュールは `/sys/devices/system/cpu/cpufreq/policy0/` にあります．Nervesの動作するBeagleBone Greenにアクセスしして，その構成を見てみましょう．

```elixir
$ ssh nerves.local 
Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
Toolshed imported. Run h(Toolshed) for more info.
RingLogger is collecting log messages from Elixir and Linux. To see the
messages, either attach the current IEx session to the logger:

  RingLogger.attach

or print the next messages in the log:

  RingLogger.next

iex(1)> cpufreq_dir = "/sys/devices/system/cpu/cpufreq/policy0/" 
"/sys/devices/system/cpu/cpufreq/policy0/"
iex(2)> ls cpufreq_dir                                           
affected_cpus                     cpuinfo_cur_freq                  cpuinfo_max_freq                  
cpuinfo_min_freq                  cpuinfo_transition_latency        related_cpus                      
scaling_available_frequencies     scaling_available_governors       scaling_cur_freq                  
scaling_driver                    scaling_governor                  scaling_max_freq                  
scaling_min_freq                  scaling_setspeed                  stats                             
```

いろんなファイルがあるので，要点な感じだけ．

- cpuinfo_cur_freq: 現在の動作周波数
- cpuinfo_max_freq, _min_freq: 設定可能な最大／最小の周波数
- scaling_available_frequencies: 設定可能な周波数の一覧
- scaling_available_governors: 設定可能なガバナの一覧
- scaling_max_freq, _min_freq: ガバナで調節される周波数の上限／下限値（設定値）
- scaling_setspeed: 手動で調節する場合の周波数の設定値

## 設定可能な周波数

BeagleBone Greenで設定可能な周波数の一覧を見てみましょう．

```elixir
iex(3)> cat(cpufreq_dir <> "scaling_available_frequencies")     
300000 600000 720000 800000 1000000 

```

単位はKHzです．つまり300MHzから1.0GHzまでの5段階の周波数が設定できるわけです．

## 設定可能なガバナ

ガバナ（governor）というのは，`cpufreq` で提供される標準で用意されている周波数制御のポリシーです．BeagleBone GreenのNervesではこんな感じになっています．

```elixir
iex(4)> cat(cpufreq_dir <> "scaling_available_governors") 
ondemand userspace powersave conservative performance 

```

5種類のガバナがあります．ざっくりこんな感じです．

| governor | 意味 |
| :--- | :--- |
| performance | 常に最大の動作周波数に固定 |
| powersave | 常に最小の動作周波数に固定 |
| userspace | ユーザの指定する周波数で動作 |
| ondemand | アプリ動作負荷に応じて自動調節 | 
| conservative | アプリ動作負荷に応じて，適応的に自動調節 |

`ondemand` と `conservative` の違いは，前者はCPU使用率に応じてすぐにその対応する周波数に切り替える（例えば300MHzから一気に800MHzに変更したりする）のに対して，後者はゆっくり適応的に切り替えます（例えば300MHzから，600MHzと720MHzを経由して，それでも負荷が高ければ800MHzにする）

ちなみにデフォルト（電源リセット後）の設定は `conservative` です．

```elixir
iex(5)> cat(cpufreq_dir <> "scaling_governor")           
conservative

```

# 設定方法

Linuxカーネルに設定を任せる場合は，`scaling_governor` に設定したいガバナを書き込むだけです．
最大にする `performance` と最小にする `powersave` にして，周波数を見てみましょう．

```elixir
iex(6)> File.write(cpufreq_dir <> "scaling_governor", "performance")
:ok
iex(7)> cat(cpufreq_dir <> "cpuinfo_cur_freq")
1000000

iex(8)> File.write(cpufreq_dir <> "scaling_governor", "powersave")  
:ok
iex(9)> cat(cpufreq_dir <> "cpuinfo_cur_freq")                    
300000

```

まぁカーネルに任せといたほうが良いんですが，どうしても自分で変えたい！という場合は，ガバナを `userspace` に設定して， `scaling_setspeed`に設定可能な値を書き込みます．

```elixir
iex(10)> File.write(cpufreq_dir <> "scaling_governor", "userspace")
:ok
iex(11)> File.write(cpufreq_dir <> "scaling_setspeed", "720000")   
:ok
iex(12)> cat(cpufreq_dir <> "cpuinfo_cur_freq")                      
720000

```

# 効果のほどは？？

それでは周波数制御の効果を見てみましょう．

[こんな感じ](https://github.com/takasehideki/nerves_bbg/commit/9c52a14e36c668093233f821fc3465fceed0b413)で，フィボナッチ数を求める関数をベタに実装しました．

```elixir
defmodule NervesBbg do
  @moduledoc """
  Documentation for NervesBbg.
  """

  @doc """
  Calculate Fibonacci number by recursive call.

  ## Examples

      iex> NervesBbg.fib(20)
      6765

  """
  def fib(0), do: 0
  def fib(1), do: 1
  def fib(n), do: fib(n-1) + fib(n-2)
end
```

周波数を下から順に切り替えて，`:timer.tc`で実行時間を測定してみました．

```elixir
iex(13)> File.write(cpufreq_dir <> "scaling_setspeed", "300000")
:ok
iex(14)> :timer.tc(NervesBbg, :fib, [20])
{7184, 6765}
iex(15)> File.write(cpufreq_dir <> "scaling_setspeed", "600000")
:ok
iex(16)> :timer.tc(NervesBbg, :fib, [20])                       
{3570, 6765}
iex(17)> File.write(cpufreq_dir <> "scaling_setspeed", "720000") 
:ok
iex(18)> :timer.tc(NervesBbg, :fib, [20])                       
{3032, 6765}
iex(19)> File.write(cpufreq_dir <> "scaling_setspeed", "800000")
:ok
iex(20)> :timer.tc(NervesBbg, :fib, [20])                       
{2645, 6765}
iex(21)> File.write(cpufreq_dir <> "scaling_setspeed", "1000000")
:ok
iex(22)> :timer.tc(NervesBbg, :fib, [20])                        
{2127, 6765}
```

おぉっ，なんだか速くなっているようです．
表にするとこんな感じです．

| 周波数[KHz] | 実行時間[us] |
| ---: | :---: |
| 300000 | 7184 |
| 600000 | 3570 |
| 720000 | 3032 |
| 800000 | 2645 |
| 1000000 | 2127 |

# おわりに

ということでこの記事では，Nervesで動作周波数を制御する方法を紹介しました．
ていうか実はNervesどうこうじゃなく，Linuxが"いごく"んならなんでもおけです．

それといろいろ設定できますが，ガバナは割りと賢いので，デフォルトの `conservative` にしとけば性能と電力のバランスは良いです．
ただし周波数が切り替わると実行時間が変動しますし，切り替え時に時間／エネルギーのオーバヘッドも掛かります．ちょっとシビアなことをしたいな？？というときは，周波数の固定化など検討してもよいかもしれません．

ただし，じゃあ `performance` で最大にしときゃええんちゃう？？となると，SoC BBQがお楽しみになれるので注意しましょう^^
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/06b3612e-c0fb-e4dc-063f-30c695cfc610.png)


ということで改めまして，[ NervesJP ](https://nerves-jp.connpass.com)にれっつじょいなす！！

[![NCJP_Logo3_V2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/5ef22bb9-f357-778c-1bff-b018cce54948.png)](https://nerves-jp.connpass.com)