Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その14

atan関数の実装について色々と見てきたが、最後にそれぞれの速度を計測してみよう。

  • 標準関数
  • M-KAI氏の方法
  • こびにぃ氏の方法

を、それぞれ1000回実行し、時間を表示してみた。

f:id:raohu69:20180729151710p:plain


  • 標準関数:179,532マイクロ秒

raohu69.hatenablog.jp

  • M-KAI氏の方法:52,708マイクロ秒

raohu69.hatenablog.jp

  • こびにぃ氏の方法:10,176マイクロ秒

raohu69.hatenablog.jp


こびにぃ氏の方法が、圧倒的に高速だった。
割り算を使用していないことが、高速化につながったと思われる。


ゲームの種類によって、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);
  }
}