2014年9月29日月曜日

ブロックする広告サイトの見つけ方

広告を出しているサイトを見つける方法を簡単に説明。

PCだと、画面要素の上で右クリック→要素を検証ってやるとソースが見えます。
例えば、下のイメージみたいに。


ただ、この行その物は何か広告スクリプトを読んで、そこから得た文字列を展開しているだけなので、このcd.ladsp.comを止めても他の広告は相変わらずです。必要なのは広告スクリプトの展開主なので、そこから、行を戻っていって、こうなります。このadm.shinobi.jpを閉じれば、ここからの一連の広告が止まるというわけです。


では、アンドロイドの場合は?

2014年9月28日日曜日

DD-WRTで広告を斬る

取り敢えず、最近目邪魔な画面を占拠するタイプの広告をブロックする。
adm.shinobi.jp、おまえのことじゃ。この左下のやつね。


最初はAccess restrictionから色々やってみたのだけど、うまくいかない。


諦めてググる。


/tmp/hosts0にブラックリストを作り、それをローカルDNS介して、0.0.0.0に割り当ててアクセスできないようにするらしい。詳細はこちら。

2014年9月27日土曜日

DD-WRT


DD-WRTはOSSのルーター用組み込みLinuxです。
非公式ファームウェアという奴ですね。

これをインストールしたのでその記録を。
無線を有効にすると電波法に違反するので、
無線は無効にして有線ルータ代わりにしてます。

うちは、ネットはeo光の1Gなんですが、どうにも途切れるんですね。
これで随分長いこと困ってました。

例えば、iPadのアプデDLしながら、別のタブでyoutube見てると、音なんかがぶちぶち途切れて使い物にならない。何が原因か調べたくてもどう調べればいいのか分からない。

多分ルーターが悪いんだろうなあと思ってて、eo光標準のじゃだめなのかと、1万前後のWifi付きルータを色々、NECやApple、Netgearなど試してみたのですがどれもダメでした。

今回、NetgearのWNDR4300を使っていたのですが、これにはDD-WRTが載せれます。


これをインストールしました。まず、ファームのDLから。


公式ファームからのインストールには、webflash image for first installationというimg形式を使います。もうひとつのbin形式は、DD-WRTからアップデートする際に使います。

以下詳細

2014年9月23日火曜日

setter関数について (実装別コピー回数)

C++では、setter関数を書くとき、
  1. void setter(type val) { var = val; }
  2. void setter(const type& val) { var = val; }
  3. void setter(const type& val) { var = val; }
    void setter(type&& val) { var = std::move(val); }
  4. template<typename T>
    void setter(T&& val)
    { var = std::forward<T>(val); }
  5. void setter(type val) { var = std::move(val); }
と、様々な書き型ができます。3〜5はC++11から使えるようになりました。

結論から書くと、
  • intやfloatなら1で
  • std::stringなどmoveが軽く、コピーできるなら5で
  • 派生クラスなどコピーできないなら、2で
  • 自前クラスでmoveが重く、コピーもできて、
    どうしても高速化したいなら、3で
という感じで使い分けるのがいいんじゃないかな?
4は3を1行で書いてるだけなので、普段は使いにくいかと。

3で全部書くから5は使わないという方針もありですが、
同じ関数を何個も書くのは資源の無駄ですし、
5を取り入れるのはいい考えだと思います。

最後に、それぞれの処理内容を比べてみましょう。
  1. lvalueだと関数呼び出し時のコピー1回、代入時のコピー1回
    rvalueだと関数呼び出し時のコピーは省略、代入時のコピー1回
  2. lvalueだと関数呼び出しは参照渡し、代入時のコピー1回
    rvalueだと関数呼び出し時は参照渡し、代入時のコピー1回
  3. lvalueだと関数呼び出しは参照渡し、代入時のコピー1回
    rvalueだと関数呼び出しは参照渡し、代入時のムーブ1回
  4. lvalueだと関数呼び出しは参照渡し、代入時のコピー1回
    rvalueだと関数呼び出しは参照渡し、代入時のムーブ1回
  5. lvalueだと関数呼び出し時のコピー1回、代入時のムーブ1回
    rvalueだと関数呼び出し時のコピーは省略、代入時のムーブ1回
ここでは、rvalueそのものを作成するコンストラクタ、例えば、
“setName(std::string("str"));”のstd::stringのコンストラクタ部分は
数えていません。

ムーブができるようになったので、5のコスト低下が顕著です。
また、5の場合内部でメモリ確保をしないので、noexceptionが
つけれます。そんな感じで5はなかなかいい選択肢になってます。

2014年9月21日日曜日

C++11で型の名前を出す方法

さて、templateなんかが一杯くっつき始めると、
_Tpなんて出てくる型が何の型かさっぱり……
となってしまいます。

便利な型名のダンプルーチンがPrint variable type in C++にあったので引用。
#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}
これを、type_name.hなんてファイルにしておいて、
#include <type_name.h>
...
  std::cout << "type " << type_name<_Tp>() << std::endl;
なんてするだけで型名がダンプできて便利。変数に対してやるときは、
...type_name<decltype(a)>()...
と使います。

以下に実例追記

std::moveの仕組み

こないだからC++11づいてますが、std::moveの定義内容の説明でも。
template<typename _Tp>
  constexpr typename std::remove_reference<_Tp>::type&&
  move(_Tp&& __t) noexcept
  { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

この関数の機能は、rvalueへのキャスト、つまり破壊していいよというマークをつけることです。

やっている内容は引数を参照で受け取り(つまりコピーは発生しません)、
さらに受け取った物をrvalue参照で返す(こちらもコピーは発生しません)
ということです。

ただ、その過程で戻り値がrvalue参照になるため、コンパイラは、その値を壊していいと扱うわけです。

例えばこのstd::move関数にlvalueを渡して、
std::string hello("hello");
std::string a = std::move(hello);
std::cout << hello << std::endl;
なんてすると、何も表示されません。
lvalueの変数helloは破壊されてしまいます。
気をつけましょう。



以下、もうちょっと詳しい内容
str = std::move(std::string("hello"));
こうやってrvalueを渡すと、
constexpr std::string&&
move(std::string&& __t) noexcept
{ return static_cast<std::string&&>(__t); }
と動作します。やってることは、rvalue参照をrvalue参照にキャストして返します。
つまり、何もしません。一方、
std::string hello("hello");
str = std::move(hello);
のように上記の例と同様にlvalueを渡すと、
constexpr std::string&&
move(std::string& __t) noexcept
{ return static_cast<std::string&&>(__t); }
と動作します。その理由は、std::string& &&のように使うと、
reference collapsingが起きて、std::string&と扱われるからです。
やってることは、lvalue参照をrvalue参照にキャストして返します。
つまり、破壊していいとマークを付けている訳です。

ここで、こんなコードを書くと、どちらが呼ばれるでしょうか?
void setter1(std::string&& name)
{
  name_ = std::move(name);
}
void setter2(std::string name)
{
  name_ = std::move(name);
}
どちらの関数とも、lvalue参照のstd::moveを呼び出します。
どちらも、setterを呼び出す側から見るとrvalueを渡しているのですが、
setter関数内部からは、もう変数のアドレスが指すメモリ上の値なので、
lvalueとして扱われます。

つい、見た目でrvalueなんて勘違いしそうですが、注意しましょう(自戒)。

最後に、
void setter3(const std::string& name)
{
  name_ = std::move(name);
}
とすると、どうなるでしょう?

この場合、
constexpr const std::string&&
move(const std::string& __t) noexcept
{ return static_cast<const std::string&&>(__t); }
となります。一見rvalue参照にキャストされたように見えますが、
rvalue参照の戻り値はrvalueとして扱いますので、
const std::string&&はconst std::stringとして扱われます。
そして、const付きrvalueはlvalueですので、次の代入の=では、
move assign operatorではなく、assign operatorが呼び出されます。
結果として、const std::stringの中身は、破壊されることなくコピーされるというわけです。

色々難しいですね。
性能を上げようと、rvalue参照を使いはじめると、慣れるまで色々引っかかるかもしれません。
頑張っていきましょう。

2014年9月12日金曜日

青のり

青のり、知ってますか?青のり、食べたことがありますか?
実はその青のり、アオサかもしれません。

焼きそばなんかについてくる青のりはほぼ間違いなくアオサです。
青のりは高級品だそうなので、コストを下げるようなお店やメーカーだと、
間違いなくアオサに切り替えられてます。

僕も10代の頃は気付かず青のりをたべていてその頃は青のり大好きでした。
懇意にしてたお好み焼き屋さんが、うちの青のりは高いんじゃがとか言ってくれても、
全然気付かず、もっと掛けてとかいってました。

その後、アオサに切り替わってるのに気付かず、何も疑問に思わず育って、
おっさんになって、その頃はもう青のりは付け合わせで歯につくし無くても
いいとか思ってました。

30過ぎて、初めてスーパーで青のりの原料を見るその日まで。


スーパーで買ってきた青のりの衝撃的な香り、口当たり。
スーパーで買うと50円かそこらしか変わらない値段で、
家での消費量だと一年たっても使い切らないくらいです。

スーパーでは迷わず青のりを買って、家で焼きそばやお好み焼き、
たこ焼きを食べる度に青のりを掛けて食べましょう。人生楽しみが増えますよ。

青のりの見分け方は、香りもですが、見た目が細いより糸状か、千切れた葉っぱ状かで
見分けれます。写真なんかは、青のりとアオサの違い からどうぞ。

2014年9月11日木曜日

Xeon E5-2600 v3 HSW-EP

出ましたね。メモリ速くなったのに遅くて大変です。

理論ピーク性能はE5-2695 v3、2.3GHz 14coreでDPで515.2GFLOPS (=2.3*14*2*8)@1ソケット。
メモリの理論性能が2133MT/s 4 channelで68.3GB/s(2133*4*8)@1ソケット。
BPFを計算すると、0.13BPFと。

Ivy Bridgeで同様に計算して、E5-2695 v2 2.4GHz 12coreのDPで230.4GFLOPSと、59.7GB/s。
こちらは0.26BPFと。

メモリどんだけ頑張ってもコアがそれ以上に速くなるので涙目。


BPFもそうですけど、個人的にはQPI跨ぐアクセス速度の落ち具合が気になってたり。

webの記事みてたんですが、Intel発表の資料の解像度が低くて困ったので、
解像度の高い図をリンクしてたサイトのURLでも。
解像度の高い資料つきE5-2687W v3のレビュ

SNBやIVBで1 addと1 mulの合計2計算ユニット/コア、HSWで2 fmaユニット/コアっての忘れていて、理論ピーク性能が半分でした。後コア数間違えてた。そこら辺修正してます。

2014年9月7日日曜日

rvalueとlvalueって何?rvalue参照って?

これまで、rvalue参照は壊していいと説明してきました。
しかし、何故でしょう?

というわけで少し解説。
辞典などを引くと、rvalueは右辺値、lvalueは左辺値と出てきます。
分かりやすい言葉で、分かったような気になりますが、実は何も分からない名前ですw

コンパイラの授業なんかを取ると、
lvalueはメモリ位置(アドレス)
rvalueはlvalueのメモリ位置に記録されている値
などと説明されます。

感のいい人だと、アドレスと値のことかな?なんて分かったかもしれませんね。

lvalueとrvalueは、例えば以下のCの式の左辺値と右辺値です。
a = a * 12;
これをコンパイラの中間表現の一種、three-address codeで書くと、
t1 = &a         // aのアドレス取得
t2 = *t1         // aの値取得
t3 = t2 * 12   // aの値×12
*t1 = t3         // aの値×12をaのアドレスに書き戻す
なんてなるわけですが、Cの式の「代入」という処理の左辺と右辺は、
この最後の行の左辺と右辺のことなわけです。

つまり、元々、lvalueはメモリ上のアドレスで、
rvalueはレジスタ上の値のことでした。


lvalueはメモリ上のアドレス、つまりその中身は永続的なので壊してはいけません。
rvalueはレジスタ上の値、テンポラリな物なので壊しても問題ありません。
これが、rvalueやrvalue参照を壊していいという考え方の背景です。

Cは高級言語なので、変数名さえ付いていれば代入できてしまいます。
そこらへんから、元々の意味と乖離したんでしょうね。

例えば、“a = b;”のaはaがレジスタ上にとられててもlvalueと呼びますし、
“int fun(int a)”なんて関数のaを指してrvalueでといった呼び方もします。
“int fun(const int a)”のように、constが付くと破壊できなくなるため、
lvalueになったなどという場合もあります。

C++では、もう、破壊可能な物がrvalue、破壊してはいけない物がlvalueと
考えたほうが良さそうなありさまです。

さて、いつの間にレジスタ上の値だったrvalueが、
参照なんていうメモリ上のアドレスを得ることができるようになったのでしょうか。

これは、C++で導入された参照が関係してるんではないでしょうか。
参照できるのは、アドレスを持つlvalueのみです。しかし、
setName("hello")などとやった場合の()の中身はrvalueですので
参照渡しが使えません。それが都合が悪かったので、const付き参照のみ、
rvalueを参照しても良いと例外を作り、そこからrvalueのアドレスが
とれるようになったのだと思います。

今回C++11で、rvalue参照を作ったことで、
ここらへんのネジレが解消すればいいですね。
とはいえ、元々メモリ位置とは関係なかったrvalueに、
アドレスを与えて参照するのをどんな場合にでも許すとか、
ますます元の意味からかけ離れていってる気もしますが。
まあ、高速化ジャンキーの考えることですから、しょうがないですねw

Happy Hacking!

rvalue参照

さて、便利な便利なrvalue参照ですが、そもそもこれはどういう考え方、
どういうパラダイムなんでしょうね?

C、C++98、C++11について考えてみましょう。
それぞれ、以下の扱いが存在します。
  • C、値とポインタ
  • C++98、値とポインタと参照
  • C++11、値とポインタと参照とrvalue参照
参照とは、プログラムの構文的には値として扱いながら、
関数呼び出しでは参照渡しで高速にやりとりをするために
考えられた物です。

ポインタでも同等の高速化は行えます。ですが、そのためには、
既存プログラムを修正して&などを付け直す必要がありました。

参照を使えばライブラリを使うアプリのプログラムはそのままに、
ライブラリなどの、クラスのメンバ関数のヘッダやコードだけを
参照渡しに変更するだけで高速に動くようになります。
やったね、C++。

しかし、ここで壁が現れます。もっともっと高速化したいのに、
参照渡しの値を壊していいのかどうか言語内に定義されていないから、
参照渡しの値を壊す高速化ができない!!!

後は、簡単ですね。高速化ジャンキーのためのC++11では、
壊していい参照をrvalue参照として言語自体に取り込みました。
これで皆ハッピー^^

2014年9月6日土曜日

move semanticsとrvalue参照

ところで、std::moveを使うだけで、どうして「移動」ができるんでしょうね?
std::string name;
name = std::move("hello");
C++98では、=は代入、つまりコピーでした。
つまり、std::moveだけでは「移動」できる訳がありません。

「移動」するための仕組みは、rvalue参照にあります。

std::moveは、実は実装としては何もしません。
この関数は、rvalue参照の引数を取り、それをrvalue参照に型変換するだけです。
この型変換によって、テンプレートの何処かでlvalueが紛れ込んでいないことを、
コンパイル時に確認します。
この型変換によって、何でも破壊可能なrvalueに変換します。
間違えてlvalue参照を渡すと破壊されるので注意が必要です。

実際の「移動」は、rvalue参照を引数にしたstd::string用の=オペレータが処理します。
=オペレータが、rvalueを参照渡しで受け取り、swapして内容を入れ替えます。
std::string&
operator=(std::string&& str)
{
    this->swap(str);
    return *this;
}
この、引数がrvalue参照の=オペレータを、
ムーブ代入オペレータ(move assignment operator)と呼びます。
これまでの引数がconst参照の=オペレータは、
これまで通り代入オペレータ(assignment operator)と呼びます。

しかし、なぜこのムーブ代入オペレータは「移動」するためのswapを使う
コードを記述できたのでしょうか?

それは、rvalue参照渡しのデータは、呼ばれ側が、
自由に壊して良いというルールが存在するからです。

そのため、上記の通りswapを使った、破壊型ですが、
メモリコピーの必要ない高速なコードを書くことができました。

C++11の実装では、std::stringやstd::vectorなど、標準ライブラリ全てに対し、
rvalue参照を取る新しいオペレータや関数を用意して、上記のようにswapを使うなどして、
効率化を向上し、高速化を達成しました。

その結果、std::moveが型変換しかしなくても、「移動」が実現されたというわけです。
これが、moveがsyntaxではなく、semanticsな理由でもあります。
素晴らしい、さあ、窓からC++98を投げ捨(ry

補足: もちろん、std::move("hello")の所も、そのままだとコピーが発生します。
そこで、std::move自体もrvalueの参照渡しで引数を受け取っています。

C++11、C++14

C++11のいい教科書やまとめってなんかないかね。

最近やすかったので、Effective Modern C++買ってみたら、
思っていた以上に使いやすそうなので今後C++11に移行したいと思ってる。

「C++11のいい教科書やまとめ」ってググってもなんもでないし。
とか思いながら、ふと「C++11」や「C++14」だけでググったら、
結構色々出てきたw

今の不十分な理解で好きなC++11の機能は、以下の機能。
  • std::move
  • alias
  • auto
  • range
  • lamda式
  • スマートポインタ
  • constexpr
  • noexcept
  • const function
簡単にmoveとlamda式について説明してみる。

まず、std::move!これは、これまではコピーしかできなかったC言語に、
新しく移動、つまり元の値を壊していいという新しいsemanticsを追加した物です。

semanticsて何?というと長い話になるので割愛。
素晴らしい点は「移動」できるという機能を汎用的に定義し宣言した点です。
これまでも「移動」の概念自体はありましたが、汎用的ではなかったので、
これを利用して性能向上しようなどという話はありませんでした。今回、
何でも「移動」できるようになったため、色々最適化できるようになりました。

例えば、このstd::moveがあると、こんな事ができるのです。
class test {
    std::string name;
public:
    void setName(std::string str) { name = std::move(str); }
};
これの何が凄いかというと、std::stringのコンストラクタが1回しか呼ばれない点です。

これまで、
    void setName(const std::string& str) { name = str; }
という書き方をしていましたが、これだとsetName("hello");とすると、
関数呼び出し前にコンストラクタが1回、内部の代入でメモリ割り当てが1回と、
重い処理が2回走ってました。

ですがこれからは値渡しの1関数だけでOk、素晴らしい!!!
どんな場合でも関数呼び出し前にコンストラクタが1回走るだけです。
setterの中で例外が発生しにくい点もいいですね。 
これだけでC++98捨てるのに躊躇ありません。

lamda式は、algorithmを使って関数オブジェクト書いてた人だけ、
嬉しい機能です。これまでは関数オブジェクトを利用する関数の外側とか、
凄く離れた位置に処理内容を書く必要がありました。

これが、algorithmを使うその場所で、関数オブジェクトの内容を、
新しいlambda式の形で書けるようになりました。素晴らしい!!!

更に、これまでは関数オブジェクトのクラスの中に、
関数オブジェクト内部で使うパラメータのスレッショルド値や、
計算中の最大値の格納場所を用意しないといけなかったのに、
これをlambda式のすぐ外の自動変数を参照することが
できるようになったんですね。これがlambda式の[=]や[&]構文。
unsigned int max = 0;
for_each(vec.begin(), vec.end(),
    [&](unsigned int i){ max = std::max(max, i); });
みたいな。凄えぜ。

さあ、皆もC++98を窓から投げ捨てろ!
明日からC++11だ!

ベクトルレジスタでの位置合わせ (_mm256_alignr_epi8)

引き続き、ベクトルレジスタ同士を結合してシフトの方法など。最後のAVX2編。

AVXでは合わせてシフト命令がなかったので、shuffle命令を使いました。
AVX2でもshuffle命令で操作可能です。

でも、AVX2にはシフト命令が追加されていますのでこれも使えます。
シフト命令は、1バイト単位でシフトできるので、使い方をしっておくと便利そうです。
なので、今回はこれを使います。_mm256_alignr_epi8命令です。

この命令はリンク先の説明の通り、なんか複雑なシフトをします。
何を計算しているかというと、もともと128ビットでやってたシフト処理を、
下位128ビット同士と上位128ビット同士で同時に計算しています。

おお、これは便利と、単純に使ってみると、
__m256 t = _mm256_alignr_epi8(s08, s00, 4);
tには、s01s02s03s08s05s06s07s12みたいなデータが入って(´・ω・`)ガッカリ…

そこで、事前準備をします。
__m256 s04 = _mm256_permute2f128_ps(s08, s00, 3);
__m256 t = _mm256_alignr_epi8(s04, s00, 4);
こうすると、tにはs01s02s03s04s05s06s07s08が入ってばっちりです。

実際に使うには、キャストが必要で、こんな感じですね。
__m256 s04 = _mm256_permute2f128_ps(s08, s00, 3);
__m256 t = _mm256_castsi256_ps(_mm256_alignr_epi8(
        _mm256_castps_si256(s04), _mm256_castps_si256(s00), 4);
また、シフト幅を増やすときは、桁溢れに気をつけてください。
s00 = s00;
s01 = _mm256_alignr_epi8(s04, s00, 4);
s02 = _mm256_alignr_epi8(s04, s00, 8);
s03 = _mm256_alignr_epi8(s04, s00, 12);
s04 = s04;
s05 = _mm256_alignr_epi8(s08, s04, 4);
s06 = ...

みたいな感じになります。

これでシフト処理のintrinsic忘備録お仕舞です。

ベクトルレジスタでの位置合わせ (_mm256_shuffle_ps)

続けて、ベクトルレジスタ同士を結合してシフトの方法、AVX編。

AVXの場合は256ビット長のベクトルレジスタ2つに入ったデータを、
合わせてシフトする命令はありません。

そこでshuffle命令を使います。_mm256_shuffle_psです。

shuffle命令を使うと、2つの256ビット長レジスタから、
1つ目の上位128ビット長のどこか2か所の32ビット2つと、
2つ目の上位128ビット長のどこか2か所の32ビット2つ、
続けて1つ目の下位128ビット長の先程と同じ相対位置の32ビット2つ、
最後に2つ目の下位128ビット長のやはり先程と同じ相対位置の32ビット2つ、
これをまとめて計256ビット長の値を合成できます。
はっきりいって訳わかりません。

分かりやすく書くと、以下の処理でシフトできるわけです。
__m256 x02x03x04x05x06x07x08x09 =
    _mm256_shuffle_ps(
        x04x05x06x07x08x09x10x11,
        x00x01x02x03x04x05x06x07,
        _MM_SHUFFLE(1, 0, 3, 2) /* = 0x4E */);
これを文章で書くと、
まず第2引数の下位128ビットの2番目(8~11バイト)と3番目(12~15バイト)を取り出し、
次に第1引数の下位128ビットの0番目(0~3バイト)と1番目(4~7バイト)を取り、
続けて第2引数の上位128ビットの2番目(24~27バイト)と3番目(28~31バイト)を、
最後に第1引数の上位128ビットの0番目(16~19バイト)と1番目(20~23バイト)を取って、
それを結果の256ビットにまとめる。という処理になります。
Little-endianなので下位ビットが最初のバイトになります。

同様に、今度は1番目(4バイト目)からの256ビット長の値が欲しい場合は、
__m256 x01x02x03x04x05x06x07x08 =
    _mm256_shuffle_ps(
        x02x03x04x05x06x07x08x09,
        x00x01x02x03x04x05x06x07,
        _MM_SHUFFLE(2, 1, 2, 1) /* = 0x99 */);
となります。

最後に、4番目(16バイト目)からの256ビット長の値が欲しい場合は、
__m256 x04x05x06x07x08x09x10x11 =
    _mm256_permute2f128_ps(
        x08x09x10x11x12x13x14x15,
        x00x01x02x03x04x05x06x07,
        3);
となります。_mm256_permute2f128_psを使います。

ここら辺は、面倒臭いので呪文のように覚えるのが吉ですね。

ベクトルレジスタでの位置合わせ (_mm_alignr_epi8、_mm512_alignr_epi32)

こないだいってたフィルター処理のベクトル化の話の続き。

こないだはこういう形で書くとロード回数が減っていいじゃんという話。
ただ、ロード回数を減らすには、256ビット長の2つのレジスタを
くっつけて、自由にシフトできないといけませんでした。

なので今回は、ベクトルレジスタ同士を結合してシフトの方法など。
まずは、簡単なSSEとMICとAVX512から。

SSEの場合は128ビット長のベクトルレジスタ2つに入ったデータを、
MICやAVX512の場合は512ビット長のベクトルレジスタ2つに入ったデータを、
合わせてシフトします。

SSEの場合は、
__m128i s00 = _mm_loadu_si128(&schar[x]);
__m128i s16 = _mm_loadu_si128(&schar[x+16]);
__m128i s01 = _mm_alignr_epi8(s16, s00, 1);
__m128i s02 = _mm_alignr_epi8(s16, s00, 2);
__m128i s03 = ...
という具合。MICやAVX512の場合は、
__m512i s00, s16;
s00 = _mm512_loadunpacklo_epi32(s00, &sint[x]);
s00 = _mm512_loadunpackhi_epi32(s00, &sint[x+16]);
s16 = _mm512_loadunpacklo_epi32(s16, &sint[x+16]);
s16 = _mm512_loadunpackhi_epi32(s16, &sint[x+32]);
__m512i s01 = _mm512_alignr_epi32(s16, s00, 1);
__m512i s02 = _mm512_alignr_epi32(s16, s00, 2);
__m512i s03 = ...
という具合。

簡単ですね!超カンタン!全部がこうならいいのに。

浮動小数点型をシフトしたい場合は、タイプキャストを行います。例えば、
__m512 s01 = _mm512_castsi512_ps(_mm512_alignr_epi32(
    _mm512_castps_si512(s16), _mm512_castps_si512(s00), 1));
となります。

MICやAVX512では、32ビット単位の操作以外はできないので、
もっと細かい処理をするにはなんか別の方法が必要です。
自分は細かい処理しなかったので調べてません。