Melchior

バンクーバーあたりで働くエンジニアの備忘録

classの継承についての考察

概要

ES6の継承が遅すぎると話題なので検証・最適化をしました。

どれくらい遅いのか

f:id:suguru03:20170428145927p:plain

こちらベンチマークによると、少しずつ改善は見られるものの、2.3-17倍程度遅い様子です。

実際に検証しました。

  • Node v6.10.1
  • benchmark.js v2.1.4

継承

// classes
function ParentES5(value) {
  this._value = value;
}
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
}
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
}

// functions
const funcs = {
  'es5': function() {
    return new ES5(1);
  },
  'es6': function() {
    return new ES6(1);
  }
};
**** benchmark.js ****
[1] "es5" 0.000029ms [1.00]
[2] "es6" 0.000045ms [1.56]

単純にclassを使うだけでも1.5倍遅いようです。

super classメソッドの呼び出し

function ParentES5(value) {
  this._value = value;
}
ParentES5.prototype.get = function() {
  return this._value;
};
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

ES5.prototype.get = function() {
  return ParentES5.prototype.get.call(this) * 2;
};

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
  get() {
    return this._value;
  }
}
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
  get() {
    return super.get() * 2;
  }
}
const es5 = new ES5(1);
const es6 = new ES6(1);

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
**** benchmark.js ****
[1] "es5" 0.000021ms [1.00]
[2] "es6" 0.000078ms [3.79]

super classメソッドの呼び出しは非常に遅いので避けたいところです。

考察

super classメソッドの呼び出し

function ParentES5(value) {
  this._value = value;
}
ParentES5.prototype.get = function() {
  return this._value;
};
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

ES5.prototype.get = function() {
  return ParentES5.prototype.get.call(this) * 2;
};

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
  get() {
    return this._value;
  }
}
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
  get() {
    return ParentES6.prototype.get.call(this) * 2;
  }
}
const es5 = new ES5(1);
const es6 = new ES6(1);

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
**** benchmark.js ****
[1] "es6" 0.000020ms [1.00]
[2] "es5" 0.000020ms [1.02]

super.get()の代わりにsuper classメソッドをcallすることでes5と同等のパフォーマンスが得られました。これは使えそうです。

こちらのパターンも同じような結果が得られます。

function ParentES5(value) {
  this._value = value;
}
function getES5() {
  return this._value;
};
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

ES5.prototype.get = function() {
  return getES5.call(this) * 2;
};

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
}
function getES6() {
  return this._value;
};
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
  get() {
    return getES6.call(this) * 2;
  }
}
const es5 = new ES5(1);
const es6 = new ES6(1);

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
**** benchmark.js ****
[1] "es5" 0.000023ms [1.00]
[2] "es6" 0.000023ms [1.00]

こちらもなかなか使いどころがありそうです。

継承

ParentES6.prototype.constructorへのアクセスは禁止されているため、classをextendsしsuperを呼び出す必要があります。

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
}

class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
}
// エラー
class ES6 {
  constructor(value) {
    ParentES6.prototype.constructor.call(this, value); 
  }
}
// TypeError: Class constructor ParentES6 cannot be invoked without 'new'

今のところたどり着いた最速の継承はこちらです。

function ParentES5(value) {
  this._value = value;
}
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
  get() {
    return this._value;
  }
}
function ES6(value) {
  this._value = value;
}
ES6.prototype = ParentES6.prototype;

const es5 = new ES5(1);
const es6 = new ES6(1);
console.log(es6.get()); // 1
console.log(es6 instanceof ParentES6); // true

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
[1] "es6" 0.000020ms [1.00]
[2] "es5" 0.000026ms [1.33]

es5の継承よりも高速な継承が可能となりました。もう少し検証が必要ですが、instanceofがtrueとみなされるのでこちらは意外と使えるかもしれません。 こちらの案はthamminさんがベースを提供してくれました。

まとめ

superクラスの呼び出しを避ける考察でした。ns,μsレベルのチューニングなので普通にsuperを呼んでいただければと思います。

JavaScriptっておもしろい。

リンク

Dockerでローカル開発環境のセットアップ

概要

開発環境のセットアップをVagrantからDockerに移行しました。 サーバ間通信のテストもしているので今回は同時にNode.jsのクラスタサーバをセットアップします。

一番の目的は、新人さんがWindowsやらUbuntuやらでうまくVagrantfileが動かなかったのでこれを機にDockerでセットアップすることにしました。 これで文句は言わせまい。

docker-composeの作成

yamlファイルはこちらです。

# mysql
version: "3"
services:
  mysql:
    container_name: mysql
    image: mysql:5.7.17
    ports:
      - "3306:3306"
    expose:
      - "3306"
    environment:
      MYSQL_ROOT_PASSWORD: mysql
  redis:
    container_name: redis
    image: redis
    ports:
      - "6379:6379"
  nginx:
    container_name: nginx
    restart: always
    build: ./nginx
    ports:
      - "80:80"

開発の作業はローカルで行いたいので、MysqlはDockerの外部からアクセスが必要なためexposeします。docsが優秀なので何か特別なことをやりたい場合は公式ドキュメントを参照してください。NginxはNode.jsのクラスタ用です。Nginxの中身はこちらを参考にしました。NginxのconfigはWebsocket用の設定です。

nginx
└─ Dockerfile
└─ sites-enabled 
  └─ default
# Dockerfile
FROM tutum/nginx
RUN rm /etc/nginx/sites-enabled/default
ADD sites-enabled/ /etc/nginx/sites-enabled
# default
upstream websockets {
  server localhost:3000;
  server localhost:3001;
}

server {
  listen 80;
  location / {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_pass http://websockets;
  }
}

開発環境のセットアップ

docker-compose up -dを実行すれば空のMySQL、Redis、Nginxが立ち上がるのですが、新人さんが嵌りそうなのでどうにか自動化できないかなぁとshellスクリプトも書くことにしました。 Windows以外は自動的にセットアップが完了するかなぁと思います。もしNginxがうまく動かなかったら手動で設定する必要があります。

#! /bin/sh

# Hostの取得
if type ipconfig; then
  # mac
  HOST=$(ipconfig getifaddr en0) 
elif type netstat; then
  # linux (引用元が見つかりませんでした…すみません><)
  HOST=$(netstat -nr | grep '^0\.0\.0\.0' | awk '{print $2}')
else
  HOST=""
fi

# Nginxの設定
nginxconf="./nginx/sites-enabled/default"
if [ "$HOST" != '' ]; then
  echo HOST: $HOST
  sed -i.bak -e "s/localhost/$HOST/g" $nginxconf
  # TODO sed~
  rm $nginxconf.bak
else
  echo "WEARING: need to modify nginx ip address"
fi

# Dockerの起動
docker-compose up --build -d

# 依存ファイルのインストール
npm i
npm i -g mocha pm2

# MySQLの自動セットアップ
...

# サーバの起動
pm2 start pm2.json
// pm2.json
{
  "name": "test-cluster",
  "script": "server.js",
  "exec_mode": "fork",
  "instances": 2,
  "merge_logs": true,
  "out_file": "./log/pm2/out.log",
  "error_file": "./log/pm2/error.log"
}

Docker上のNginxでローカルのipアドレスがうまく解決できなかったので、無理やり解決しています、もっといい方法があるかもしれません。。

まとめ

docker-composeがとても便利でした。 来週新人さんが来るのでうまく動くことを祈っています、おそらくmacユーザではないです。

リンク

Powerlineの導入

概要

Powerlineとはshell・vimにちょっとした情報をカッコよく表示してくれるツールです。何よりカッコいい。導入手順はこちらです。

Powerline for Shell

まずはshellからカッコよくしていきます。はじめにpowerline-shellをクローンします。

$ cd ~
$ git clone https://github.com/milkbikis/powerline-shell

このディレクトリは削除できないのでお好みなところにクローンしてきてください。 次にインストールします。設定を変えたい場合はconfig.pyを変更することでお好みの設定に変更できます。設定変更後は再度install.shを実行する必要があります。

$ ./install.sh

これでpowerline-shell.pyが生成されるので、Symlinkを貼ります。

$ ln -s ~/powerline-shell/powerline-shell.py ~/powerline-shell.py

私はzshを使用しているので、zshrcに設定を以下のように追加します。bash, fishの設定はこちらを参照してください。

# power-shell
function powerline_precmd() {
    PS1="$(~/powerline-shell.py --cwd-max-depth 1 $? --shell zsh 2> /dev/null)"
}

function install_powerline_precmd() {
  for s in "${precmd_functions[@]}"; do
    if [ "$s" = "powerline_precmd" ]; then
      return
    fi
  done
  precmd_functions+=(powerline_precmd)
}

if [ "$TERM" != "linux" ]; then
    install_powerline_precmd
fi

次にフォントのインストールをします。こちらはREADMEのスクリプトをそのまま実行するだけです。

# clone
git clone https://github.com/powerline/fonts.git
# install
cd fonts
./install.sh
# clean-up a bit
cd ..
rm -rf fonts

こんな感じになりました、カッコいいだけでなく意外に便利です。

f:id:suguru03:20170403130056p:plain

Powerline for Vim

次にvimのpowerlineの設定をしていきます。はじめにお使いのvimの環境を確認してください。こちらから条件を確認できます。

私はvim-plugを使っているので.vimrcに以下のように追加します。

Plug 'powerline/powerline', {'rtp': 'powerline/bindings/vim/'}

フォントもインストール済みなので、以上で設定は終了です。 別のプラグインを使っている方はこちらから確認してみてください。

こんな感じになります。

f:id:suguru03:20170403125852p:plain

まとめ

デメリットはshellが少し重くなりますが、カッコいいだけでなく意外にも便利だったので導入してもいいかなと思いました。

リンク

AnyenvでGoのバージョン管理

概要

Anyenvとはこれ一つで色々なパッケージ管理ができるスグレモノのようです。今回はGolangまわりだけ整理していたので、Golangのセットアップ手順について書きます。

インストール

まずはリポジトリをクローン。

$ git clone https://github.com/riywo/anyenv ~/.anyenv

.zshrcに以下を追記。

# anyenv
export PATH="$HOME/.anyenv/bin:$PATH"
eval "$(anyenv init -)"

goenvをインストールします。

$ anyenv install goenv

次にGolangをインストールします。

$ goenv install 1.8

.zshrcにGOPATHを設定します。(goenv rehashで解決してくれると思ったらそうでもない)

# Go
export GO_VERSION=1.8
export GOROOT=$HOME/.anyenv/envs/goenv/versions/$GO_VERSION
export GOPATH=$HOME/dev
export PATH=$HOME/.anyenv/envs/goenv/shims/bin:$PATH
export PATH=$GOROOT/bin:$PATH
export PATH=$GOPATH/bin:$PATH
echo Now using golang v$GO_VERSION

なんとなくnvmと同じechoを追加します。

Now using node v6.10.1 (npm v3.10.10)
Now using golang v1.8

$ go version
go version go1.8 darwin/amd64

良い感じになりました。

まとめ

Version Managerにこだわりが無いようでしたら、新しい言語をインストールするときにあちこちVersion Managerを探しに行くよりサクッとインストールできるのでいいかなぁと思います。

リンク

GitHub - riywo/anyenv: all in one for **env

GitHub - ingtk/dotfiles

Dockerのお掃除

概要

MBPの容量が圧迫しておりどうやら犯人はDocker for Macのようなのでお掃除していきます。

不要なコンテナの削除

Docker – Clean Up After Yourself! | Yohan Liyanage

こちらのブログより。

exited containersは自動削除されないとのことで、こちらを実行してくださいとの。

$ docker rm -v $(docker ps -a -q -f status=exited)

rmコマンドは少なくとも1つ以上の引数を必要とするので、

"docker rm" requires at least 1 argument(s).
See 'docker rm --help'.

Usage:  docker rm [OPTIONS] CONTAINER [CONTAINER...]

Remove one or more containers

このエラーが出た場合は削除できるexited containersが存在しないとのことなので気にせず進みます。

不要なイメージの削除

macos - How to clean up Docker for Mac containers - Ask Different

続いてこちら。 ポイポイしていきます。

$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /etc:/etc spotify/docker-gc

不要なコンテナ・イメージの削除【追記】

Christinaさんよりアドバイスいただき、以下のコマンドで上記2つは解決できるそうです、ありがとうございます。

$ docker system prune

Docker.qcow2の削除

Where does Docker keep images/containers so i can better track my disk usage - Docker for Mac - Docker Forums

最後のこちらが一番大物で、私の場合は30GBほど占領していました。 キャッシュのようですが容量をこんなに占拠されては困りますのでポイポイしていきます。

$ rm ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2

ソース

Node.jsで内部変数・関数の取得

概要

テストやちょっとしたツールを作りたいときに内部変数・関数にアクセスしたい時がありますが、 そんなときに便利なのがvmモジュールです。しかしvmではvarの変数は取得できるものの、letconstの変数は取得できません。 そこで色々試行錯誤して作ったツールについて書きます。

vmを用いた内部関数の取得

vm.runInNewContextではコードを実行時にglobalオブジェクトをセットすることができます。vm.runInThisContextは自動的に現在のglobalオブジェクトが使われます。

この関数は実行時にglobalスコープ上のvarをglobalオブジェクトに代入することで外からのアクセスを可能にしてくれますが、letconstは変換されません。

sample.js

const test1 = 'test1';
let test2 = 'test2';
var test3 = 'test3';

function test(arg1, arg2, arg3) {
  const sum = arg1 + arg2 + arg3;
  let num1 = 1;
  var num2 = 2;
  num3 = 3;
  return sum + num1 + num2 + num3;
}

exec.js

const vm = require('vm');
const fs = require('fs');
const path = require('path');

const filepath = path.resolve(__dirname, 'sample.js');
const file = fs.readFileSync(filepath, 'utf8');

const context = {};
vm.runInNewContext(file, context);
console.log(context);
/* 
 * { test3: 'test3', 
 *   test: [Function: test] } // let, constが取得できない
 */

vm-agent

今回作ったツールはlet, constが取得できるのと、関数を自動実行して値を取得できるツールを作りました。 また関数の実行も可能です。

const fs = require('fs');
const path = require('path');
const { Agent } = require('vm-agent');

const filepath = path.resolve(__dirname, ''sample.js');
const file = fs.readFileSync(filepath, 'utf8');

const result1 = new Agent(file)
  .run()
  .getInnerVariable();

console.log(result1);
/* 
 * { test1: 'test1',
 *   test2: 'test2',
 *   test3: 'test3',
 *   test: [Function: test] }
 */

const result2 = new Agent(result1.test)
  .setArguments(4, 5, 6)
  .run()
  .getInnerVariable();

console.log(result2);
/*
 * { arg1: 4, 
 *   arg2: 5, 
 *   arg3: 6, 
 *   sum: 15, 
 *   num1: 1, 
 *   num2: 2, 
 *   num3: 3 }
 */

vm-agent/example at master · suguru03/vm-agent · GitHub

実装内容

実装内容はesprimaを用いてlet, constvarに変換し、escodegenを用いてコードを再構築し実行するというものです。constが上書きできてしまったりパフォーマンスが低下したりしますが、テスト用なのでいったん良しとします。

また関数の実行については、関数の引数を解析し変数に変換することでvmモジュールでのアクセスを可能にしています。

ソース

Objective-CにおけるDate型の扱い

概要

なぜか最近仕事でObjective-Cを書いています。 サーバからmsのtimestampを受け取っているにも関わらず、時系列順に表示されなかったので調べてみました。

Types in Objective-C

まずはじめにハマったのが、msはint型には収まりません。JavaScriptの住人なのでここで時間をロスしました。

Types in objective-c on iPhone - Stack Overflow

この記事によると32bit端末ではlong型が4bytes扱い、64bit端末ではlong型が8bytes扱いだそうです。

Device一覧: https://en.wikipedia.org/wiki/List_of_iOS_devices

WikipediaによるとiPhone5S以降は64bitなので、long型を使うことにしました。(良いのかは不明)

Primitive sizes:
The size of a char is: 1.
The size of short is: 2.
The size of int is: 4.
The size of long is: 8.
The size of long long is: 8.
The size of a unsigned char is: 1.
The size of unsigned short is: 2.
The size of unsigned int is: 4.
The size of unsigned long is: 8.
The size of unsigned long long is: 8.
The size of a float is: 4.
The size of a double is 8.
Ranges:
CHAR_MIN:   -128
CHAR_MAX:   127
SHRT_MIN:   -32768
SHRT_MAX:   32767
INT_MIN:    -2147483648
INT_MAX:    2147483647
LONG_MIN:   -9223372036854775808
LONG_MAX:   9223372036854775807
ULONG_MAX:  18446744073709551615
LLONG_MIN:  -9223372036854775808
LLONG_MAX:  9223372036854775807
ULLONG_MAX: 18446744073709551615

Dateの扱い

Objective-CではDateがmsをサポートしていないため、以下のように書かなければなりません。

NSNumber *timestamp = responseDict[@"timestamp"];
long ts = [timestamp doubleValue]; // coredataへ保存
NSDate *date = [NSDate dateWithTimeIntervalSince1970:ts/1000]; // ms -> s

JavaScriptの住人にとっては何だこれっていうレベルです。Swift知りませんが、きっとここらへんは改善されてるはず…祈

このプロジェクトではCoreDataにDate型で保存していたため、secondまでしかサポートされておらず時系列順にソートすると残念な結果になったので、しれっとInteger 64型に書き換えて保存するようにしました。

まとめ

せっかくならSwift書きたいです。

ソース