今回の内容は実行時に少し速度が速くなる小ネタ程度の内容です。
C++のコードを交えますが、考え方はどの言語でも使えるものとなります。
ベクトルの大きさを変更するには?
ベクトルの大きさのみを変更したい場合、すべての要素に同じ値を乗算か除算するScaleという処理を使用することになります。
本記事では三次元ベクトルで統一して考えてみたいと思います。
n倍のScaleの式
\begin{pmatrix}x' & y' & z' \end{pmatrix}
=
n\begin{pmatrix}x & y & z \end{pmatrix}
1/nのScaleの式
\begin{pmatrix}x' & y' & z' \end{pmatrix}
=
\begin{pmatrix}x & y & z \end{pmatrix} / n
コードで書いてみたいと思います。
n倍のScaleのコード
ScaleMul.cpp
#include <iostream>
#include <vector>
//メイン関数
int main() {
std::vector<float> vec = { 1.3f, 2.3f, 3.3f };
// ベクトルの要素を出力
std::cout << "befor:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
//ベクトルの大きさを変更、今回は2倍する。
for (auto it = vec.begin(); it != vec.end(); ++it) {
(*it) *= 2;
}
// ベクトルの要素を出力
std::cout << "after:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
system("pause");
return 0;
}
result
befor:1.3 2.3 3.3
after:2.6 4.6 6.6
続行するには何かキーを押してください . . .
1/nのScaleのコード
ScaleDiv.cpp
#include <iostream>
#include <vector>
//メイン関数
int main() {
std::vector<float> vec = { 1.3f, 2.3f, 3.3f };
// ベクトルの要素を出力
std::cout << "befor:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
//ベクトルの大きさを変更、今回は2で割る。
for (auto it = vec.begin(); it != vec.end(); ++it) {
(*it) /= 2;
}
// ベクトルの要素を出力
std::cout << "after:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
system("pause");
return 0;
}
result
befor:1.3 2.3 3.3
after:0.65 1.15 1.65
続行するには何かキーを押してください . . .
どう最適化する?
実はコンピュータは除算が苦手で、四則演算の中で最も遅いです。
有識者の記事を参考にしますと、乗算が4クロックなのに対し、除算は最大で97クロック、速度に20倍近くの違いがあることになります。
参考記事↓
人間の感覚からするとほとんど誤差がないですが、これが3Dゲームなどで毎フレーム何回も行う処理となると、やはり最適化したくなりますよね。
逆数を使って除算の回数を減らそう
除算の結果と、逆数を乗算したものの結果は同じであったはずですよね。
除算
a/b = \frac{a}{b}
逆数の乗算
a\frac{1}{b} = \frac{a}{b}
逆数は、1
をn
で除算すれば求められましたよね。
早速書いてみましょう。
ScaleDiv.cpp
#include <iostream>
#include <vector>
//メイン関数
int main() {
std::vector<float> vec = { 1.3f, 2.3f, 3.3f };
// ベクトルの要素を出力
std::cout << "befor:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
//ベクトルの大きさを変更、今回は2分の1倍にする。
float scaler = 1.0f / 2.0f;
for (auto it = vec.begin(); it != vec.end(); ++it) {
(*it) *= scaler;
}
// ベクトルの要素を出力
std::cout << "after:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
system("pause");
return 0;
}
result
befor:1.3 2.3 3.3
after:0.65 1.15 1.65
続行するには何かキーを押してください . . .
除算でScaleしたのと同じ結果ですが、除算の回数を3回から1回に減らせました。
演算の回数は多くなっていますが、除算が乗算の約20倍遅いことを考えると、毎フレーム何度もやる処理であった場合、処理を最適化できたといえるでしょう。
総括
- 除算は速度が遅い
- VectorのScaleのように、すべての要素を同じスカラーで除算する場合、逆数を求めてからそれで乗算を行った方が処理の速度が速くなる