Rokiのチラ裏

学生による学習のログ

あらゆるSTLコンテナをあらゆるSTLコンテナへ一発変換する(連想配列などを除く)

range adaptorライブラリを書いていて、copiedがあるのだから、集合の種類を指定できても良いだろうと思い、実装する事にした。

以下のように使える。

auto v = boost::irange(0x41,0x47) | srook::adaptors::to_range<std::vector>(); // boost::irangeと併用可能
auto li = v | srook::adaptors::to_range<std::list>(); // リストへ
auto t1 = v | srook::adaptors::to_range<std::tuple,6>(); // タプルへ
auto str = v | srook::adaptors::to_range<std::basic_string>(); // std::stringへ
auto ar = v | srook::adaptors::to_array<std::array,6>(); // std::arrayへ
auto str1 = v | srook::adaptors::to_string<std::wstring>(); // std::wstringへ
auto str2 = v | srook::adaptors::to_string<std::u16string>(); // std::u16stringへ
auto str3 = v | srook::adaptors::to_string<std::u32string>(); // std::u32stringへ

tupleに対する特殊化がされており、再起によって元集合のデータを取り出すようにしている。このコードのみではlistとarrayに対応できていない。まあ直ぐ書けるのだが、余談を挟むと、色々とこれ以外にも作業をしていたら、執筆段階で朝の4時となってしまったので一旦休眠し、後日書くことにした。

上記の通り対応した。arrayのテンプレートパラメータを特殊化させる事は不可能なため、別途設けているが何か良い方法はないだろうか。それと、to_rangeにbasic_stringを渡して文字列を生成する事も上記の通り可能ではあるが、chatTを指定する術がないため、必ずstd::stringを返すようにしている。よってこの書き方だとwstirng,u16string,u32stringに対応できない。そのために、一応、to_stringも設ける事にした。using namesapce std;using namespace srook::adaptors;を行うとstd::to_stringと名前が被りそうな雰囲気が漂うが、オーバーロード解決により正しく呼び出される。

C++14までの標準規格実装に準ずる厳密なhas_iterator

void_t detection idiomのエントリなどから何度か当ブログで登場している、イテレータを持つtypeであるかを判別するメタ関数has_iteratorであるが、これまでのエントリでは以下のような実装を示してきた。

template<class,class=std::void_t<>>
constexpr bool has_iterator_v=std::false_type::value;

template<class T>
constexpr bool has_iterator_v<T,std::void_t<typename T::iterator>> = std::true_type::value;

しかし、このメタ関数はiteratorクラスを継承したイテレータを持たないクラスに対して、誤った判定結果を返す。下記のコードではstatic_assertがfireして欲しいところだが、残念ながらコンパイルが通ってしまう。

static_assert(has_iterator_v<std::back_insert_iterator<std::vector<int>>>); // passing... ?!

std::back_insert_iteratoriteratorクラスを継承している。back_insert_iterator自体はiteratorであるが、iteratorを持っているわけではないので、この判定結果は間違いである。この問題を回避するためには、iteratorというエイリアスが見えているのか、それともiteratorという生のtypeが見えているのか(この場合、継承関係を調査する)を判定すれば良い。(コンパイラごとに分岐するマクロが記述されている理由は当エントリの追記に書かれている。)

表題として、C++14までのとしているのは、C++1zではiteratorクラスがdeprecatedとされる予定*1だからだ。しかし新たにC++1z制定以降に世間で書かれるコードがiteratorクラスを使わないにしても、今まで使われてきた標準ライブラリがどのように実装されるのか(そのままの可能性も大いに考えられる)は不明であるので、依然として対策は必要である。

追記

当エントリの内容は、比較的有名なコンパイラー(ClangやGCC)のバージョンごとで誤った解釈がされる可能性がある。

実際に処理内容として正しいコードは上記にあるものだが、コンパイラのバージョンによっては対応しなければならないかもしれない。 原因は今の所不明であるが、ライブラリ実装の問題ではなくコンパイラの解釈自体に問題があるのではないかと私は踏んでいる。

Intel Code Modernization 2017に参加した

Intel Code Modernization 2017に参加した。Intel流のParallel Computingについての情報やそれに対する知見を得た。その際に個人的に印象深かったAVX512命令であるが、当然ながら今現在でその命令に完全対応したコンシュマー向けのアーキテクチャは存在しない*1。 しかし、エミューレータがあるというお話だったので、手元のマシンでも命令を動かして見る事にした。*2

現代のマシンでAVX512命令をエミュレートする遊び

エミュレータWindowsLinuxに対応しているようだが、Microsoft側の閉鎖的な開発コミュニティによってIntel側のライブラリの開発が順調でないという旨をセミナーで聞いたので、やはりLinux上で動かすのが好ましいだろう。カーネルの改変なども当然ながらLinux上でなければ不可能であるし。

環境

% uname -a
Linux roki-VB 4.8.0-27-generic #29-Ubuntu SMP Thu Oct 20 21:03:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

% cat /proc/cpuinfo 
processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 69
model name  : Intel(R) Core(TM) i5-4308U CPU @ 2.80GHz
stepping    : 1
cpu MHz     : 2800.000
cache size  : 3072 KB
physical id : 0
siblings    : 1
core id     : 0
cpu cores   : 1
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq monitor ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm
bugs        :
bogomips    : 5600.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual

準備

まずLicense Agreement to Download | Intel® Softwareからエミュレータを、GCC for Intel AVX-512, Intel MPX and Intel SHA extensionsからGCCGDBをダウンロードする。

% mkdir $HOME/mpx
% export MPX_HOME="$HOME/mpx"
% cd $MPX_HOME
% tar xvf ../sde-external-7.49.0-2016-07-07-lin.tar.bz2
% tar xvf ../2014-02-13-mpx-runtime-external-lin.tar.bz2
% tar xvf ../binutils-gdb_install_2.24.51.20140422.tar.gz
% tar xvf ../gcc_5.0.0-mpx-r214719-src.tar.gz
% mv sde-external-7.49.0-2016-07-07-lin.tar.bz2 $MPX_HOME
% mv 2014-02-13-mpx-runtime-external-lin.tar.bz2 $MPX_HOME
% mv binutils-gdb_install_2.24.51.20140422.tar.gz $MPX_HOME

GCCのビルド。とりあえずconfigureを走らせると、必要なライブラリなどがあれば止まるのでそれ頼りで。インストールは$HOME/local、3 stage buildはdisable。

% cd gcc_install_5.0.0-mpx-r214719
% mkdir objdir && cd objdir
% ./configure --prefix=$HOME/local --disable-bootstrap
...
configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+.
Try the --with-gmp, --with-mpfr and/or --with-mpc options to specify

ということでエラー通りにライブラリを導入する。

% mkdir gmp mpfr mpc
% cd gmp && wget ftp://ftp.gmplib.org/pub/gmp/gmp-5.1.3.tar.bz2 && tar xf gmp-5.1.3.tar.bz2 && rm *.tar.bz2 && mv gmp-5.1.3/* ./
% cd ../mpfr && wget http://www.mpfr.org/mpfr-current/mpfr-3.1.5.tar.gz && tar xf mpfr-3.1.5.tar.gz && rm *.tar.gz && mv mpfr-3.1.5/* ./
% cd ../mpc && wget http://www.multiprecision.org/mpc/download/mpc-1.0.3.tar.gz && tar xf mpc-1.0.3.tar.gz && rm *.tar.gz && mv mpc-1.0.3/* ./

さて、再度configureを実行してビルド、インストールする。

% ./configure --prefix=$HOME/local --disable-bootstrap
% make
% sudo make install

そしてパスを通す。

% export SDE_KIT=$MPX_HOME/sde-external-7.49.0-2016-07-07-lin
% export MPX_RUNTIME_LIB=$MPX_HOME/2014-02-13-mpx-runtime-external-lin
% export MPX_BINUTILS=$MPX_HOME/binutils-gdb_install_2.24.51.20140422
% export MPX_GCC=$HOME/local

動かす

コードは以下のような感じ。まあ内容は、512ビットのデータを32ビットの整数に16分割したデータを2つ用意し、そいつらをAVX512を利用して各々のデータを加算するといったもの。

#include<iostream>
#include<iterator>
#include<cstdint>
#include<boost/range/algorithm_ext/iota.hpp>
#include<boost/range/algorithm/for_each.hpp>
#include<boost/range/algorithm/copy.hpp>

namespace{ constexpr std::size_t S=16; }

std::uint32_t a[S] __attribute__((aligned(64)))={};
std::uint32_t b[S] __attribute__((aligned(64)))={};
std::uint32_t result[S] __attribute__((aligned(64)))={};

inline void init()
{
    boost::range::iota(a,0);
    boost::range::iota(b,0);
    boost::range::for_each(b,[](std::uint32_t& x){return x*=100;});
}

inline void print()
{
    boost::copy(b,std::ostream_iterator<std::uint32_t>(std::cout," "));
}

int main()
{
    init();

    __asm__ (
            "vmovdqa32 (%0), %%zmm0;"
            "vpaddd (%1),%%zmm0,%%zmm0;"
            "vmovdpa32 %%zmm0, (%2);"
            ::"a"(a), "b"(b), "c"(result)
        );
    print();
}

__asm__中のvmovdqa32vpadddがAVX512命令である。さて、これを例えば何もコンパイルオプションを指定せずにコンパイルすると以下のようなエラーが出力される。

t.cpp:31:4: error: instruction requires: AVX-512 ISA
                        "vmovdqa32 (%0), %%zmm0;"
                        ^
<inline asm>:1:2: note: instantiated into assembly here
        vmovdqa32 (%rax), %zmm0;vpaddd (%rbx),%zmm0,%zmm0;vmovdpa32 %zmm0, (%rcx);
        ^
t.cpp:31:4: error: instruction requires: AVX-512 ISA
                        "vmovdqa32 (%0), %%zmm0;"
                        ^
<inline asm>:1:26: note: instantiated into assembly here
        vmovdqa32 (%rax), %zmm0;vpaddd (%rbx),%zmm0,%zmm0;vmovdpa32 %zmm0, (%rcx);
                                ^
t.cpp:31:4: error: invalid instruction mnemonic 'vmovdpa32'
                        "vmovdqa32 (%0), %%zmm0;"
                        ^
<inline asm>:1:52: note: instantiated into assembly here
        vmovdqa32 (%rax), %zmm0;vpaddd (%rbx),%zmm0,%zmm0;vmovdpa32 %zmm0, (%rcx);
                                                          ^~~~~~~~~
3 errors generated.

まあ当然なのだが、エラー文からも正しくAVX512命令を下せている事が確認できる。さて、オプションを指定して、動かす。

% $MPX_GCC/bin/g++ -fcheck-pointer-bounds -mmpx -L$MPX_RUNTIME_LIB -B$MPX_BINUTILS/bin -lmpx-runtime64 -Wl,-rpath,$MPX_RUNTIME_LIB -std=c++1z t.cpp

と思いきや、sys/cdefs.hがないという旨が。libc6-dev-i386というパッケージに含まれているので、インストール。

% sudo apt install libc6-dev-i386

再度実行。

$ sde64 -- ./a.out
0 101 202 303 404 505 606 707 808 909 1010 1111 1212 1313 1414 1515

...という事である程度遊んでみたわけだが、実際の実機でこの命令を動かせるようになるのは、一体いつになるのやら。

Xeon Phiと最適化に纏わるコンパイラなどの情報

本セミナーで得たXeon Phiの特徴とそれを活かしたコードを動かすためのコンパイラ対応情報など。 以下に示されている内容は本セミナーで私がヒアリングした内容を書き出したものなので、厳密な情報を求める場合は規格書や公式のリファレンスガイドなどを参照すべし。

  • インテルXeon Phi プロセッサーが装備されたマシンでは一般的なOSを起動可能(セミナーでは一般的なCentOSが動いていた)
  • Xeon Phiは、3つのMCDRAM(Xeon Phi内蔵)メモリーモードを起動時にBIOS設定に準じて選択する
      * キャッシュモード:自動的な利用。排他制御などはOS側管理
      * フラットモード:MCDRAMが見える。それを使い分けるために、実行時またはプログラムコード内でその旨の最適化?attribute的なものを書く必要がある
      * ハイブリットモード:上記二つの両方。一部を割り当て可能なメモリとしたり。
    
  • 様々なクラスターモード
      * sub-NUMA Clusteringを除くすべてはソフトウェア側から見えない(自動制御)
      * sub-NUMA Clusteringでは明示的に記述する(NUMA対応)
    
  • コンパイラ対応
      * インテルコンパイラ 15.0以降
      * GCC 4.9.1(binutils 2.24)以降
          * option:-mav512f -mavx512cd -mavx512er -mav512pf
    
  • 競合検知拡張命令(AVX512-CD)はXeon 6から。コンパイラは対応している。
  • MCDRAMを利用するには:
      * コマンドによる割り当て、AutoHBWライブラリによる半自動化
      * memkind ライブラリで利用
    

関連ツイート

Intel Code Modernization 2017は30人前後の収容人数で、小休憩を10分挟んで合計2時間程、最新のIntelプロセッサー、Xeon Phi搭載マシンの情報と、それに対する適したプログラミングスタイルを説明するといった内容。個人的には、スライド資料がとてもよく纏まっており、分かりやすく感じた。*3

最後のツイートの書籍は、近頃日本語訳された書籍*4が販売されるそうだ。折角無償で頂いたので、英書籍を読む事にする。

*1:Xeon Phiでは動く

*2:MCDRAMのメモリーモードごとの挙動の変化なども自分の手で動かして遊んで見たいところだが、流石に実機が必要なので、残念ながら不可能。

*3:紙媒体でスライド資料を貰えるので、後に見直す事ができる。

*4:8k前後?のお値段

swap of variadic templates packages

オレオレライブラリ、Srook C++ Librariesでは、Variadic templateを端的なpackで型情報を保持している。

// simply pack implemention
template<class...>
struct pack{};

かの有名なAndrew Koenig氏は、swapは基本的な操作であると述べている

よって、Variadic templateのpack操作にもswapが必要であるので書く事にした。実装は短く、とても簡単。

尚、本エントリでは、型情報の出力に一々boost::typeindex……と記述するのも怠い冗長であるので以下の関数を用いて実行結果を検証する。

#include<boost/type_index.hpp>
#include<iostream>

template<class OutputType>
void print(OutputType&&)
{
    using namespace boost::typeindex;
    std::cout<< type_id< std::decay_t<OutputType> >().pretty_name() <<std::endl;
}

さて、肝心なSwapAtは以下のように使う。

#include<srook/mpl/variadic_player.hpp>

using tp1=
    srook::mpl::make_some_pack<
        srook::mpl::pack_setting<int,2>,
        srook::mpl::pack_setting<float,3>
    >;
using tp2=
    srook::mpl::make_some_pack<
        srook::mpl::pack_setting<char,2>,
        srook::mpl::pack_setting<double,3>
    >;

// tp1とtp2のindex 1をswapしたtp1の結果
using swaped_L=srook::mpl::SwapAt_L<tp1,tp2,1>;
// tp1とtp2のindex 2をswapしたtp2の結果
using swaped_R=srook::mpl::SwapAt_R<tp1,tp2,2>;

print(swaped_L());
print(swaped_R());
srook::mpl::v1::pack<int, char, float, float, float>
srook::mpl::v1::pack<char, char, float, double, double>

単に型同士を完全にSwapするという操作は無意味である*1ため、パックの特定のインデックス同士をSwapする実装となっている。

また、上記のSwapでは、お互いに同じインデックス値の型を交換する事となるが、相違なるインデックス値でSwapしたい時もあろう。そのような時のためにSwapAt_Specifiedを書いた。

今後の機能拡張などの可能性を踏まえた上で、若干複雑な実装となっているが本質的にはとても簡単な操作である。以下のように使える。

#include<srook/mpl/variadic_player.hpp>

using tp1=srook::mpl::pack<char,int,float>;
using tp2=srook::mpl::pack<double,bool>;

// tp1のindex 0とtp2のindex 1をswapしたtp1の結果
using swaped_L=srook::mpl::SwapAt_Specified_L<tp1,0,tp2,1>;
// tp1のindex 0とtp2のindex 1をswapしたtp2の結果
using swaped_R=srook::mpl::SwapAt_Specified_R<tp1,0,tp2,1>;

print(swaped_L());
print(swaped_R());
srook::mpl::v1::pack<bool, int, float>
srook::mpl::v1::pack<double, char>

快適なSwapライフを。

*1:オブジェクトのように、パックの中身がデータとして変動するわけではないため

Amazon dash buttonでmotionのオンオフ

動機

以前頂いたVostro 1540の使い道をこれまで色々と試行錯誤した結果、自宅内のLANを用いた様々な用途でのサーバーとして稼働させるという方向性で落ち着いた。最近はその用途の一つとして、motionという動体検知ユーティリティツールを用いた自宅内の監視カメラを一任させてみている。実際のカメラは自宅の中心部分に堂々と置いてある。自宅全体を監視するためだ。しかしそのために、普通にカメラの目の前を私が生活の中でよく通りまくるので、その度に一々動体検知されて、録画を開始してしまう。これは動体検知のレベルの調節で解決できるわけではない。監視カメラとしての役割と果たして欲しいタイミングというのは、誰も自宅にいない時のみである。そこで、玄関あたりに今流行りのAmazon dash buttonを設置して、そこからmotionのプロセスを開始させたり殺したりできれば、無駄な稼働を省く事ができるはずである。

まとめ

題名はmotionのオンオフとなっているが、本質的にやっている事は単に同LAN上に存在するマシンに対して、ボタンを押下した場合に特定のコマンドを実行するという事だけである。

環境

作業

まずnode.jsを導入

$ sudo apt-get install -y nodejs npm
$ sudo npm cache clean
$ sudo npm install n -g
$ sudo n stable
$ sudo ln -sf /usr/local/bin/node /usr/bin/node
$ sudo apt-get purge -y nodejs npm

作業ディレクトリを生成してその中でプロジェクトの生成。例の如くdash-buttonを使う。

$ mkdir camera_motion_project && cd camera_motion_project
$ sudo apt-get install libpcap-dev
$ npm init
$ npm install --save dash-button jxa slack-node

package.jsonの"script"を以下のように変更する。

{
  "scripts": {
    "scan": "dash-button scan"
  }
}

Amazon dash buttonのMACアドレスを取得する。

$ sudo npm run scan

とし、セットアップ済みのamazon dash buttonを押下するとMACアドレスが出力される。あとは dash-buttonにある通りjsファイルを記述すれば良い。

動作

それにしても、便利な時代になったものだ。

追記

dash buttonを押してからしっかりとオンオフがされているかは、ログを目視しなければ確認出来ず、不自由に感じたため、音声でオンオフの処理を流す事にした。また、オンの時にmotion.confから任意の設定を読み込んでそれを読み上げさせる事にした。読み上げには、まず標準出力にmotion.confの任意の内容を出力させて、それをespeakというソフトウェアの引数にする事にした。 よってまずは、espeakを入手しなければならない。

$ sudo apt-get install espeak

そして、任意の設定内容を標準出力させる。

これで、dash buttonのオンオフと共に、その旨の音声と、起動時には設定内容の一部(任意)が読み上げられるようになった。

C++11でのムーブキャプチャをシミュレート

C++11の文法内ではラムダ式の構文でムーブする事はできない。

auto ptr=std::make_unique<int>(0);
[a=std::move(ptr)](){}(); // From C++14

ググると様々な方法(コピーキャプチャした際にムーブを行わせるラッパー等)が検討されている。その中の1つの方法として、以下にstd::bindへrvalueを渡す方法を提唱する。

std::unique_ptr<int> ptr(new int);
std::bind([](const std::unique_ptr<int>&){},std::move(ptr))();

ptrstd::bindの生成するクロージャ内にムーブされる。クロージャが実行されると、内包する実引数が元々指定されたクロージャに渡される。この時、C++14と同じラムダを生成するという事は、クロージャクラスのメンバ関数operator()がconstであるという事であるから、const lvalue referenceとしなければならない。同じように、mutableなクロージャ(以下例)

[a=std::move(ptr)]()mutable{}();

であれば、lvalue referenceとしなければならない。

std::bind([](std::unique_ptr<int>&)mutable{},std::move(ptr))();

同じ型が入った std::tuple を楽に書きたい

同じ型が入った std::tuple を楽に書きたい - Qiitaというようであれば、オレオレライブラリsrook.mpl.variadic_playerでも、より簡単に記述できますよ〜、という紹介。

#include<srook/mpl/variadic_player.hpp>
#include<boost/type_index.hpp>
#include<iostream>
#include<tuple>

int main()
{
    using int6_tuple_type = // int型が6つのタプル
        srook::mpl::Transfer_t<
            std::tuple,
            srook::mpl::make_pack<int,6>
        >;
    using int3_char4_float2_tuple_type = // int型が3つ、char型が4つ、float型が2つのタプル
        srook::mpl::Transfer_t<
            std::tuple,
            srook::mpl::make_some_pack<
                srook::mpl::pack_setting<int,3>,
                srook::mpl::pack_setting<char,4>,
                srook::mpl::pack_setting<float,2>
            >
        >;

    std::cout<< boost::typeindex::type_id< int6_tuple_type >().pretty_name()<<std::endl;
    std::cout<< boost::typeindex::type_id< int3_char4_float2_tuple_type >().pretty_name()<<std::endl;
}
std::__1::tuple<int, int, int, int, int, int>
std::__1::tuple<int, int, int, char, char, char, char, float, float>

実装は以下のように楽チン&簡単。