Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その14
atan関数の実装について色々と見てきたが、最後にそれぞれの速度を計測してみよう。
- 標準関数
- M-KAI氏の方法
- こびにぃ氏の方法
を、それぞれ1000回実行し、時間を表示してみた。
- 標準関数:179,532マイクロ秒
- M-KAI氏の方法:52,708マイクロ秒
- こびにぃ氏の方法:10,176マイクロ秒
こびにぃ氏の方法が、圧倒的に高速だった。
割り算を使用していないことが、高速化につながったと思われる。
ゲームの種類によって、atan関数に要求する値の正確さや速度、テーブルサイズは異なるだろうから、
その都度使い分けてもらえればと思う。
#include <Arduboy2.h> Arduboy2 arduboy; #define N 1000 const uint8_t atanTbl[] PROGMEM = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, }; const int8_t atanOffset[] PROGMEM = { 0, 32, 0, 32, }; uint8_t getAtan2_1(float y, float x) { uint8_t idx = 0; if (x == 0.f && y == 0.f) return idx; uint8_t offset = 0; if (x < 0.f) { x = -x; offset |= 1; } if (y < 0.f) { y = -y; offset |= 2; } if (x > y) { uint8_t ratio = y / x * 64; idx = pgm_read_byte(atanTbl + ratio); } else { uint8_t ratio = x / y * 64; idx = 16 - pgm_read_byte(atanTbl + ratio); } if (((offset + 1) & 2) != 0) { idx = -idx; } return idx + pgm_read_byte(atanOffset + offset); } const uint8_t radarTbl[] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 5, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16,11, 8, 6, 5, 4, 3, 3, 2, 2, 2, 2, 2, 2, 1, 1, 16,13,10, 8, 7, 6, 5, 4, 4, 3, 3, 3, 2, 2, 2, 2, 16,14,11, 9, 8, 7, 6, 5, 5, 4, 4, 4, 3, 3, 3, 3, 16,14,12,10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, 3, 3, 16,14,13,11,10, 9, 8, 7, 7, 6, 6, 5, 5, 4, 4, 4, 16,15,13,12,11,10, 9, 8, 7, 7, 6, 6, 5, 5, 5, 4, 16,15,14,12,11,10, 9, 9, 8, 7, 7, 6, 6, 6, 5, 5, 16,15,14,13,12,11,10, 9, 9, 8, 7, 7, 7, 6, 6, 6, 16,15,14,13,12,11,10,10, 9, 9, 8, 8, 7, 7, 6, 6, 16,15,14,13,12,12,11,10,10, 9, 8, 8, 8, 7, 7, 6, 16,15,14,14,13,12,11,11,10, 9, 9, 8, 8, 8, 7, 7, 16,15,14,14,13,12,12,11,10,10, 9, 9, 8, 8, 8, 7, 16,15,15,14,13,13,12,11,11,10,10, 9, 9, 8, 8, 8, 16,15,15,14,13,13,12,12,11,10,10,10, 9, 9, 8, 8, }; const uint8_t radarOffset[] PROGMEM = { 0, 32, 0, 32, }; int16_t getAtan2_2(int16_t y, int16_t x) { int8_t offset = 0; if (x < 0) { x = -x; offset |= 1; } if (y < 0) { y = -y; offset |= 2; } int16_t n = (y >= x) ? y : x; while (n >= 16) { n >>= 1; x >>= 1; y >>= 1; } uint8_t angle = pgm_read_byte(radarTbl + (y << 4) + x); if (((offset + 1) & 2) != 0) { angle = -angle; } return angle + pgm_read_byte(radarOffset + offset); } void setup() { arduboy.begin(); arduboy.setFrameRate(60); arduboy.clear(); } void loop() { if (!arduboy.nextFrame()) return; arduboy.pollButtons(); arduboy.clear(); // 各atan関数の戻り値を変数に入れないと、 // 最適化で処理がスキップされてしまう static float dust; // 低速(標準関数) uint32_t start = micros(); for (uint16_t i = 0; i < N; i++) { dust = atan2f(i, i); } arduboy.print(micros() - start); arduboy.print("\n"); // 中速(M-KAI氏の方法) start = micros(); for (uint16_t i = 0; i < N; i++) { dust = getAtan2_1(i, i); } arduboy.print(micros() - start); arduboy.print("\n"); // 高速(こびにぃ氏の方法) start = micros(); for (uint16_t i = 0; i < N; i++) { dust = getAtan2_2(i, i); } arduboy.print(micros() - start); arduboy.print("\n"); arduboy.display(); if (arduboy.justPressed(A_BUTTON)) { // Aボタンが押されたら画面キャプチャをシリアル通信で送る Serial.write(arduboy.getBuffer(), 128 * 64 / 8); } }