2018年6月9日土曜日

boost::compute テスト

ちょいと GPU 使ってみようかと思って OpenCL について調べました。macbook pro retina MID2012 が壊れて、GPU が Intel Iris しか搭載されてないものに買い替えたため、Cuda は選択肢に無くなったんです。データセンターで使うな騒動もあったし、Cuda は、もうええんですわ。
そしたら、boost::compute というのがあって、OpenCL を使えるらしいのです。
チュートリアルを macbook pro でコンパイルしてみたら動く感じだったんで、本当に速いのかチェックしてみました。

コンパイルは、brew install boost してある状態で、こんなん。
g++ tutorial3.cpp -I /usr/include -framework OpenCL -std=c++11 -lboost_timer
チュートリアルを少し弄ってあります。
#include <vector>
#include <algorithm>

#include <boost/compute/algorithm/transform.hpp>
#include <boost/compute/container/vector.hpp>
#include <boost/compute/functional/math.hpp>

#include <iostream>
#include <cmath>
#include <boost/timer/timer.hpp>

namespace compute = boost::compute;

int main()
{
    // get default device and setup context
    compute::device device = compute::system::default_device();
    compute::context context(device);
    compute::command_queue queue(context, device);

    std::vector<float> host_vector(100000000);

    boost::timer::cpu_timer timer;

    float n = 1.0f;
    std::generate(host_vector.begin(), host_vector.end(), [&n]() { return n += 1.0f; });
    // generate random data on the host
    //std::generate(host_vector.begin(), host_vector.end(), rand);

    timer.start();

    // create a vector on the device
    compute::vector<float> device_vector(host_vector.size(), context);

    // transfer data from the host to the device
    compute::copy( host_vector.begin(), host_vector.end(), device_vector.begin(), queue );

    // calculate the square-root of each element in-place
    compute::transform(
        device_vector.begin(),
        device_vector.end(),
        device_vector.begin(),
        compute::sqrt<float>(),
        queue
    );

    // copy values back to the host
    compute::copy( device_vector.begin(), device_vector.end(), host_vector.begin(), queue );
    
    timer.stop();
    
    std::cout << "GPU: " << timer.format() << std::endl;

    n = 1.0f;
    std::generate(host_vector.begin(), host_vector.end(), [&n]() { return n += 1.0f; });
    
    std::sqrt<float>(n);
    
    timer.start();
    std::transform( host_vector.begin(), host_vector.end(), host_vector.begin(), static_cast<float (*)(float)>(std::sqrt));
    timer.stop();
    std::cout << "CPU: " << timer.format() << std::endl;
    
    //for( float x: host_vector) { std::cout << x << ","; }

    return 0;    
}
元は 10000 個の配列演算だったんですが、それだと
GPU:  0.078395s wall, 0.000000s user + 0.000000s system = 0.000000s CPU (n/a%)
CPU:  0.000097s wall, 0.000000s user + 0.000000s system = 0.000000s CPU (n/a%)
メモリ転送の処理に時間がかかって効果を確認できず。
100000000 個の配列演算で比較すると
GPU:  0.367576s wall, 0.000000s user + 0.170000s system = 0.170000s CPU (46.2%)
CPU:  0.819997s wall, 0.820000s user + 0.000000s system = 0.820000s CPU (100.0%)
ようやく効果が確認できました。

追記: host_vector size が 1000000000 -> 100000000 に訂正しました

2018年6月4日月曜日

android studio gradle compile 備忘録

どこかに書き留めないと、また調べる事になりそうなので、備忘録。

ある日突然、AndroidStudioでビルドをしようとすると、こんなメッセージが出てビルドできない。
Configuration 'compile' is obsolete and has been replaced with 'implementation' and 'api'.
It will be removed at the end of 2018. For more information see: http://d.android.com/r/tools/update-dependency-configurations.html

はい、compile は deprecated になり、implementation コマンドに変更されました。
以下のように書き換える必要があります。
dependencies {
    //compile files('../../Ref/Java/android-support-v4.jar')
    //compile 'com.android.support:support-v4:18.0.0'
    implementation 'com.android.support:support-v4:18.0.0'

    //compile files('../../Ref/Java/foo.jar')
    implementation fileTree(dir: '../../Ref/Java', include: 'foo.jar')
    //compile files('../../Ref/Java/bar.jar')
    implementation fileTree(dir: '../../Ref/Java', include: 'bar.jar')
}

2018年4月15日日曜日

tiny-dnn で遊ぼ (AutoEncoder)

#include "tiny_dnn/tiny_dnn.h"

namespace td = tiny_dnn;

#include "AudioFile.h"

#include <vector>
#include <random>

//Some traning variable 
const int SAMPLE_NUM = 1024;   //How much traning samples
const int WINDOW_SIZE = 2048;  //How long is each sample
const int BATCH_SIZE = 16;     //How many samples per batch
const int NUM_EPOCHS = 2048;   //How much epoch we want to run

int main()
{

  //Create a sample buffer
  std::vector<td::vec_t> samples(SAMPLE_NUM, td::vec_t(WINDOW_SIZE));

  AudioFile<float> audioFile;
  audioFile.load("a.wav"); //Load audio
  auto& data = audioFile.samples[0]; //Just use the first channel

  //Create a RNG and a distribution to generate random numbers
  std::mt19937 rng;
  std::uniform_int_distribution<int> dist(0, data.size()-WINDOW_SIZE);
  //Generate samples
  for(auto& v : samples)
  {
    int offset = dist(rng);//Generate random offsets
    auto start = data.begin() + offset;
    //Copy data from source to sample
    std::copy(start, start+WINDOW_SIZE, v.begin());
  }
  
  //Create an autoencoder
  td::network<td::sequential> net;
  net << td::fully_connected_layer(WINDOW_SIZE, 512) << td::tanh_layer()
    << td::fully_connected_layer(512, WINDOW_SIZE);
  
  //Helper class
  td::progress_display disp(SAMPLE_NUM);
  td::timer t;
  
  int currentEpoch = 0;
  
  //Callbacks when a mini bactch is done
  auto onMinibatch = [&]()
  {
    //This updates the progress display
    disp += BATCH_SIZE;  
  };
  
  //Callbacks when an epoch is done
  auto onEpoch = [&]()
  {
    std::cout << "Epoch " << ++currentEpoch << "/" << NUM_EPOCHS << "done. "
      << t.elapsed() << "s elapsed." << std::endl;
      
    //Reset progress display and timer
    disp.restart(SAMPLE_NUM);
    t.restart();
  };
  
  //train the network with absolute(L1) error.
  td::adagrad optimizer;
  net.fit<td::absolute>(optimizer, samples, samples, BATCH_SIZE, NUM_EPOCHS
    , onMinibatch, onEpoch);
  net.save("net");

  //Let's try the network
  std::vector<float> result(data.size());
  for(int i=0;i<data.size();i+=WINDOW_SIZE)
  {
    //Input to the neural network
    td::vec_t input(WINDOW_SIZE);
    //copy data into the input vector
    std::copy(data.begin()+i, data.begin()+i+WINDOW_SIZE, input.begin());

    //Run the neural network then copy it to the result buffer
    td::vec_t predict = net.predict(input);
    std::copy(result.begin()+i, result.begin()+i+WINDOW_SIZE, predict.begin());
  }
  
  //Save the audio we ganarated
  AudioFile<float> saveFile;
  AudioFile<float>::AudioBuffer buffer(1);
  buffer[0] = result;
  audioFile.setAudioBuffer(buffer);
  audioFile.save("audioFile2.wav");
}
コンパイル
g++ auto_encoder.cpp -I . -std=c++14 AudioFile.cpp
実行
...

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************
Epoch 336/2048done. 40.7405s elapsed.

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************
Epoch 337/2048done. 42.2788s elapsed.

...
wavファイルによると思うが、3日ぐらいかかりそうな勢い

tiny-dnn で遊ぼ (XOR)

チュートリアル XOR
#include <tiny_dnn/tiny_dnn.h>
#include <vector>

namespace td = tiny_dnn;
namespace tda = tiny_dnn::activation;

int main() {
  td::network<td::sequential> net;
  net << td::fully_connected_layer(2,3) << td::sigmoid_layer()
  << td::fully_connected_layer(3,1) << td::sigmoid_layer();

  std::vector<td::vec_t> trainIn = {{0,0}, {0,1}, {1,0}, {1,1}};
  std::vector<td::vec_t> trainOut = {{0}, {1}, {1}, {0}};

  td::gradient_descent optimizer; //(0.53);
  optimizer.alpha = 0.53f;
  net.fit<td::mse>(optimizer, trainIn, trainOut, 1, 1000);
  net.save("net");
  std::cout << net.predict({0,0})[0] << std::endl;
  std::cout << net.predict({0,1})[0] << std::endl;
  std::cout << net.predict({1,0})[0] << std::endl;
  std::cout << net.predict({1,1})[0] << std::endl;

  return 0;
}
出力
0.0411867
0.956243
0.959534
0.037655

2017年12月19日火曜日

Windows 環境変数適用 備忘録

環境変数を値に置き換える
#include <boost/regex.hpp>
#include <string>
#include <iostream>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

std::string getEnvValue(const std::string& str) {
  std::string result;
  std::string inp = str.substr( 1, str.size() - 2 );
  //std::cout << inp << std::endl;
  char buf[ 4096 ];
  DWORD sz = sizeof(buf);
  if( GetEnvironmentVariable( inp.c_str(), buf, sz ) > 0 ) {
    result = buf;
  }
  return result;
}

bool replaceEnv(std::string& str) {
  boost::regex r("(%[a-zA-Z0-9_]+%)");
  boost::smatch m;
  if( boost::regex_search(str, m, r) ) {
    std::string eval = m.str(1);
    std::string sval = getEnvValue( eval );
    while( std::string::npos != str.find(eval) ) {
      str.replace( str.find(eval), eval.size(), sval );
    }
    return true;
  }
  return false;
}

int main(int argc, char* argv[]) {
  std::string str = "%USERPROFILE%\\hoge\\fuga";
  while( replaceEnv( str ) ) {}
  std::cout << str << std::endl;

  

  return 0;
}

2017年12月5日火曜日

MSVCを使ったOSSのビルドについて

C++アドベントカレンダー2017のDAY5の記事になります。


皆さんOSS(オープン・ソース・ソフトウェア)のビルド、どうしてますか?
Linux 系だとパッケージメンテナーが存在していて、apt get コマンドや yum コマンド一発でビルドされたライブラリが取得できますし、プラットフォームが更新されれば、update をかませば一括してライブラリもそれに合わせて更新されます。
それに大抵は ./configure し、make し、make install で済んでしまいます。

Windows 環境でOSSを利用したい時、どうしてますか?
僕は毎回ソースをダウンロードしてから、install.txt や readme.txt やサイトを検索してビルド方法を試行錯誤していました。一度ビルドしてしまえば、当面ビルドの必要無いですしね?
現状を振り返ってみて、気がついてみれば、結構いろんなOSSを利用していました。
それに、会社ではサポートしなければならないOS等の関係もあり、長らくVisual Studio 2005を使っていました。情勢が変わって、Windows 7 の期限も近づいており、さすがに Visual Studio 2010 に切り替えました。これを契機にOSSのビルドを自動化して時間の効率化を図ろうと決意しました。

 ちょっと脱線しますが、自動化しようと思った動機について、もう少し書こうと思います。

 ひとつは、MSの提供するライブラリよりもOSSに依存した方が安全だと思った事です。ISAPI が C++でサポートされなくなったり、Regex系のライブラリが消えたり、Web系のC++ライブラリがサポートされなくなったり。特にSoapを使った独特のIDL言語を使った機構がサポートされなくなったのは、かなりの痛手です。MSのライブラリに依存することは、危険です。使いやすいからWTLでアプリを組んでいましたが、リモート・アプリケーションでのプリンタが認識できない問題で、印刷コモンダイアログを自作する羽目になりました。幸いWTLは、まだメンテナンスが行われており、Issues 等を提案すると取り込んでもらえる状況なのが救いです。Regex系のライブラリが消えた時は、移植で死にそうになりました。Soapがサポートされなくなった時は、MSはC#が大切でC++なんか気にしちゃいないと実感しました。

 もうひとつは、自社で開発している既存のソフトウェアを32bit環境から64bit環境へ移行する目論見も出てきました。移行する時にOSSライブラリを個々に調べてビルドしていたら大変です。

 最後は、あわよくばプルリクエストをもらって楽しよう共闘できると思ったからです。そんなプロジェクト、どこにも無いですし、それなりに価値があるのではないかと思っています。

そうやって作成したのが、こちら
build-oss-library-on-windows
です。

 自動化するにあたっては、いろいろと躓きました。Visual Studio の IDE を使ってコンパイルしなければならないのが面倒で調べたところ、msBuild というコマンドで IDEのプロジェクトをビルドできる事が分かりました。ところが、このIDEはバージョン依存で正しいビルドツールを指定しないとエラーでビルドできません。正しいビルドツールの番号、気が狂ってます。Visual C++ のバージョンを判定するバッチファイルの修正履歴を見ていただければ、わかると思いますが、現実は予想の斜め上を行ってました。信じらない事に Microsoft からVisual C++のバージョンに関する公式の表がどこにも掲載されていません
Visual Studio 製品名VC通称ツールセットのバージョンコメント
Visual Studio 2003VC7v70
Visual Studio 2005VC8v80
Visual Studio 2008VC9v90
Visual Studio 2010VC10v100
Visual Studio 2012VC11v110
Visual Studio 2013VC12v1202015がVC14だからVC12で合ってるか調べる羽目になったやんけ(#^ω^)ピキピキ
Visual Studio 2015VC14v140なんで一個バージョン飛んでるねん?
Visual Studio 2017VC15v141v141って殺すぞ何ぞ?(^ω^)
また、*.sln プロジェクトファイルは、それぞれのバージョンに合わせなければなりません。幸い devenv /upgrade でコマンドラインからアップグレードできるのが救いです。

 今のところ Visual Studio 2010 win32 プラットフォームでしかビルド検証されていない状況ですが、ドキュメントを参考に是非、利用してみてください。特に自前で、コツコツと毎回ビルドされている方には使ってみて欲しいです。時間の節約になると思います。そして共闘しましょう٩( 'ω' )و

 最後に…
安全という名の緊縛の言語 Java氏ね Java氏ね Java氏ね 

追記:コメント頂いた。vcpkg にて、凄い勢いでビルドパッケージが構築されているので、Visual Studio 2015 以上を使う場合には、vcpkg を利用した方が良さそうです。
尚、Visual Studio を巡るバージョン情報は、@yumetodo さんにより更新されております(これ、凄い労力だな…)


2017年8月2日水曜日

Remote Desktop と Default Printer をめぐるバグ

世相を反映して、近年 Remote Desktop でアプリケーションを動作させる機会が増えました。
Remote Desktop App として、アプリケーションを実行すると、印刷ダイアログにて、デフォルトプリンタが取得できない。他のアプリでは出来ているから直せと言われたのが発端でした。

 デフォルトプリンタの取得について調べてみました。自分のアプリケーションでは WTL(Windows Template Library)を利用しています。デフォルトプリンタの取得には、GetProfileString という関数が使用されていました。

 このGetProfileStringを使ってデフォルトプリンタを取得する方法、一昔前においてはデファクトスタンダードでしたが、現在では GetDefaultPrinter というWindows API が提供されており非推奨です。特に Remote Desktop 環境で GetProfileString を使用すると正しくデフォルトプリンタを取得できません。

 デフォルトプリンタの取得方法には、EnumPrinters という Windows API を利用する方法がありました。この記事を書いた時点では、MSDN日本語訳 と MSDN英語 のドキュメントにも相違があり混沌としています。新しいドキュメントからは PRINTER_ENUM_DEFAULT というフラグの記述が消えており、GetDefaultPrinter API を使用しろとあります。

 以上から、WTL の OpenDefaultPrinter 関数を GetDefaultPrinter API を使って書き直しました。

 しかし、Remote Desktop App として、アプリケーションを実行すると、依然としてローカルのデフォルトプリンタが取得できない現象が発生しました。奇妙な事に、セッションを維持する設定にすると、セッションが保たれている状況では正しくローカルのデフォルトプリンタが取得できるのです。

 このバグが発生するシナリオは、以下のようなものでした。

   自分が書いた アプリケーション(COM Control) では、インスタンス生成時に、ページ設定ダイアログのインスタンスを生成しています。このダイアログ生成時に、デフォルトプリンタの取得も行われていました。

  1. Remote App 開始
  2. Remote Destop Session 開始
  3. ページ設定ダイアログ生成
  4. デフォルトプリンタ取得(call GetDefaultPrinter)
  5. この時点では Remote Desktop Session 内にローカルプリンタの情報が揃っていません。
  6. Remote Server 内のデフォルトプリンタが返される。
  7. Remote Desktop Session がローカルプリンタの情報を取得
ですので、Remote Desktop Session が既に接続済みな状態で Remote App を開始すると、GetDefaultPrinter はローカルプリンタのデフォルトプリンタを返すのです。

 対処法としては、自分が書いたアプリケーションにおいて、ページ設定ダイアログの生成を使われる直前まで先延ばしする事でした。

疑似コードは、以下のような感じです。
  CCustomPageSetupDialog* getPrintDialog() {
    if( NULL == print_dialog_ ) {
       print_dialog_ = new CCustomPageSetupDialog( this, ... );
    }
    return print_dialog_;
  }