逆リファクタリングとfma関数
この記事について
100%ポエムです。あとは微妙な技術的要素。
俺は無知だからfma関数を使い道がよくわからん意味不明の関数だと思ってたんだけど、実際は浮動小数点の高速計算のために使われたり、そのためハードウェア実装されていたり、厳格に仕様があってIntelのCPUではHaswellから実装された最近重要なものだったと知った。
— ワイルド (@while_D_) 2018年4月27日
fma関数と僕
僕が所属しているサークルでは、人生において無意味みたいなことをときたまやっているのですが、先日逆リファクタリングをしようという動きがありました。 ざっくり逆リファクタリングを説明すると、いかにコードを汚くかけるかというもので、クソほどの役にもたちません。
実を言うと去年一度開催してまして、そのときにお世話になったのが記事名にもあるfma関数です。このfma関数は何をしてくれるかと言うと
fma(a, b, c); => a * b + c
といった具合に積和演算をしてくれます。この関数があると逆リファクタリングする際にあることができます。
そう...アラビア数字を1文字も使わなくてよいのです!!
ご存知の通りc++ではtrueが1、falseが0であり、fma関数から任意の数字を作り出すことが可能です。(これ書いてる途中に気がついたんですが、ビット演算ができるから別にfma関数なくても任意の数を作れますね)
以下はほんの一例
fma(fma(true, true, true), fma(true, true, true), true); => 2 * 2 + 1 = 5 fma(true, ~false, ~false) => -2
そもそもこの関数なんのためにあるんだ?
恥知らずなので最近業務でc++を書きはじめてバッドになってる先輩に「数字を使わないでFizzBuzzかけるんですよ〜〜」とか言ったらひとしきり大爆笑した後、謎関数呼ばわり。 気になったので調べてるとどうやらfma関数はC++99の時点であってかなりの古参関数らしい。
こんな掛けて足すだけの関数が本当に重要なのか……?とう疑念を持ちつつも調べていくと、どうやらハードウェアレベルの話でfma系の命令が実装されているっぽいことがわかった。その時は完全にSIMDとかSSEとかに無知だったので意味不明だったのだが、それらを調べると「俺のほうが無知、fmaは偉大みたい」な話に発展。
どうやらfmaをハードウェア側で実装すると丸めの計算が1回に減る上に、大量のベクトル計算ができるから爆速でいいねということだった。
となると当然fma命令を実際に回したかったのだが、僕の環境ではfma命令を入れてくれなかった。俺は偽物のfmaを呼び出して和積を計算。ちゃんとコンパイルオプションつけてコンパイルするとfma関数でfma命令を入れてくれるらしい。
最後に
そんなこんなでサークルのごく一部で無駄に熱いコンテンツになったfma。 そのおかげでCPUのアーキテクチャやマイコンなんかも触りたくなって結構いい感じにテックできたなといった感じです。機会があれば積極的に使っていこうと思います、それでは。
FMA周りが詳しく紹介されているサイト。
FMA (Fused Multiply-Add) について色んな観点でまとめてみた - 小清水さんとコンピューター数学
あと去年書いたコード。
#include <iostream> #include <cmath> using namespace std; int inc(int num); int zero(); int one(); int two(); int three(); int five(); int seven(); int ten(); int sixty_six(); int seventy(); int one_hundred_five(); int one_hundred_seventeen(); int one_hundred_twenty_two(); string Fizz(); string Buzz(); int main(){ for(int i=zero(); i <= fma(ten(), two(), zero()); i = inc(i)){ if(i%three() == zero() && i%five() == zero()) cout << Fizz() << Buzz() << endl; else if(i%three() == zero()) cout << Fizz() << endl; else if(i%five() == zero()) cout << Buzz() << endl; else cout << i << endl; } } int inc(int num) { return fma(num, one(), one()); } int zero(){ return false; } int one(){ return true; } int two(){ return fma(one(), one(), one()); } int three(){ return fma(two(), one(), one()); } int five(){ return fma(three(), one(), two()); } int seven(){ return fma(three(), two(), one()); } int ten(){ return fma(five(), two(), zero()); } int sixty_six(){ return fma(five(), ten(), fma(three(), five(), one())); } int seventy(){ return fma(ten(), seven(), zero()); } int one_hundred_five(){ return fma(ten(), ten(), five()); } int one_hundred_seventeen(){ return fma(one_hundred_five(), one(), fma(ten(), one(), two())); } int one_hundred_twenty_two(){ return fma(one_hundred_five(), one(), fma(ten(), one(), seven())); } string Fizz(){ string fizz; fizz.push_back((char)seventy()); fizz.push_back((char)one_hundred_five()); fizz.push_back((char)one_hundred_twenty_two()); fizz.push_back((char)one_hundred_twenty_two()); return fizz; } string Buzz(){ string buzz; buzz.push_back((char)sixty_six()); buzz.push_back((char)one_hundred_seventeen()); buzz.push_back((char)one_hundred_twenty_two()); buzz.push_back((char)one_hundred_twenty_two()); return buzz; }