はじめに
汎用性の高い、map関数。(remapとも言う?)
私は特にArduinoのアナログ入力→アナログ出力(PWM)の変換で使用することが多いのですが、
毎回古いコードから探すのが面倒なので自分用にメモ。
結論
コピペ用に結論から書いておきます。
測定した限りでは、これが一番速かったです。
// analog read to analog write
#define ar2aw(x) ((long)(x * 0.249266862170088f)) // 0.249266862170088 = 255/1023
このあと解説、検証を行うので興味があれば見ていってください。
解説
どういった経緯で上のコードになるのか、通常のmap関数から段階的に記載します。
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
↓戻り値をyとし、数式に直す
y = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
↓実際の設定値を当てはめる(入力=0~1023, 出力=0~255)
y = (x - 0) * (255 - 0) / (1023 - 0) + 0
↓±0は不要なので削除
y = (x) * (255) / (1023)
↓固定値部分を事前に計算
y = x * 0.249266862170088
となります。
検証
使用する環境
・Arduino IDE
・Arduino UNO R4 Minima
(通常のUNOを引っ張り出してくるのが面倒だったので32bitのArduinoで…)
検証用のコード
何パターンか試してみました。
変更内容は各関数のコメントに書いてあります。
sumをprintしている理由は、sumの値を使わない場合だとdtの値が0になるためです。
恐らくコンパイル時の最適化によってforやmap関数ごと必要無い処理とみなされ省略されているのだと思います。
#define ANALOG_READ_MIN (0)
#define ANALOG_READ_MAX (1023)
#define ANALOG_WRITE_MIN (0)
#define ANALOG_WRITE_MAX (255)
// 数式を簡略化
long map2(long x)
{
return (long)(x * 0.249266862170088f);
};
// inlineを付ける
inline long map3(long x)
{
return (long)(x * 0.249266862170088f);
};
// マクロで記載
#define map4(x) ((long)(x * 0.249266862170088f))
// long型のまま計算
long map5(long x)
{
return (ANALOG_WRITE_MAX * x / ANALOG_READ_MAX);
};
void resultPrint(int number, unsigned long ts, unsigned long te, int sum)
{
unsigned long dt = te - ts;
String str = "map" + String(number) + "() " + String(dt) + " msec sum=" + String(sum);
Serial.println(str);
}
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.println("");
unsigned long ts, te;
int n = 10000; // 繰り返し回数
int sum = 0;
int number = 1;
// ====================================================================================================
// 1
// ====================================================================================================
ts = millis();
sum = 0;
for (int i = 0; i < n; ++i)
{
for (int j = ANALOG_READ_MIN; j <= ANALOG_READ_MAX; ++j)
{
sum += map(j, ANALOG_READ_MIN, ANALOG_READ_MAX, ANALOG_WRITE_MIN, ANALOG_WRITE_MAX);
}
}
te = millis();
resultPrint(number++, ts, te, sum);
// ====================================================================================================
// 2
// ====================================================================================================
// ~ ほぼ同じなので省略 ~
}
処理時間の測定結果
上のコードを実行した結果です。
3倍くらい高速化されました。
map1() 7372 msec sum=1300500000 ←Arduinoライブラリのmap()
map2() 2354 msec sum=1300500000
map3() 2568 msec sum=1300500000
map4() 2354 msec sum=1300500000
map5() 2666 msec sum=1300500000
※環境によって速度差が違う可能性があります。
何度か実行しましたが傾向は変わらなかったです。
(2)(4)は恐らく(2)の時点でコンパイル時の最適化によって関数が展開されて
内部的には同じになっているのだと思います。
(3)と(4)は同じになると思ったのですが
inlineを付けて遅くなることもあるんですね…謎です。
(5)は下記の違いで結果的に少し遅くなっているのかもしれません。
・(2)(4)
float型の掛け算 1回、long型にキャスト
・(5)
long型の掛け算 1回、long型の割り算 1回
(2)(4)どちらを使うかは好みで良いのかなと。
もし、より高速化できる方法があればご教授いただけますと幸いです。
コメント