WebAssembly(wat)を手書きする

2017/12/15
このエントリーをはてなブックマークに追加
この記事はWebAssembly Advent Calendar 2017の15日目の記事です。

このAdvent Calendarを口実にWebAssemblyを勉強するつもりが気がつけば当日、どころか娘の寝かしつけからの寝かしつけられカウンターが発動し、気がつけば当日23時を過ぎていました。とりあえずプレースホルダー的なエントリを投稿した後で気合で何かでっちあげて翌日AM4時にソースコードのみを追記、そして翌週である本日18日にこの文章を書いています。誠に申し訳ありません。カブクでは主にthree.js周りを担当しているあんどうです。

やったこと


みんなだいすきGame of Lifeのハンドアセンブルをがんばりました。

勉強目的とは言え時間がなく、ツールを覚える時間どころかインストールする時間も惜しいので、テキスト形式のWebAssembryを手書きしました。とても勉強になるし、とりあえず動かすだけなら意外と簡単なのでおすすめです。ライフゲームは「簡単に書けて少しは見て楽しいもの」という基準でなんとなく選んだだけで、特に意味はありません。

準備


テキスト形式のWebAssemblyはバイナリ形式に変換しないと動作を確認できません。まずはそのためのツールをインストールしておきます。インストールに使う時間も惜しいと先ほど書いたばかりですがここはしょうがない。


ここに書かれてあるとおり、git cloneしてmakeします。MacユーザーでCMakeが入っていない場合は先に入れておきましょう。

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ make

makeが完了したらとりあえず動作確認します。面倒くさいのでパスは通してません。

$ cat 42.wat
(module
  (func $i (import "imports" "imported_func") (param i32))
  (func (export "exported_func")
    i32.const 42
    call $i
  )
)
$ wabt/out/clang/Debug/wat2wasm 42.wat
$ ls -lh 42.*
-rw-r--r--  1 yasushiando  staff   435B 12 15 14:27 42.html
-rw-r--r--  1 yasushiando  staff    78B 12 18 11:02 42.wasm
-rw-r--r--  1 yasushiando  staff   135B 12 15 11:37 42.wat

ドキュメントにあったコードを取ってきてコンパイルしたら動きました。

WebAssemblyの基本


時間もないので基本サンプルコードを見て「あっ・・・(察し)」というスタンスで作業を進めています。ここに書いていることに嘘があったら教えてください。Advent Calendarを口実に勉強するはずだったのに、すでに大事なものを見失っていますが、大義はまず生き残ってから、歴史は勝者が作るのです。

  • WebAssemblyのテキスト形式はS式で表現されます。S式がよく分からなければLISPだと思ってください。LISPがよく分からなければサンプルコードから「あっ・・・(察し)」してください。
  • WebAssemblyはスタックマシンです。引数的なものをスタックにプッシュして、関数呼び出しに必要なものが揃ったところでcallを呼び出すと処理が実行されます。察してください。
  • ローカル変数が使えます。便利。

元にするコード


WebAssemblyを手書きすると言っても何もないところから書き始めるのはハードルが高すぎるので、後でハンドアセンブルすることを前提にまずはJavaScriptで対象の関数を作成します。その際気をつけたことは以下のとおりです。

  • WebAssemblyにないのでイテレータは使いません。ループはfor文のみ。
  • ハマりたくないので負数は使いません。今回はJavaScriptのUint32ArrayとWebAssemblyのi32で全てまかないます。
  • WebAssemblyで使えるデータ構造は1次元配列であるMemoryと連想配列であるTableだけです。しかも現時点ではモジュールごとにそれぞれひとつずつしか持つことができません。そのためデータはすべて1次元配列に入れます。今回はmemoryという一次元配列に以下のようにデータを詰め込んでいます。
    インデックス0
    ライフゲームの世代
    インデックス1〜width*height
    偶数世代のときの盤面
    インデックスwidth*height+1〜2*width*height
    奇数世代のときの盤面

ライフゲーム



一応書いておくとライフゲームは2次元格子で構成される世界で以下のルールに従ってセルが黒くなったり(生)白くなったり(死)する小宇宙です。心が疲れているときには何時間でも眺めていられます。

誕生
死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
生存
生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
過疎
生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
過密
生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

ソースコード(JavaScript)


先ほどのルールをそのままコードに落とすと以下のようになりました。2次元のグリッドを1次元の配列に収めていることと、偶数世代と奇数世代でオフセットを切り替えていることに注意すれば特に難しいところはないはずです。

ちなみに当初はmemory[0]に世代数をそのまま格納してモジュロ演算でオフセットを求めるようにしていましたが、WebAssemblyにはモジュロ演算命令がなさそうだったので後で条件文で0と1を交互に収めるように変更しました。

function nextStep() {
  let offset = memory[0] === 0 ? 1 : width * height + 1;
  let nextOffset = memory[0] === 1 ? 1 : width * height + 1;
  for (let y = 1; y < height - 1; y++) {
    for (let x = 1; x < width - 1; x++) {
      let neighbors = 0;
      for (let dy = 0; dy <= 2; dy++) {
        for (let dx = 0; dx <= 2; dx++) {
          if (memory[offset + x + dx - 1 + (y + dy - 1) * width] === 1) {
            neighbors += 1;
          }
        }
      }
      if (memory[offset + x + y * width] === 0) {
        if (neighbors === 3) {
          memory[nextOffset + x + y * width] = 1;
        }
        else {
          memory[nextOffset + x + y * width] = 0;
        }
      }
      else {
        if (neighbors <= 2 || 5 <= neighbors) {
          memory[nextOffset + x + y * width] = 0;
        }
        else {
          memory[nextOffset + x + y * width] = 1;
        }
      }
    }
  }
  memory[0] = memory[0] === 0 ? 1 : 0;
}

ハンドアセンブル


先ほどのJavaScriptのソースコードを機械的にWebAssemblyに置き換えていきます。置き換えに必要なものは一に根性ですが、それ以外に必要なWebAssemblyの仕様とルールは以下の通り。なお今回は変数の型としてi32しか使用しないので、型を指定する部分はすべてi32と直接記述しています。

コメント


;; <コメント>

定数をスタックにプッシュ


(i32.const <値>)

ローカル変数宣言


(local <変数名> i32)

ローカル変数に値を設定


(i32.const <値>)
(set_local <変数名>)

上記は以下のように書くこともできます。

(set_local <変数名> (i32.const <値>))

ローカル変数の値をスタックにプッシュ


(get_local <変数名>)

JavaScriptから呼び出される関数の定義


(func (export <関数名>) (param <引数名> i32) (param <引数名> i32) ...
  <定義>

)

メモリから値を取得


(i32.const <メモリのインデックス>)
(i32.load)

または

(i32.load (i32.const <メモリのインデックス>))

現時点ではメモリはモジュール内にひとつしか存在しないので、使用するモジュールを指定する必要はありません。

メモリに値を設定


(i32.const <メモリのインデックス>)
(i32.const <値>)
(i32.store)

または

(i32.store (i32.const <メモリのインデックス>) (i32.const <値>))

if文


if (cond === 0) {
  <処理>
}
else {
  <処理>
}

上記のようなfor文は以下のように置き換えられます。

(if (i32.eqz (get_local $cond))
  (then
    <処理>
  )
  (else
    <処理>
  )
)

if式の第一引数はもちろん条件式で、i32.eqz以外にもさまざまな式が利用できます。今回利用しているのは以下の5つです。

  • i32.eq: スタックの一番上の値と2番目の値が等しい
  • i32.eqz: スタックの一番上の値がゼロ
  • i32.ge_u: スタックの一番上の値と2番目の値より大きいか等しい
  • i32.gt_u: スタックの一番上の値と2番目の値より大きい
  • i32.le_u: スタックの一番上の値と2番目の値より小さいか等しい

for文


for (let i = 0; i < max; i++) {
  <処理>
}

上記のようなfor文は以下のように置き換えられます。

(local $i i32)
(local $max i32)
(set_local $i (i32.const 0))
(block $block (loop $loop
  (br_if $block (i32.ge_u (get_local $i) (get_local $max)))

  <処理>

  (br $loop)
))

$block、$loopなどはそれぞれブロック名、ループ名で、br_if式でブロックを脱出できることと、br文でループを繰り返せることを理解しておけばなんとなく察せられるはずです。

JavaScriptから受け取る値の利用


(import <オブジェクト名> <プロパティ名> <型>)

WebAssemblyで定義した関数をJavaScriptからどのように利用するかについては後ほど説明します。

ソースコード(WebAssembly)


上記の仕様とルールに基づいてJavaScriptを気合で書き直すと以下のようになります。コメントで対応するJavaScriptのコードを追記しているので、ゆっくり読めば「あっ・・・」できると思いますが、無駄に長いので別に読む必要はありません。がんばったね、と言いながらTwitterやFacebookでシェアしてくれればそれで十分です。むしろシェアだけしてくれてもいいくらいです。

(module
  (import "console" "log" (func $log (param i32)))
  (import "js" "mem" (memory 1))
  (func (export "nextStep") (param $width i32) (param $height i32)
    (local $step i32)
    (local $offset i32)
    (local $nextOffset i32)
    (local $x i32)
    (local $y i32)
    (local $dx i32)
    (local $dy i32)
    (local $neighbors i32)
    (local $temp1 i32)
    (local $temp2 i32)
    (local $temp3 i32)

    ;; let offset = memory[0] === 0 ? 1 : width * height + 1;
    (set_local $step (i32.load (i32.const 0)))
    (if (i32.eqz (get_local $step))
      (then
        (set_local $offset (i32.const 1))
      )
      (else
        (set_local $offset (i32.add (i32.mul (get_local $width) (get_local $height)) (i32.const 1)))
      )
    )

    ;; let nextOffset = memory[0] === 1 ? 1 : width * height + 1;
    (if (i32.eq (get_local $step) (i32.const 1))
      (then
        (set_local $nextOffset (i32.const 1))
      )
      (else
        (set_local $nextOffset (i32.add (i32.mul (get_local $width) (get_local $height)) (i32.const 1)))
      )
    )

    ;; for (let y = 1; y < height - 1; y++) {
    (set_local $y (i32.const 1))
    (block $yblock (loop $yloop
      (br_if $yblock (i32.ge_u (get_local $y) (i32.sub (get_local $height) (i32.const 1))))

      ;; for (let x = 1; x < width - 1; x++) {
      (set_local $x (i32.const 1))
      (block $xblock (loop $xloop
        (br_if $xblock (i32.ge_u (get_local $x) (i32.sub (get_local $width) (i32.const 1))))

        ;; let neighbors = 0;
        (set_local $neighbors (i32.const 0))

        ;; for (let dy = 0; dy <= 2; dy++) {
        (set_local $dy (i32.const 0))
        (block $dyblock (loop $dyloop
          (br_if $dyblock (i32.gt_u (get_local $dy) (i32.const 2)))

          ;; for (let dx = 0; dx <= 2; dx++) {
          (set_local $dx (i32.const 0))
          (block $dxblock (loop $dxloop
            (br_if $dxblock (i32.gt_u (get_local $dx) (i32.const 2)))

            ;; if (memory[offset + x + dx - 1 + (y + dy - 1) * width] === 1) {
            (set_local $temp1 (i32.sub (i32.add (get_local $x) (get_local $dx)) (i32.const 1))) ;; x + dx - 1
            (set_local $temp2 (i32.sub (i32.add (get_local $y) (get_local $dy)) (i32.const 1))) ;; y + dy - 1
            (set_local $temp3 (i32.add (i32.add (get_local $offset) (get_local $temp1)) (i32.mul (get_local $temp2) (get_local $width))))
            (if (i32.eq (i32.load (i32.mul (get_local $temp3) (i32.const 4))) (i32.const 1)) (then
              ;; neighbors += 1;
              (set_local $neighbors (i32.add (get_local $neighbors) (i32.const 1)))
            ))

            (set_local $dx (i32.add (get_local $dx) (i32.const 1)))
            (br $dxloop)
          ))
          (set_local $dy (i32.add (get_local $dy) (i32.const 1)))
          (br $dyloop)
        ))

        ;; if (memory[offset + x + y * width] === 0) {
        (set_local $temp1 (i32.add (i32.add (get_local $offset) (get_local $x)) (i32.mul (get_local $y) (get_local $width))))
        (if (i32.eqz (i32.load (i32.mul (get_local $temp1) (i32.const 4))))
          (then
            ;; if (neighbors === 3) {
            (if (i32.eq (get_local $neighbors) (i32.const 3))
              (then
                ;; memory[nextOffset + x + y * width] = 1;
                (set_local $temp1 (i32.add (i32.add (get_local $nextOffset) (get_local $x)) (i32.mul (get_local $y) (get_local $width))))
                (i32.store (i32.mul (get_local $temp1) (i32.const 4)) (i32.const 1))
              )
              (else
                ;; memory[nextOffset + x + y * width] = 0;
                (set_local $temp1 (i32.add (i32.add (get_local $nextOffset) (get_local $x)) (i32.mul (get_local $y) (get_local $width))))
                (i32.store (i32.mul (get_local $temp1) (i32.const 4)) (i32.const 0))
              )
            )
          )
          (else
            ;; if (neighbors <= 2 || 5 <= neighbors) {
            (if (i32.le_u (get_local $neighbors) (i32.const 2))
              (then
                ;; memory[nextOffset + x + y * width] = 0;
                (set_local $temp1 (i32.add (i32.add (get_local $nextOffset) (get_local $x)) (i32.mul (get_local $y) (get_local $width))))
                (i32.store (i32.mul (get_local $temp1) (i32.const 4)) (i32.const 0))
              )
              (else
                (if (i32.le_u (i32.const 5) (get_local $neighbors))
                  (then
                    ;; memory[nextOffset + x + y * width] = 0;
                    (set_local $temp1 (i32.add (i32.add (get_local $nextOffset) (get_local $x)) (i32.mul (get_local $y) (get_local $width))))
                    (i32.store (i32.mul (get_local $temp1) (i32.const 4)) (i32.const 0))
                  )
                  (else
                    ;; memory[nextOffset + x + y * width] = 1;
                    (set_local $temp1 (i32.add (i32.add (get_local $nextOffset) (get_local $x)) (i32.mul (get_local $y) (get_local $width))))
                    (i32.store (i32.mul (get_local $temp1) (i32.const 4)) (i32.const 1))
                  )
                )
              )
            )
          )
        )

        (set_local $x (i32.add (get_local $x) (i32.const 1)))
        (br $xloop)
      ))
      (set_local $y (i32.add (get_local $y) (i32.const 1)))
      (br $yloop)
    ))

    ;; memory[0] = memory[0] === 0 ? 1 : 0;
    (if (i32.eqz (i32.load (i32.const 0)))
      (then
        (i32.store (i32.const 0) (i32.const 1))
      )
      (else
        (i32.store (i32.const 0) (i32.const 0))
      )
    )
  )
)

こんなに長くなっちゃって何が嬉しいんだと思うかもしれませんが、バイナリ(wasm)に変換すればサイズはちゃんとJSの25%ほどになっています。実行速度は分かりませんが、ファイルダウンロード後に実行を開始するまでの時間も短くなっているはずです。

$ wabt/out/clang/Debug/wat2wasm gol.wat
$ ls -lhS gol.*
-rw-r--r--  1 yasushiando  staff   5.4K 12 16 04:18 gol.wat
-rw-r--r--  1 yasushiando  staff   1.7K 12 16 03:18 gol.js
-rw-r--r--  1 yasushiando  staff   486B 12 16 04:14 gol.wasm

実行


WebAssemblyの関数を実行するにはJavaScriptから呼び出す必要があります。JavaScript側のソースコードは以下のとおりです。

const width = 18;
const height = 11;
const memory = new WebAssembly.Memory({initial:1});
const cells = new Uint32Array(memory.buffer, 0, width * height * 2 + 1);
// cellsに初期状態を設定(省略)

// コンソールに結果を表示
function show() {
  const mem = new Uint32Array(memory.buffer);
  const offset = mem[0] === 0 ? 1 : width * height + 1;
  let world = '\n';
  for (let y = 0; y < height; y++) {
    let line = '';
    for (let x = 0; x < width; x++) {
      line += mem[offset + x + y * width] === 1 ? '*' : ' ';
    }
    world += line + '\n';
  }
  console.log(world);
}

// WebAssemblyに受け渡す処理とメモリ
var importObject = {
  console: {
    log: console.log
  },
  js: {
    mem: memory
  }
};

// wasmの取得、コンパイル、関数呼び出し
fetch('gol.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  const nextStep = results.instance.exports.nextStep;
  setInterval(() => {
    nextStep(width, height);
    show();
  }, 1000);
});

WebAssembly.instantiate関数でwasmをコンパイルできます。この関数の第二引数はWebAssembly内でimportされるオブジェクトです。余談ですが、ここで{console: {log: console.log}}を渡しておくとWebAssembly側の変数をコンソールに表示できてプリントデバッグがはかどります

instance.exports.nextStepなどとしてWebAssemblyの公開関数を取り出して利用できます。

WebAssembly.instantiateの代わりにWebAssembly.instantiateStreamingを使用すると読み込みとインスタンス化を同時に行えるはずですが、ファイルのmimetypeを設定する必要があり、今回は簡易の開発サーバーでホストしていたので使用しませんでした。

デモ


デモはこちらです。結果はコンソールに表示されます。

https://technohippy.github.io/wasmgol/

さいごに


JavaScriptからWebAssemblyへの置き換えは考えていたよりもずっと簡単でした。WebAssemblyにコンパイルできるオレオレウェブ言語を作成してみても面白いかもしれません。ていうか多分やる。

株式会社カブクではWebGLやWebAssemblyを業務で使用したいメンバーを募集しています。

その他の記事

Other Articles

2019/08/14
Maker Faire Tokyo 2019でARゲームを出展しました

2019/07/25
夏休みだョ!WebAssembly Proposal全員集合!!

2019/07/08
鵜呑みにしないで! —— 書籍『クリーンアーキテクチャ』所感 ≪null 篇≫

2019/07/03
W3C Workshop on Web Games参加レポート

2019/06/28
TypeScriptでObject.assign()に正しい型をつける

2019/06/25
カブクエンジニア開発合宿に行ってきました 2019夏

2019/06/21
Hola! KubeCon Europe 2019の参加レポート

2019/06/19
Clean Resume きれいな環境できれいな履歴書を作成する

2019/05/20
[Web フロントエンド] 状態更新ロジックをフレームワークから独立させる

2019/04/16
C++のenable_shared_from_thisを使う

2019/04/12
OpenAPI 3 ファーストな Web アプリケーション開発(Python で API 編)

2019/04/08
WebGLでレイマーチングを使ったCSGを実現する

2019/04/02
『エンジニア採用最前線』に感化されて2週間でエンジニア主導の求人票更新フローを構築した話

2019/03/29
その1 Jetson TX2でk3s(枯山水)を動かしてみた

2019/03/27
任意のブラウザ上でJestで書いたテストを実行する

2019/02/08
TypeScript で “radian” と “degree” を間違えないようにする

2019/02/05
Python3でGoogle Cloud ML Engineをローカルで動作する方法

2019/01/18
SIGGRAPH Asia 2018 参加レポート

2019/01/08
お正月だョ!ECMAScript Proposal全員集合!!

2019/01/08
カブクエンジニア開発合宿に行ってきました 2018秋

2018/12/25
OpenAPI 3 ファーストな Web アプリケーション開発(環境編)

2018/12/23
いまMLKitカスタムモデル(TF Lite)は使えるのか

2018/12/21
[IoT] Docker on JetsonでMQTTを使ってCloud IoT Coreと通信する

2018/12/11
TypeScriptで実現する型安全な多言語対応(Angularを例に)

2018/12/05
GASでCompute Engineの時間に応じた自動停止/起動ツールを作成する 〜GASで簡単に好きなGoogle APIを叩く方法〜

2018/12/02
single quotes な Black を vendoring して packaging

2018/11/14
3次元データに2次元データの深層学習の技術(Inception V3, ResNet)を適用

2018/11/04
Node Knockout 2018 に参戦しました

2018/10/24
SIGGRAPH 2018参加レポート-後編(VR/AR)

2018/10/11
Angular 4アプリケーションをAngular 6に移行する

2018/10/05
SIGGRAPH 2018参加レポート-特別編(VR@50)

2018/10/03
Three.jsでVRしたい

2018/10/02
SIGGRAPH 2018参加レポート-前編

2018/09/27
ズーム可能なSVGを実装する方法の解説

2018/09/25
Kerasを用いた複数入力モデル精度向上のためのTips

2018/09/21
競技プログラミングの勉強会を開催している話

2018/09/19
Ladder Netwoksによる半教師あり学習

2018/08/10
「Maker Faire Tokyo 2018」に出展しました

2018/08/02
Kerasを用いた複数時系列データを1つの深層学習モデルで学習させる方法

2018/07/26
Apollo GraphQLでWebサービスを開発してわかったこと

2018/07/19
【深層学習】時系列データに対する1次元畳み込み層の出力を可視化

2018/07/11
きたない requirements.txt から Pipenv への移行

2018/06/26
CSS Houdiniを味見する

2018/06/25
不確実性を考慮した時系列データ予測

2018/06/20
Google Colaboratory を自分のマシンで走らせる

2018/06/18
Go言語でWebAssembly

2018/06/15
カブクエンジニア開発合宿に行ってきました 2018春

2018/06/08
2018 年の tree shaking

2018/06/07
隠れマルコフモデル 入門

2018/05/30
DASKによる探索的データ分析(EDA)

2018/05/10
TensorFlowをソースからビルドする方法とその効果

2018/04/23
EGLとOpenGLを使用するコードのビルド方法〜libGLからlibOpenGLへ

2018/04/23
技術書典4にサークル参加してきました

2018/04/13
Python で Cura をバッチ実行するためには

2018/04/04
ARCoreで3Dプリント風エフェクトを実現する〜呪文による積層造形映像制作の舞台裏〜

2018/04/02
深層学習を用いた時系列データにおける異常検知

2018/04/01
音声ユーザーインターフェースを用いた新方式積層造形装置の提案

2018/03/31
Container builderでコンテナイメージをBuildしてSlackで結果を受け取る開発スタイルが捗る

2018/03/23
ngUpgrade を使って AngularJS から Angular に移行

2018/03/14
Three.jsのパフォーマンスTips

2018/02/14
C++17の新機能を試す〜その1「3次元版hypot」

2018/01/17
時系列データにおける異常検知

2018/01/11
異常検知の基礎

2018/01/09
three.ar.jsを使ったスマホAR入門

2017/12/17
Python OpenAPIライブラリ bravado-core の発展的な使い方

2017/12/14
AngularJS を Angular に移行: ng-annotate 相当の機能を TypeScrpt ファイルに適用

2017/12/08
Android Thingsで4足ロボットを作る ~ Android ThingsとPCA9685でサーボ制御)

2017/12/06
Raspberry PIとDialogflow & Google Cloud Platformを利用した、3Dプリンターボット(仮)の開発 (概要編)

2017/11/20
カブクエンジニア開発合宿に行ってきました 2017秋

2017/10/19
Android Thingsを使って3Dプリント戦車を作ろう ① ハードウェア準備編

2017/10/13
第2回 魁!! GPUクラスタ on GKE ~PodからGPUを使う編~

2017/10/05
第1回 魁!! GPUクラスタ on GKE ~GPUクラスタ構築編~

2017/09/13
「Maker Faire Tokyo 2017」に出展しました。

2017/09/11
PyConJP2017に参加しました

2017/09/08
bravado-coreによるOpenAPIを利用したPythonアプリケーション開発

2017/08/23
OpenAPIのご紹介

2017/08/18
EuroPython2017で2名登壇しました。

2017/07/26
3DプリンターでLチカ

2017/07/03
Three.js r86で何が変わったのか

2017/06/21
3次元データへの深層学習の適用

2017/06/01
カブクエンジニア開発合宿に行ってきました 2017春

2017/05/08
Three.js r85で何が変わったのか

2017/04/10
GCPのGPUインスタンスでレンダリングを高速化

2017/02/07
Three.js r84で何が変わったのか

2017/01/27
Google App EngineのFlexible EnvironmentにTmpfsを導入する

2016/12/21
Three.js r83で何が変わったのか

2016/12/02
Three.jsでのクリッピング平面の利用

2016/11/08
Three.js r82で何が変わったのか

2016/12/17
SIGGRAPH 2016 レポート

2016/11/02
カブクエンジニア開発合宿に行ってきました 2016秋

2016/10/28
PyConJP2016 行きました

2016/10/17
EuroPython2016で登壇しました

2016/10/13
Angular 2.0.0ファイナルへのアップグレード

2016/10/04
Three.js r81で何が変わったのか

2016/09/14
カブクのエンジニアインターンシッププログラムについての詩

2016/09/05
カブクのエンジニアインターンとして3ヶ月でやった事 〜高橋知成の場合〜

2016/08/30
Three.js r80で何が変わったのか

2016/07/15
Three.js r79で何が変わったのか

2016/06/02
Vulkanを試してみた

2016/05/20
MakerGoの作り方

2016/05/08
TensorFlow on DockerでGPUを使えるようにする方法

2016/04/27
Blenderの3DデータをMinecraftに送りこむ

2016/04/20
Tensorflowを使ったDeep LearningにおけるGPU性能調査

→
←

関連職種

Recruit

バックエンドエンジニア(Python・Go)

業務内容

当ポジションは弊社Webサービスのバックエンド機能設計及び実装を担当します。 サービス毎の開発チームで2週間スプリントのスクラム開発を実施しています。 週次で開発チームミーティングを実施し、実装設計の相談や工数見積もりを行います。 全ての開発コードはレビューと自動テストによって品質を保っています。 また、リファクタリングやフレームワークのバージョンアップも開発フローに組込み、技術的負債を放置しない開発を目指しています。

フロントエンドエンジニア(TypeScript)

業務内容

当ポジションは弊社Webサービスのフロントエンド機能設計及び実装を担当します。 サービス毎の開発チームで2週間スプリントのスクラム開発を実施しています。 週次で開発チームミーティングを実施し、実装設計の相談や工数見積もりを行います。 全ての開発コードはレビューと自動テストによって品質を保っています。 また、リファクタリングやフレームワークのバージョンアップも開発フローに組込み、技術的負債を放置しない開発を目指しています。

インターン(Webエンジニア)

業務内容

業務から独立した、調査・研究系のタスクをおまかせしています。コードレビュー、 社内での報告会、 ブログ記事執筆を通して着実にスキルアップしていただくことを目指しています。 (希望があれば、プロダクトの開発業務もおまかせします。)

→
←

お客様のご要望に「Kabuku」はお応えいたします。
ぜひお気軽にご相談ください。

お電話でも受け付けております
03-6380-2750
営業時間:09:30~18:00
※土日祝は除く