3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

各種マイコンで数値計算してみた!

3
Last updated at Posted at 2025-09-13

始める前に

みなさま閲覧ありがとうございます。数日前に記事を投稿して、その後いくつか修正を加えました。内容は変わっていないのですが、コードのミスや説明不足なところを補足しました。結果の傾向が大きく変わったわけではありませんが、より正確な比較ができるようになったと思います。

特にATMega32U4の結果が信じがたいものだったので、コードを見直しました。そもそもintで扱える範囲が他のCPUとは違っています。これをちゃんと考慮しないと他のCPUとの比較が妥当ではありません。

また、ATMega32U4の結果が速すぎるのはなぜか?という点についても、コードを見直しました。違う関数を書いてみましたので、参考にしてください。

はじめに

サイン波を再生するプログラムを書いていたのです。そこで、PCMデータが1秒間に何サンプル必要化を求めようと思って、いくつか考えてみました。

  • 周波数も時間もfloatとして考える
  • 時間はミリ秒で考えて整数として考える
  • 周波数も整数にしてしまう

ちょっと考えるだけでもこれだけあって、実際に動かしてみたらどうなるのだろう…、と考えてみました。

なんとなく知ってること

  • floatって遅いんじゃないの?
  • 整数使うと誤差が発生するんじゃないの?
  • ちょっと考えておかないとオーバーフローしちゃうよね

これらを踏まえて、以下の3つを考えてみました。

実装

普通に実装

unsigned float_normal(float f, float d) {
  float x = f * d;
  return static_cast<unsigned>(x);
}

できるだけ浮動小数点を使って計算する。

整数を使って実装

unsigned integer_normal(unsigned f, unsigned d) {
  unsigned x = (f / 1000) * d;
  return x;
}

1ミリ秒が何サンプルになるかを考えて、それに時間をかけ合わせる。

誤差を減らすようにしたらこれ

unsigned integer_underflow_fix(unsigned f, unsigned d) {
  unsigned x = (uint32_t(f) * d) / 1000;
  return x;
}

dの大きさにもよるけれど、誤差は減らせるはず。ただ、(f*d)がATMEGA32U4のunsigned intを超えるので、uint32_tにキャストしてから計算する。これは結果を見るとわかるが大きな影響を受けている。

ATMega32U4がやたらに速い?

比較用に、1ずつ足す関数を用意した。

unsigned float_add() {
  volatile float a = 0.0;
  for (uint32_t i = 0; i < 50000; ++i) {
    a += 1.0;
  }
  return static_cast<unsigned>(a);
}

unsigned int_add() {
  volatile unsigned a = 0;
  for (uint32_t i = 0; i < 50000; ++i) {
    a += 1;
  }
  return a;
}

結果はいかに

呼び出し側のコード

様々な最適化をコンパイラは行う。このようなテストを行うときはどうにかしてコンパイラの最適化を抑制する必要がある。

コードは以下のようになる。

void measurement() {
  constexpr unsigned repeated = 1000;
  volatile unsigned x = 0;
  volatile unsigned input_int = 500;
  volatile float input_float = 0.5;
  volatile unsigned freq_int = 22050;
  volatile float freq_float = 22050.0;
  auto start = micros();
  // 繰り返し回数がATMEGA32U4のuintの範囲に収まるように、1000 * 1000で行う
  for (unsigned i = 0; i < repeated; ++i) {
    for (unsigned j = 0; j < repeated; ++j) {
      x = float_normal(freq_float, input_float);
    }
  }
  auto end = micros();
  auto duration = end - start;
  Serial.println(String("float_normal() Execution time: ") + String(duration) +
                 String(" microseconds"));

  start = micros();
  for (unsigned i = 0; i < repeated; ++i) {
    for (unsigned j = 0; j < repeated; ++j) {
      x = integer_normal(freq_int, input_int);
    }
  }
  end = micros();
  duration = end - start;
  Serial.println(String("integer_normal() Execution time: ") +
                 String(duration) + String(" microseconds"));

  start = micros();
  for (unsigned i = 0; i < repeated; ++i) {
    for (unsigned j = 0; j < repeated; ++j) {
      x = integer_underflow_fix(freq_int, input_int);
    }
  }
  end = micros();
  duration = end - start;
  Serial.println(String("integer_underflow_fix() Execution time: ") +
                 String(duration) + String(" microseconds"));

  start = micros();
  for (unsigned i = 0; i < 20; ++i) {
    x = float_add();
  }
  end = micros();
  duration = end - start;
  Serial.println(String("float_add() Execution time: ") + String(duration) +
                 String(" microseconds"));
  start = micros();
  for (unsigned i = 0; i < 20; ++i) {
    x = int_add();
  }
  end = micros();
  duration = end - start;
  Serial.println(String("int_add() Execution time: ") + String(duration) +
                 String(" microseconds"));
}

platformio.iniは下記のようになる。

[env:seeed_xiao_rp2040]
platform = raspberrypi
board = seeed_xiao_rp2040
framework = arduino
build_flags = -O0
build_unflags = -flto

まだまだ考えなければならないことはあるとは思うけれど、傾向は見えるようになったと思うので、これで各種マイコンでやってみよう。

エントリーしたマイコンたち

  • XIAO RP2040
  • Raspberry Pi Pico2 RP2350
  • XIAO ESP32C3
  • XIAO ESP32C6
  • XIAO ESP32S3
  • XIAO nRF52840
  • Arduino Leonardo
  • Intel Core I9 10900K

LeonardoとRP2040にはFPUが搭載されていないらしい。これを踏まえて、どんな結果になるか楽しみである。では上からやってみよう。

RP2040

HARDWARE: RP2040 133MHz, 256KB RAM, 2MB Flash
float_normal() Execution time: 915007 microseconds
integer_normal() Execution time: 264396 microseconds
integer_underflow_fix() Execution time: 264375 microseconds
float_add() Execution time: 664611 microseconds
int_add() Execution time: 68009 microseconds

なるほど、float使うと4倍くらい遅いらしい。こりゃ整数にするかもね。

RP2350

HARDWARE: RP2350 150MHz, 512KB RAM, 4MB Flash
float_normal() Execution time: 60226 microseconds
integer_normal() Execution time: 87030 microseconds
integer_underflow_fix() Execution time: 93708 microseconds
float_add() Execution time: 46852 microseconds
int_add() Execution time: 33477 microseconds

RP2040との速度差にもびっくりなんだけど、FPUの効果が絶大。こりゃfloatを避ける意味なんてない。

ESP32C3

HARDWARE: ESP32C3 160MHz, 320KB RAM, 4MB Flash
float_normal() Execution time: 1037713 microseconds
integer_normal() Execution time: 207578 microseconds
integer_underflow_fix() Execution time: 207578 microseconds
float_add() Execution time: 566049 microseconds
int_add() Execution time: 44049 microseconds

FPU載ってるらしいんだけどなあ。RP2040とあまり大きな差はないかも?

ESP32C6

HARDWARE: ESP32C6 160MHz, 320KB RAM, 4MB Flash
float_normal() Execution time: 478412 microseconds
integer_normal() Execution time: 100751 microseconds
integer_underflow_fix() Execution time: 100753 microseconds
float_add() Execution time: 352502 microseconds
int_add() Execution time: 31494 microseconds

C3に比べると随分速くなっている。だけど、傾向は変わらない。floatの計算を整数計算に置き換えてやるメリットはある。

ESP32S3

HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
float_normal() Execution time: 58617 microseconds
integer_normal() Execution time: 54433 microseconds
integer_underflow_fix() Execution time: 62805 microseconds
float_add() Execution time: 50232 microseconds
int_add() Execution time: 37683 microseconds

さすが、速い。floatを整数にする意味はもはやない。

nRF52840

HARDWARE: NRF52840 64MHz, 232KB RAM, 792KB Flash
float_normal() Execution time: 172851 microseconds
integer_normal() Execution time: 158203 microseconds
integer_underflow_fix() Execution time: 157227 microseconds
float_add() Execution time: 109375 microseconds
int_add() Execution time: 110351 microseconds

こちらもfloatを使わなくしても速度に影響は殆どない。というより、これは優秀。クロック数比較してもS3の1/4なのだから。

ATMega32

HARDWARE: ATMEGA32U4 16MHz, 2.50KB RAM, 28KB Flash
float_normal() Execution time: 13632928 microseconds
integer_normal() Execution time: 14393728 microseconds
integer_underflow_fix() Execution time: 40327272 microseconds
float_add() Execution time: 10637824 microseconds
int_add() Execution time: 1078020 microseconds

にわかに信じがたいのだが、こうなった。アセンブラを見てみたが、float演算はcall命令だけみたい。スタック操作も起きていないように見えるのはなぜ?!クロックに比例して遅い。long intを使うととたんに遅くなるようだ。floatの足し算で十分時間がかかっているので、こちらのほうが本来の実力なのだろう。volatile宣言しているのだけれどコンパイル時に定数が使い回されちゃっていて、float_normal()が速く見えたのかもしれない。

Intel Core I9 10900

Hardware: Intel Core I9 10900K (3.6GHz / 5GHz)
float_normal() Execution time: 2034 microseconds
integer_normal() Execution time: 2989 microseconds
integer_underflow_fix() Execution time: 5624 microseconds

はい、流石にPCは速いです。参考程度に。

結果のまとめ

CPU クロック float_normal (μs) integer_normal (μs) integer_underflow_fix (μs) 備考
RP2040 133MHz 915,007 264,396 264,375 FPUなし
RP2350 150MHz 60,226 87,030 93,708 FPU搭載
ESP32C3 160MHz 1,037,713 207,578 207,578 FPU搭載
ESP32C6 160MHz 478,412 100,751 100,753 FPU搭載
ESP32S3 240MHz 58,617 54,433 62,805 FPU搭載
nRF52840 64MHz 172,851 158,203 157,227 FPU搭載
ATMega32U4 16MHz 13,632,928 14,393,728 40,327,272 FPUなし
Intel Core I9 3.6GHz 2,034 2,989 5,624 参考値

性能比較(浮動小数点演算の相対速度)

※Intel Core I9は参考値のため除外

  • 最速: ESP32S3 (58,617μs)
  • 最遅: ATMega32U4 (13,632,928μs)
  • 速度差: 約232.6倍

性能比較(整数演算の相対速度)

※Intel Core I9は参考値のため除外

integer_normal() の比較

  • 最速: ESP32S3 (54,433μs)
  • 最遅: ATMega32U4 (14,393,728μs)
  • 速度差: 約264.4倍

integer_underflow_fix() の比較

  • 最速: ESP32S3 (62,805μs)
  • 最遅: ATMega32U4 (40,327,272μs)
  • 速度差: 約642.4倍

演算方式別の性能ランキング

※Intel Core I9は参考値のため除外

浮動小数点演算 (float_normal)

  1. ESP32S3: 58,617μs
  2. RP2350: 60,226μs
  3. nRF52840: 172,851μs
  4. ESP32C6: 478,412μs
  5. RP2040: 915,007μs
  6. ESP32C3: 1,037,713μs
  7. ATMega32U4: 13,632,928μs

整数演算 (integer_normal)

  1. ESP32S3: 54,433μs
  2. RP2350: 87,030μs
  3. ESP32C6: 100,751μs
  4. nRF52840: 158,203μs
  5. ESP32C3: 207,578μs
  6. RP2040: 264,396μs
  7. ATMega32U4: 14,393,728μs

整数演算 (integer_underflow_fix)

  1. ESP32S3: 62,805μs
  2. RP2350: 93,708μs
  3. ESP32C6: 100,753μs
  4. nRF52840: 157,227μs
  5. ESP32C3: 207,578μs
  6. RP2040: 264,375μs
  7. ATMega32U4: 40,327,272μs

FPU効果の比較

FPU搭載による浮動小数点演算の改善

RP2040 (FPUなし) vs RP2350 (FPU搭載)

  • RP2040: 915,007μs
  • RP2350: 60,226μs
  • 改善率: 約15.2倍の高速化

精度を保つ演算での比較(float_normal vs integer_underflow_fix)

※integer_normal()は除算による誤差を含むため、正確な比較にはinteger_underflow_fix()を使用

ESP32S3 (240MHz, FPU搭載)

  • float_normal: 58,617μs
  • integer_underflow_fix: 62,805μs
  • : floatが6.7%高速(FPUによる最適化)

RP2350 (150MHz, FPU搭載)

  • float_normal: 60,226μs
  • integer_underflow_fix: 93,708μs
  • : floatが35.7%高速(FPUの威力)

nRF52840 (64MHz, FPU搭載)

  • float_normal: 172,851μs
  • integer_underflow_fix: 157,227μs
  • : integerが9.9%高速(僅差)

FPU効果のまとめ

  • FPU搭載の効果は絶大: RP2040→RP2350で15倍の性能向上
  • 現代マイコンではfloat使用推奨: ESP32S3、RP2350、nRF52840ではfloatが実用的
  • FPUなしでは整数演算が有利: RP2040では整数演算が3.5倍高速
  • 8bitマイコンは例外: ATMega32U4ではfloatライブラリが意外に最適化されている

加算命令の比較

ATMEGA32U4の結果に疑問を感じたので、加算命令の実行時間も測定してみた。

CPU クロック float_add (μs) int_add (μs) 備考
RP2040 133MHz 664,611 68,009 FPUなし
RP2350 150MHz 46,852 33,477 FPU搭載
ESP32C3 160MHz 566,049 44,049 FPU搭載
ESP32C6 160MHz 352,502 31,494 FPU搭載
ESP32S3 240MHz 50,232 37,683 FPU搭載
nRF52840 64MHz 109,375 110,351 FPU搭載
ATMega32U4 16MHz 10,637,824 1,078,020 FPUなし

こちらの結果はおそらく妥当なのではないだろうか。ATMega32U4はやはり遅い。float_add()がint_add()の約10倍かかっている。

感想

S3は優秀ですね。CPUクロックも一番高いのですが、速さとしては優秀ですね。続いてRP2350の浮動小数点計算の速さに興味を引かれました。

驚いたのがATMega32です。どうしてこんなに速いのかよくわからないのですが、速いです。

当たり前といえば当たり前なのですが、FPUをちゃんと搭載しているCPUならば、浮動小数点演算を避けるために整数にすると、実は遅くなることがあるんだなということです。Intel Core I9での結果を見ると明らかですが、他のマイコンでも似たような結果になるのは時間の問題でしょう。S3とかRP2350ではもうfloatを使わない理由が遅いからというのは過去の話のようです。

ということで、私のコードで百万回なんか連続して呼び出すようなことはおそらく一生ないと思うので、floatをびしびし使っていきたいなと思いました。

これを読まれた方で、「なんか違うんじゃないの?」とか「条件が甘いでしょ!」とか改善提案とかありましたら教えていただけると嬉しいです。

マイコン選びの参考になれば幸いです。

参考情報

3
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?