Raspberry Pi 3 の消費電流計測

Raspberry Pi 3 って 2.5A 食うらしいし、電源周り難しいよね』って不安だったので、計測してみた。

先にまとめ。

Pi3 vs Pi2 で起動時の電流計測してみた まとめ

  • 起動時ピーク電流は Pi 2 がやや少ない。
  • アイドル状態では大差無い。
  • shutdown 後の待機電流は Pi 3 のほうが優秀? 個体差ある?
  • Pi 3 の内蔵 WiFi, Bluetooth は無効にしても消費電流は大して下がらない。
  • リンクしない方が起動時のピーク電流が大きい?

それで?

  • 低負荷状態だと 2A ぐらい想定の Pi 2 の電源環境でも問題なさそう。
  • 通信しながら、計算させながらの負荷状態だとどうなるかは今後の課題

以下計測内容

計測環境

本体 Raspberry Pi 3 Model B, Raspberry Pi 2 Model B
OS Raspbian Jessie Lite
ACアダプタ オズマ OSMA IACU2-024WN
電源用USBケーブル Nexus7(2013)付属のケーブル
USB簡易電圧電流テスタ ルートアール RT-USBVAC3QC
USB WiFi NIC バッファロー WLI-UC-GNM2 (hostapd でもそのまま使える便利)
microSD Transcend TS16GUSDU1PE 16GB Class10 UHS-I

ネットワークアドレス取得は全てDHCP

USB AC アダプタと USB ケーブルの間に RT-USBVAC3QC を挟んで、電源を接続してからログイン待ち状態になるまでの間、電流計を睨んで目視で観察。
その目視でのピーク時消費電流とログイン待ちアイドル時消費電流をメモ。
ネットワーク周りの環境を次の通り変更して、それぞれ観察した結果。

凡例 NICの状態
× 物理的に接続しない
接続してるが未リンク (USB NICでは挿しただけ状態)
リンクアップ有 (通信可能状態)


まず Raspberry Pi 3 。内蔵WiFiBluetoothがあるので、その有無効の状態も変えて観察。

有線LAN 内蔵WiFi USB WiFi NIC boot時 ピーク(A) ログイン待ちアイドル(A)
× × × 0.48 0.21
× × 0.49 0.21
× × 0.57 0.25
× 0.48 0.24
0.84 0.42
0.77 0.44
× 0.49 0.26

shutdown 後の待機時電流は 0.08A


次に Raspberry Pi 2 。

有線LAN USB WiFi NIC boot時 ピーク(A) ログイン待ちアイドル(A)
× × 0.37 0.24
× 0.42 0.27
0.65 0.44
0.59 0.48

shutdown 後の待機時電流 0.12A。
別の Pi 2 だと shutdown 後 0.00A だったりするので、個体差が大きいのかもしれない。


参考にした Raspberry Pi 3 の電源周りのお話

ACアダプタもそうだけどケーブルも重要
Raspberry Pi 3の電源問題について 株式会社スイッチサイエンス 宗村 和則
CPU負荷をかけた状態で 0.85A 前後の温度80℃。安物1Aアダプタだと不安定
RPi3:2.5A必要らしいけどいろんな電源を試してみる | Japanese Raspberry Pi Users Group
通常利用では Raspberry Pi2 と大差なしでヨユーでは?
Raspberry Pi 3 の電源は本当に2.5A必要なのか? - 時には立ち止まってみるのもいいよ

機材買いたい人は

amazonはアフィ付き

本体 Raspberry Pi 3 Model B 秋月電子通商 amazon
本体 Raspberry Pi 2 Model B 秋月電子通商 amazon
microSD Transcend TS16GUSDU1PE 16GB Class10 UHS-I ヨドバシ.com amazon
USB WiFi NIC バッファロー WLI-UC-GNM2 ヨドバシ.com Joshin amazon
ACアダプタ オズマ OSMA IACU2-024WN ヨドバシ.com Joshin amazon
ACアダプタ 秋月電子通商 3A USB ACアダプタ(固定ケーブル) 秋月電子通商 2.5A USB ACアダプタ(アダプタ単体)
電源用USBケーブル ELECOM 高耐久2A対応ケーブル1.2m MPA-AMBS2U12BK ヨドバシ.com Joshin amazon
USB簡易電圧電流テスタ ルートアール RT-USBVAC3QC 秋月電子通商 ヨドバシ.com(前モデルの RT-USBVAC2) amazon

InnerException の再スローには ExceptionDispatchInfo を使おう

単発タスク(Task.WaitAllとかで集約していないタスク)を使った非同期処理を扱うメソッドでの例外を投げる場合、 AggregateException をそのまま出すと、メソッドを呼ぶ側で InnerException をチェックしたりしないといけないので面倒です(面倒ですよね?)。

/// <exception cref="AggregateException">非同期処理がIOExceptionした場合. InnerException参照のこと</exception>
void hoge() {
  Task t = Task.Run(() => { // ※ 裏で進められる IOException が出るかもしれない処理 });
  // ※ 表の処理
  t.Wait();    // AggregateException 出るかも
}

try {
  hoge();
} catch (AggregateException e) {
  if (e.InnerException is IOException) {
    // IOException の処理
  }
}

InnerException を throw するとスタックトレースが破棄されて(デバッグ時に)使い物にならない。

/// <exception cref="IOException">IOException出た</exception>
void hoge() {
  Task t = Task.Run(() => { // ※ 裏で進める IOException が出るかもしれない処理 });
  // ※ 表の処理
  try {
    t.Wait();    // AggregateException 出るかも
  } catch (AggregateException e) {
    throw e.InnerException;
  }
}

try {
  hoge();
} catch (IOException e) {
  // IOException の処理
  // ※ e のスタックトレースは throw e.InnerException 時点なので…
}

なので、 .Net 4.5 以降に制限されますが、 ExceptionDispatchInfo.Capture を使ってスタックトレースを保ったまま InnerException を throw しましょう。
呼び出し元では IOException 待つだけなので少し幸せに。

/// <exception cref="IOException">IOException出た</exception>
void hoge() {
  Task t = Task.Run(() => { // ※ 裏で進める IOException が出るかもしれない処理 });
  // ※ 表の処理
  try {
    t.Wait();    // AggregateException 出るかも
  } catch (AggregateException e) {
    ExceptionDispatchInfo.Capture(e.InnerException).Throw();  // スタックトレースを保って InnerException を throw
  }
}

try {
  hoge();
} catch (IOException e) {
  // IOException の処理
  // ※ e のスタックトレースは Task 内での例外発生時点 デバッグが楽に
}


await を使えるなら await でよさそうですが、タスクのタイムアウト待ちしたりする場合や async にしたくない場合には使えないので。


.net - In C#, how can I rethrow InnerException without losing stack trace? - Stack Overflow

C# の long は読み書き Atomic じゃないから Interlocked 使おう

複数のスレッドから触られる long 変数(64bit変数)の取り扱い。 Java メインの人は引っかかりそうなのでメモ。

Java ではできる「volatile long」が C# では未サポート(コンパイルエラーになる)なので、 long 変数への Atomic な読み書きをするには読み書き部を lock するか、もしくは Interlocked を使って次の通りにする。

// hoge : マルチスレッドから触りたい変数, local_hoge : スレッドローカルな変数
long local_hoge = Interlocked.Read(ref hoge);   // long の読み込み hoge → local_hoge
Interlocked.Exchange(ref hoge, local_hoge);     // long の書き込み hoge ← local_hoge

Java では volatile long としておけば、読み書きに対する Atomic 性は保証されているので、 JavaC# をうろうろしている人にはちょっとした罠。
C# では 32bit 単位のメモリアクセスを想定しているのか、それを越えるサイズの long や double の Atomic なアクセスは言語機能としてはカバーしない。ライブラリを使えというスタンスな模様。
Java よりハードウェアに近いそういうトレードオフなんだろう。

lock が速いか Interlocked が速いかは評価していないけれど、たぶん後者が速いんだろう。

JavaのAtomicXXXX相当のクラスが欲しいなぁ)

マルチスレッド環境でlongを共有するときの注意 (C#編) - No hack, no life.
volatile (C# リファレンス)
Interlocked クラス (System.Threading)

2016/7/14 追記
Interlocked.Read メソッド (Int64) (System.Threading) の解説によると

解説
The Read method is unnecessary on 64-bit systems, because 64-bit read operations are already atomic.On 32-bit systems, 64-bit read operations are not atomic unless performed using Read.

The Read method and the 64-bit overloads of the Increment, Decrement, and Add methods are truly atomic only on systems where a System.IntPtr is 64 bits long.On other systems, these methods are atomic with respect to each other, but not with respect to other means of accessing the data.Thus, to be thread safe on 32-bit systems, any access to a 64-bit value must be made through the members of the Interlocked class.

『32bit 環境だと 64bit 変数は atomic にアクセス出来ないから Interlocked 使え』とのこと。
64bit 環境だと最適化されて Interlocked.Read とかは空メソッド相当になるんだろうから、 volatile long とすればコンパイラが頑張って Interlocked 経由の操作に置き換えてくれたらいいのに。

TcpClient#Connect のタイムアウト制御

TcpClient でコネクションを張る際の接続時タイムアウトをユーザコードで任意に指定しようとすると、次のようなコードが今風(C# 5.0 以降風)なのだろうか?

try {
    int timeout = 接続タイムアウト(ms);
    TcpClient client = new TcpClient();
    Task con_task = client.ConnectAsync(remote_addr, remote_port);
    if (!con_task.Wait(timeout))    // 接続拒否などは AggregateException
    {
        client.Close();
        throw new SocketException(10060);   // 10060 -> WSAETIMEDOUT
    }

    // ※接続後の処理

} catch (SocketException e) {
   // timeout でのタイムアウト
} catch (AggregateException e) {
   if (e.InnerException is SocketException) {
       // 接続失敗 拒否されたか Socket でのタイムアウト
   }
}

※timeout にシステムデフォルトのタイムアウト時間(約21秒)より長い時間を指定した場合は、システムデフォルト値が優先されます。
timeout 優先にしようとすると、リトライ処理を書くことになりそうな
例外が SocketException と AggregateException に割れるのも美しくない…

WindowsにおけるTCPの接続要求タイムアウト - 涼の成長記録

C# 非同期メソッドを作るにあたり、例外が出るタイミングでハマったメモ

通常のメソッドを非同期化しようとしてハマった罠。
普段 C# を使いこんでる人は既知だろうからスルー推奨。

次のような感じのコードで hoge を非同期化しようとする場合、

void Main() {
  try {
    hoge();
  } catch (Exception e) {
    ※ 例外処理
  }
  ※ 続きの色々な処理
}

void hoge() {
  ※ 例外吐きそうな処理
  ※ 非同期化したい部分
}

こうしないと『※ 例外吐きそうな処理』で生じる例外を拾えない。

void Main() {
  var t = hoge();    // 『※ 例外吐きそうな処理』は同スレッドで走るが、ここで例外は出ない
  if (t.IsFaulted) {
    try {
      t.Wait();      // 『※ 例外吐きそうな処理』が出した例外はここで初めて出てくる
    } catch (Exception e) {
      ※ 例外処理
    }
  }
  ※ 続きの色々な処理
}

async Task hoge() {
  ※ 例外吐きそうな処理
   await Task.Run(() => { ※ 非同期化したい部分 });
}


async - await の説明をざっと見て、『要は await の所まで同じスレッドで走って、そこから先は Worker で走らされて、元スレッドはそのまま戻るんでしょ』と単純に解釈して、 Main から hoge に処理が進んで、『※ 例外吐きそうな処理』が例外出したら catch すればよし。と次のようにしたら、その catch が効かない。

void Main() {
  try {
    hoge();
  } catch (Exception e) {
    ※ 例外処理
  }
  ※ 続きの色々な処理
}

async void hoge() {
  ※ 例外吐きそうな処理
   await Task.Run(() => { ※ 非同期化したい重い部分 });
}


VisualStudio の出力ウィンドウには『例外がスローされました』と出ているし、『※ 例外吐きそうな処理』を try - catch で囲むと間違いなく例外は吐いているので、例外そのものは出ている。

『async void は特殊な状況でしか使わない。async Task にしろ』ということなので Task に変更
するものの、状況は変わらず。

async Task hoge() {
  ※ 例外吐きそうな処理
   await Task.Run(() => { ※ 非同期化したい重い部分 });
}

Task が返ってきているので、ということで、 Wait() をすると、 catch されて『※ 例外処理』が走るものの、『※ 非同期化したい重い部分』を含めて hoge 全体が終わるまでブロックされて、非同期にならない。

void Main() {
  try {
    hoge().Wait();
  } catch (Exception e) {
    ※ 例外処理
  }
  ※ 続きの色々な処理
}

試行錯誤の結果、 Task.IsFaulted プロパティを見れば例外発生の有無が分かると判明し、最初のコードに至る。


async メソッドは、単純に走らせるスレッドを振り分けてるだけじゃなくて、全体が Task でラップされた特殊な環境下になってるんだねってことに気付いた日曜日。
等価コードとかみたら理解進むのかもしれない(自動生成Stateマシンらしい)