Cのプログラムをx86-64のマシン上でPowerPC向けにコンパイルする

x86-64のマシン + Ubuntu 16.04で、PowerPC 64bit向けにC言語のプログラムをコンパイルする方法を紹介します。俗に言うクロスコンパイルっていうやつです。

以下のように、必要なものをaptで入れます。binutilsにはアセンブラやローダが入っています。

$ sudo apt install gcc-powerpc-linux-gnu binutils-powerpc-linux-gnu

これで環境は整ったので、実際に動かしてみます。まず、以下のファイルをa.cとして保存します。

int fact(int n) {
  if (n == 1) return 1;
  return n * fact(n - 1);
}

以下のようなコマンドでコンパイルします。わかりやすさのために最適化オプションを無効にし、アセンブリを出力しています。

$ powerpc64-linux-gnu-gcc -S a.c -O0

以下のような(見慣れた?)PowerPCのアセンブリを得ることができました。

 .file   "a.c"
    .machine power7
    .section    ".toc","aw"
    .section    ".text"
    .align 2
    .globl fact
    .section    ".opd","aw"
    .align 3
fact:
    .quad   .L.fact,.TOC.@tocbase,0
    .previous
    .type   fact, @function
.L.fact:
    mflr 0
    std 0,16(1)
    std 31,-8(1)
    stdu 1,-128(1)
    mr 31,1
    mr 9,3
    stw 9,176(31)
    lwz 9,176(31)
    cmpwi 7,9,1
    bne 7,.L2
    li 9,1
    b .L3
.L2:
    lwz 9,176(31)
    addi 9,9,-1
    extsw 9,9
    mr 3,9
    bl fact
    mr 9,3
    mr 10,9
    lwz 9,176(31)
    mullw 9,9,10
    extsw 9,9
.L3:
    mr 3,9
    addi 1,31,128
    ld 0,16(1)
    mtlr 0
    ld 31,-8(1)
    blr
    .long 0
    .byte 0,0,0,1,128,1,0,1
    .size   fact,.-.L.fact
    .ident  "GCC: (Ubuntu/IBM 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"

とても簡単で最高ですね。

ちなみに、今回入ったアプリケーションたちのバージョンはこのような感じでした。

$ powerpc64-linux-gnu-gcc --version
powerpc64-linux-gnu-gcc (Ubuntu/IBM 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
...
$ powerpc64-linux-gnu-as --version
GNU assembler (GNU Binutils for Ubuntu) 2.26.1
...
$ powerpc64-linux-gnu-ld --version
GNU ld (GNU Binutils for Ubuntu) 2.26.1
...

URLから画像のサイズを取得する(Node.js)

サーバーサイドアプリケーションを書いていると、「あるURLでアクセスできる画像の(縦横の)サイズを知りたい」という場面がまれにあると思います。Nodejsにおいて、そういった場合にどうしたらよいかを紹介します。

準備

image-size を利用します。また、ここでは axios を用いますが、node-fetch など他のライブラリでも同様にできると思います。標準ライブラリの https.requesthttp.request を用いることもできますが、いろいろとめんどくさいと思います(そもそもhttpかhttpsかで使うライブラリを分けなくてはいけない?)。

ちなみに執筆時点でのバージョンは axios@0.18.0, image-size@0.7.3 です。

$ npm i image-size axios

実装

以下のような関数を用意します。axios.get のオプションにおいて responseType: 'arraybuffer' と指定してあげないと、 res.data が文字列になったりしてうまくいきません(ContentTypeなどをもとにうまくやってほしいとも思ってしまいますが...)。

この関数に画像のURLを投げれば、width プロパティ(number)と height プロパティ(number)をもったオブジェクト(のプロミス)を返してくれます。大体のメジャーなフォーマットに対応してくれています。

const imageSize = require('image-size');
const axios = require('axios');

function fetchSize(imgUrl) {
  return axios.get(imgUrl, {
    responseType: 'arraybuffer',
  })
    .then(res => imageSize(res.data))
    .then(i => ({
      width: i.width,
      height: i.height,
    }));
}

TypeScriptの場合

簡単ですが、TypeScriptだと以下のようになります。

import imageSize from 'image-size';
import axios from 'axios';

function fetchSize(imgUrl: string): Promise<{ height: number; width: number; }> {
  return axios.get(imgUrl, {
    responseType: 'arraybuffer',
  })
    .then(res => imageSize(res.data))
    .then(i => ({
      width: i.width,
      height: i.height,
    }));
}

NginxとDockerでリバースプロキシサーバーを作る

Dockerコンテナ内でnginxを動かし、リバースプロキシサーバーを作る簡単な例を紹介します。

プロダクションで利用する際はnginxの設定をもっとしっかりしないといけませんが、この記事ではそのあたりは端折ります。

コード

Dockerfile

nginx.confを /etc/ginx に追加するだけのものすごく簡単なものです。イメージについては2019年4月9日現在の最新バージョンである nginx:1.15.10 を使用しています。

FROM nginx:1.15.10
COPY nginx.conf /etc/nginx/

nginx.conf

nginxの設定ファイルです。/proxy/ からはじまるパスへのリクエストを http://example.com に振り分けるようにします。それ以外は /usr/share/nginx/html (ドキュメントルート)から配信するようにしています。

また、ログに $upstream_addr を追加するようにしています。これでプロキシ先のアドレスがログに含まれるようになります。

ちなみに、nginxの公式イメージでは、 /var/log/nginx/access.log/dev/stdout (標準出力)のエイリアスになっています。(そのイメージをもとに作っている)今回のイメージでも同様です。標準出力に吐き出されたログは docker logs コマンドなどでみることができます。

user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {
  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  log_format main '"$request" $status $upstream_addr';

  access_log /var/log/nginx/access.log  main;

  server {
    location / {
        root /usr/share/nginx/html;
    }

    location /proxy/ {
        proxy_pass http://example.com/;
    }
  }
}

起動

イメージを作成してコンテナを実行します。-p 3000:80 で、ホストのポート3000でコンテナのポート80にアクセスできるようにしています。

$ docker run -d -p 3000:80 $(docker build .)

確認

ブラウザなどで http://localhost:3000/ にアクセスすると Welcome to nginx! のページが、 http://localhost:3000/proxy/ にアクセスすると Example Domain のページが見れ、正しく動いていることがわかります。

また、以下のようにコンテナのログを確認してみます。

$ docker logs <コンテナid>
"GET / HTTP/1.1" 200 -
"GET /proxy/ HTTP/1.1" 200 93.184.216.34:80

うまくいっていることがわかります。(example.comのIPアドレスは変わるかもしれません。)

初期化されたMySQLのDockerコンテナを用意する

サーバーサイドアプリケーションの開発環境などにおいて、「初期化された(データベースやテーブルが作成された)MySQLのDockerコンテナを用意したい」といった場面があると思います。そういった際にどうすればよいか、簡単な例を通して説明します。

コード

init.sql

実行したいSQL文たちをファイルとして保存します。

CREATE DATABASE db1;
USE db1;
CREATE TABLE table1(id INT);
INSERT INTO table1 VALUES(1);

Dockerfile

SQL文が書かれたファイルを /docker-entrypoint-initdb.d/ ディレクトリ下にもってきます。MySQLのDockerオフィシャルイメージにおいて、このディレクトリ下に置かれたファイル内に記載されたSQL文がデータベース起動時に実行されるようになっています。

ここでは、2019/4/8現在最新の mysql:8.0.15 イメージを用いています。

FROM mysql:8.0.15
COPY init.sql /docker-entrypoint-initdb.d/init.sql

実行

Dockerファイルとinit.sqlが存在しているディレクトリ内で以下のコマンドを実行します。

$(docker build . -q) はDockerファイルをもとにイメージを作成し、イメージのIDを返しています。そしてそのイメージをもとにコンテナを立ち上げています。簡単のために、 -e MYSQL_ALLOW_EMPTY_PASSWORD=1 によってパスワードを無効化しています。

$ docker run -d -e MYSQL_ALLOW_EMPTY_PASSWORD=1 $(docker build . -q)

確認

データベース内にアクセスして確認してみます。

$ docker exec -ti <コンテナ名> mysql
mysql> select * from db1.table1;
+------+
| id   |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

うまく初期化され、データが入っていることがわかります。

複数ファイルに分割したい場合

今回はSQL文が書かれたファイルを1つ用意しましたが、わかりやすさのためにこれを複数に分けたい場合もあると思います(テーブルの作成とダミーデータの挿入を分けるなど)。そういった際には、init001.sql, init002.sql ... といったようにファイルを分割し、それらを /docker-entrypoint-initdb.d/ 以下に持ってくれば大丈夫です。辞書順で実行してくれます。

NginxのリバースプロキシでのDNS名前解決における落とし穴

問題

Nginxのリバースプロキシでは、プロキシ先として(IPアドレスの他に)ホスト名を指定することができます。 その際、設定ファイルのlocationコンテキストは以下のようになると思います。

location /hoge/ {
  proxy_pass http://example.com/;
}

しかし、これには大きな落とし穴があります。DNSの名前解決がnginxの起動時にしか行われず、TTLは無視され、初回起動時に解決されたIPアドレスがずっと使われてしまうのです。 もちろん、ホスト名に対応するIPアドレスに変更があったときにエラーになってしまいます。

自分の場合では、プロキシ先としてAWSのELBのホスト名を指定しており、(ELBのアドレスは固定ではないので)この問題が原因で502エラーを吐き出してしまっていました。

解決策?

nginxにはresolverというディレクティブがあります(公式ドキュメント)。By default, nginx caches answers using the TTL value of a response とあるので、TTLが尊重されるみたいです。うまく行きそうな気がします。

試しにresolverにはGoogle Public DNS8.8.8.8を利用してみます。

location /hoge/ {
  resolver 8.8.8.8;
  proxy_pass http://example.com/;
}

しかし、これでもうまくいきません。もはやバグですね。

解決策

このようにして、一旦ホスト名を変数に格納して使うと、うまくいくようです。

location /hoge/ {
  set $target example.com;
  resolver 8.8.8.8;
  proxy_pass http://$target/;
}

なんだかなあって感じです。

inversifyJSを用いてTypeScriptでDIをする

この記事では、InversifyJS を用いて TypeScript で DI をするサンプルを紹介し、また、(DIに慣れていない方向けに)DI をすると何が嬉しいのか、というのを説明します。

サンプル

「ユーザーの登録」「登録したユーザーの情報を取得」の 2 つのシンプルな機能を持ったプログラムを書きます。 また、ユーザーが登録されたときにログされるようにします。

準備

必要な依存をインストールします。

$ npm i inversify reflect-metadata typescript ts-node

tsconfig.jsonを以下の要領で作成します。 types": ["reflect-metadata"], "experimentalDecorators": true, "emitDecoratorMetaData": trueというのが InversifyJS のために必要です。

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es6", "dom"],
    "types": ["reflect-metadata"],
    "module": "commonjs",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

コード

src/User.ts

User クラスを定義します。 プロパティ2つとtoString()関数のみのシンプルなクラスです。

class User {
  constructor(public id: number, public name: string) {}

  toString(): string {
    return `User${JSON.stringify({ id: this.id, name: this.name })}`;
  }
}

src/UserRepository.ts

User クラスに対応付けられるデータの永続化に用いるためのインターフェイスを定義します。 「ユーザーの作成」と「ユーザーの取得」の 2 つの操作を定義しています。

import User from "./User";

export default interface UserRepository {
  find(id: number): User;
  create(name: string): User;
}

src/UserRepositoryImpl.ts

UserRepository の実装を用意します。 通常のアプリケーションであればここでデータベースなどを利用した実装をするかと思いますが、今回はシンプルにインメモリのオブジェクトを用いています。

後に DI で他オブジェクトに注入できるよう、inversify の@injectable()デコレータをつけています。

import { injectable } from "inversify";
import User from "./User";
import UserRepository from "./UserRepository";

@injectable()
export default class UserRepositoryImpl implements UserRepository {
  private store: { [key: string]: User | undefined } = {};
  private nextUserId: number = 0;

  constructor() {}

  find(id: number): User {
    const user = this.store[id];
    if (!user) {
      throw new Error("user not found");
    }
    return user;
  }

  create(name: string): User {
    const id = this.nextUserId++;
    const user = new User(id, name);
    this.store[id] = user;
    return user;
  }
}

src/Logger.ts

こちらはロガー用のシンプルなインターフェイスです。

export default interface Logger {
  log(message: string): void;
}

src/LoggerImpl.ts

ロガーの実装です。実際は winston などを使ったりするとは思いますが、ここでは console に流すだけにしています。 こちらも@injectable()デコレータをつけています。

import { injectable } from "inversify";
import Logger from "./Logger";

@injectable()
export default class LoggerImpl implements Logger {
  log(message: string): void {
    console.log(message);
  }
}

src/App.ts

メインのクラスを定義します。

createNewUser(name: string)により、(UserRepository インターフェイスを実装したインスタンスによって)ユーザーが作成され、また新しいユーザーが作成されたことが(Logger インターフェイスを実装したインスタンスを用いて)ログされるようにしています。

findUser(id: number)により、特定の id に対応するユーザーのデータを取得できるようにしています。

@inject()デコレータによって、userRepositoryUserRepositoryのインスタンスが、loggerLoggerのインスタンスが注入されるようになっています。

import { injectable, inject } from "inversify";

import UserRepository from "./UserRepository";
import Logger from "./Logger";

@injectable()
export default class App {
  private readonly userRepository: UserRepository;
  private readonly logger: Logger;

  constructor(
    @inject("UserRepository") userRepository: UserRepository,
    @inject("Logger") logger: Logger
  ) {
    this.userRepository = userRepository;
    this.logger = logger;
  }

  createNewUser(name: string) {
    const user = this.userRepository.create(name);
    this.logger.log(`new user: ${user}`);
    return user;
  }

  findUser(id: number) {
    const user = this.userRepository.find(id);
    return user;
  }
}

src/inversify.config.ts

DI コンテナを作成し、使用するオブジェクトをバインドします。

import { Container } from "inversify";

import App from "./App";
import Logger from "./Logger";
import LoggerImpl from "./LoggerImpl";
import UserRepository from "./UserRepository";
import UserRepositoryImpl from "./UserRepositoryImpl";

const container = new Container();
container.bind<Logger>("Logger").to(LoggerImpl);
container
  .bind<UserRepository>("UserRepository")
  .to(UserRepositoryImpl)
  .inSingletonScope();
container.bind<App>("App").to(App);

export default container;

src/index.ts

実際に実行させてみるコードです。

import "reflect-metadata";

import container from "./inversify.config";
import App from "./App";

container.get<App>("App").createNewUser("Taro");
container.get<App>("App").createNewUser("Jiro");
console.log(container.get<App>("App").findUser(1)));

実行

実際に実行させてみると、以下のようになり、うまくいっていることがわかります。

$ ./node_modules/.bin/ts-node srd/index.ts
new user: User{"id":0,"name":"Taro"}
new user: User{"id":1,"name":"Jiro"}
User { id: 1, name: 'Jiro' }

inversifyJS、直感的でわかりやすいですね。

補足

DIコンテナへのオブジェクトの登録において、簡単のために文字列を用いましたが、Symbolを利用することが推奨されています。

何が嬉しいのか

オブジェクト指向的なプログラミングをする際、インターフェイス等を用いて「実装は抽象に依存させる」こと、そして「できるだけ抽象への依存を増やす」ことが大切です。 そうすることでオブジェクト同士が疎結合になり、テストもしやすく、各オブジェクトの実装の変更が簡単な設計になります。

例えば、今回のAppクラスはLoggerとUserRepositoryという抽象(インターフェイス)に依存をしていました。 そして、LoggerImplという実装はLoggerという抽象に依存し、UserRepositoryImplという実装はUserRepositoryという抽象に依存していました。

このことにより、AppクラスはLoggerの実装やUserRepositoryの実装と切り離されていました(疎結合)。

実際、UserRepositoryに関して、(今回や、テスト時のために)単純なオブジェクトを用いた実装を用意しても、MySQLを用いた実装を用意しても、Appクラスのコードは全く変わらないことがわかると思います。

このように、インターフェイスを用いて依存の方向を制御するとソフトウェアの構成がいい感じにはなるのですが、オブジェクトの作成・その受け渡しが結構面倒になります。

今回のケースだったら、

const logger = new LoggerImpl();
const userRepository = new UserRepository();
const app = new App(userRepository, logger);

程度で済んだりもしますが、オブジェクトの数が増え、依存性も複雑になってくるとかなりしんどくなってくるはずです。

それの面倒を見てくれるのが、inversifyJSのようなDIフレームワーク、DIコンテナというわけです。

参考

inversify.io

github.com

クラウドとディープラーニングと価格予測の話

AlpacaJapanさん主催のMarketTech Meetup #02でLTをさせていただいたので、その時の内容をここにもまとめておきます。

スライドはこちらです。

www.slideshare.net

ディープラーニングで価格予測モデルを作る

「ディープラーニングで金融商品の価格予測モデルを作る」という場合に、

  • いろいろなアーキテクチャ、ハイパーパラメータ、データで学習したい
  • いろいろな学習済みモデル、データ、ハイパーパラメータでバックテストをしたい
  • 複数のモデルを組み合わせて一つのモデルを作る、すなわちアンサンブルをしたい

といったことがしたくなります。

しかし、当たり前ながらディープラーニングの学習にはには時間がかかります。 アーキテクチャ5種類、ハイパーパラメータ5種類、データ5種類だけでも、組み合わせによって125通りのモデルができます。 それぞれに1時間かかっていたら5日間かかります。

バックテストにおいても、組み合わせが増えるとバカにならない時間がかかります。

クラウドを使っていいい感じにディープラーニングしよう!というのが、今回のトピックです。

クラウドでディープラーニングをすると

クラウドの特徴として「独立した処理については、一つずつ動かしても、一気に動かしても、かかるコストは同じ」ということがあげられます。 この特徴は、今考えている問題と非常に相性がいいです。

さきほどの例、つまり1時間かかる処理が125個ある例、では、5日間ではなく、1時間ですべての処理が終わらせることができます。

また、「様々な種類のマシンを利用できる」というのもクラウドの大きな利点です。GPUが載っているのも多数あります。

また、今回のような処理は、ユーザーを待たせているわけではないため「すぐに結果が必要」でないこともあります。

そのため、「需要の低い時間に安い価格でインスタンスを借りて計算する」ということも可能になります。AWSでいうスポットインスタンス、GCPでいうプリエンプティブインスタンスです。このおかげで思ったよりコストがかさまなかったりします。

ちなみに自分たちはAWSのスポットインスタンスを用いることで、p2.xlargeというインスタンスを70%オフくらいで使うことができています。

考えなくてはいけないこと

もちろんいいことだけではなく、考えなくてはならないこともあります。

まず1つ目が「処理実行の管理をしないといけない」ということです。いくつかのインスタンスを立ち上げ、いい感じに分散させて処理を実行する必要があります。また、スポットインスタンスを用いる場合には考えることが増えます。ローカルで実験するのに比べると、とても手間がかかります。

そして2つ目が「実行結果が分散されてしまう」ということです。それぞれの処理の結果は複数のインスタンスに散らばっている状態になるので、それをどうにかして一箇所に集めることが必要になります。やはりローカルで実験するよりは手間がかかります。

その上で、実際どうやっているか

上にあげたようなことを考え「実際にこんな感じでクラウドで価格予測モデルを作っているよ」という話をします。

実際どうやっているか①

まず、学習のためのDockerコンテナを作成します。 実行コマンドのコマンドライン引数によって、アーキテクチャ、データ、ハイパーパラメータを指定できるようにします。

また、学習が終わったら、生成されたモデルをS3などのクラウドストレージに、パラメータなどをデータベースに保存するようにしています。こうしていろいろなマシンで実行された学習の結果が一箇所に集まるようにします。

余談ですが、Nvidia Dockerを用いることでDockerでもGPUを活用できます。

実際どうやっているか②

そしてあとは学習の実行ですが、自分たちはAWS Batchというサービスを用いています。 このサービスは、ジョブのキューを用意してくれます。ジョブというのは、Dockerイメージと実行コマンドの組み合わせです。

自分たちがキューにジョブを追加すると、AWS Batchがうまいことインスタンスを立ち上げ、そのジョブを実行してくれるというわけです。 スポットインスタンスも簡単に利用できるようになっています。

実際どうやっているか③

バックテストも学習と同様です。 Dockeイメージを用意し、コマンドライン引数によってテストしたいモデルや使いたいデータなどを指定するようにします。 そして、実行が終了した際に結果をクラウドストレージやデータベースに保存するようにしています。 また、AWS Batchを用いているのも同様です。

実際どうやっているか④

AWSのコンソールやCLIのみでこれらの操作すべてをやるのは厳しく、また機能的にも足りないところが出てくるので、機能を拡張し、そしてGUIも用意しています。

具体的には、

  • 学習ジョブ・バックテストジョブの追加
  • 実行中ジョブのログの表示
  • 学習済みモデルの詳細を表示
  • 実行済みバックテストの詳細を表示

といった機能をもたせています。

これのおかげで、スマホからでも学習・バックテストを回したり、それらの結果を確認できたりもします。

おわりに

クラウドを使うとディープラーニングが快適にできます。そして、クラウドプロバイダはディープラーニングに役立つサービスをたくさん提供しているので利用すべきです。

おまけ

本当に相場を学習して勝てているのかの見定めは難しいです。「4回の大きい価格変動を当てる」モデルは16個に一個くらいはできてしまいます。

また、探索空間が膨大に増えますが、複数モデルのアンサンブルもしています。