くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

OpenCVを用いたCとDjangoの特徴検出について

みなさんこんにちは、エンジニアのツヅキです。
突然ですが問題です。 みんなのマーケットのオフィスにはCDjangoという名前の猫が二匹います。
下記に2枚の画像がありますが、どちらかがCでどちらかがDjangoの写真です。
さて、どちらがCでどちらがDjangoでしょうか?

画像1 画像2

正解は、最初の画像がCで後の画像がDjangoです。
どちらがCで、どちらがDjangoかわかりましたか?(私はわかりませんでした)
そんな、どちらがどちらかわからない私のような人間はふと思います。
顔の特徴などがわかったら、2匹を判別できるのではないだろうか。。。。
さあ、ここからが本題、2匹の猫の特徴をOpneCVインテルが開発・公開したオープンソースのコンピュータビジョン向けライブラリ)[1]を用いてCDjangoの顔画像の特徴を調べていきます。

利用する画像について

今回は、4枚の画像を利用しました。

画像1 画像3 画像2 画像4jango

利用するプログラミング言語およびパッケージについて

利用する言語

  • Python3

    利用するライブラリ

  • OpenCV3

    特徴を調べる

    今回は、4枚の画像の特徴を調べるために、特徴点というものを探していきます。

特徴点とは、際立って検出できる画像上の点のことをいう。[1]

らしいです。
ということで、特徴を見ていきましょう!
特徴を検出するプログラムを以下に示します。

akaze = cv2.AKAZE_create()
face_feature = akaze.detect(img)
photo_face  = cv2.drawKeypoints(img, face_feature, None, flags=2)
cv2.imwrite(“test.jpg", photo_face)

上記のプログラム[2]によって検出された2匹の特徴を以下に示します。

c1 c2 d1 d2

上の2枚はCの特徴を検出したものです。2枚目の画像に関しては背景の画像の特徴も捉えてしまっていますが、おでこのあたりに赤の値が高めの特徴を多く捉えていることがわかります。
また、下の2枚はDjangoの特徴を検出したものです。おでこのあたりに緑の値が高めの特徴を多く捉えていることがわかります。

特徴を一致させる

先ほど特徴を調べましたが、今度は別の2匹の画像の特徴を一致させてみます。 特徴を一致させるプログラムの一部を以下にします。

akaze = cv2.AKAZE_create()
bf = cv2.BFMatcher()
kp1, des1 = akaze.detectAndCompute(img1, None)
kp2, des2 = akaze.detectAndCompute(img2, None)
matches = bf.knnMatch(des1, des2, k=2)
cat = []
for m, n in matches:
    if m.distance < 0.75 * n.distance:
        cat.append([m])
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, cat, None, flags=2)
cv2.imwrite(“test.jpg", img3)

上記のプログラム[3]によって検出された2匹の特徴を一致させた画像を以下に示します。

c d

上の画像が、Cの特徴を一致させた画像です。おでこのあたりに赤の特徴が一致したことを示す線と鼻のあたりに緑の特徴が一致したことを示す線があります。
また、下の画像が、Djangoの特徴を一致させた画像です。おでこから目にかけて緑の特徴が一致したことを示す線と、鼻のあたりに橙色の特徴が一致したことを示す線があります。

結果

2匹の顔画像から特徴を検出することができました。
Cは赤色が多めで、Djangoは緑色が多めということがわかりました。
これにより、人間の目で2匹を区別しなくても、特徴点から2匹を判別することができるようになりました。
この技術を応用すれば画像を自動で判別し
「写真に写っているのは人の顔なのか」
「誰が写っている写真なのか」
などがわかるようになるかもしれません。
(これは簡単な特徴を検出しただけなので、この特徴が全てではありません。
また、顔画像ではなくて、背景の中に2匹がいたらどうなるの?とか、他の猫を認識させたらどうなるの?とかはまた別のお話です。)

最後に

私は今、上記の技術を応用して、くらしのマーケットのサイトで活用できるよう奮闘中です。
私たちテックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています。ご興味のある方、ご応募お待ちしております!
www.minma.jp

参考文献

[1] https://ja.wikipedia.org/wiki/OpenCV
[2] https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%8A%E3%83%BC%E6%A4%9C%E5%87%BA%E6%B3%95
[3] https://qiita.com/hitomatagi/items/caac014b7ab246faf6b1

スクラム導入後のその後

エンジニアののりすけです。以前このブログで紹介されたスクラム ( https://tech.curama.jp/entry/2018/05/18/140000 ) についてのその後のお話です。

導入した経緯などは過去の記事を見てもらえればと思いますが、私自身もどんな変化をもたらしたのか整理する意味も込めてまとめてみたいと思います。

記事時点から変わった点・継続している点

スプリント期間の変更

記事の時点ではスプリント期間を1週間としていましたが、あまりにも短いため、2週間のスプリントを試し、現在ではスプリント期間を2週間としています。

様々な書籍などでも2週間のスプリントを推奨されていますが、個人的には1週間のスプリントを以下の点から経験できてよかったと感じています。

  • スクラムという新しい動きを短い時間の中で一気に経験できた
  • スプリント期間を短くしたため毎週KPTを使った振り返りができた

KPTは継続して実施

スクラムのメリットとして上がっていたKPTですが継続して実施し、いろいろな問題点をチームで共有、解決してきました。これらの積み重ねがチームの経験として溜まってきているように感じています。

当初はKeepやProblemをどう書けばいいかなどもありましたが、現在では具体的な問題だけでなく、漠然と「なんとなくおかしい」など抽象的な意見があがってもその問題を具体化する動きや、意見の上がったことに対して何かしらの策を講じたりなどチーム自らで改善を促す動きができるようになってきました。

ツールの変更(Trello -> Zenhub)

当初ストーリーの管理をTrelloで管理していましたが、スクラムを継続しているうちに大きいユーザーストーリーの管理をどうするかという問題に突き当たってしまいました。そもそも大きすぎるストーリー自体を避けるべきなど、様々なやり方を考えては見ましたが、Epicというストーリーをまとめる機能をもつZenhubというGitHub上で動くツールに変更しています。

不満点を上げるとTrelloのサクサク動く感じから少しもっさりとした動作が個人的には気になりますが、機能面では事足りている状況です。

受入れ条件の記述が良くなった

オーナーが記述するユーザーストーリーには受け入れ条件が含まれています。我々エンジニアはこの条件を満たすように設計/実装を行うのですが、記述内容についても時間の経過に連れて変化してきました。

スクラムを始めた当初、受け入れ条件についてどう記述すべきかいろいろ分からない部分もあり単純な情報しか書かれていませんでした。スクラム運用を進めるうちに様々な失敗(受け入れ条件漏れなど)を経験し記述方法を少しずつ修正することでエンジニア視点からもわかりやすい受け入れ条件が書かれるようになりました。

ストーリーの規模も違うので一概には言えませんがスクラム運用をはじめた当初はほとんどのストーリーで数個しかなかった条件が、現在では十数個ないし30近く条件が設定されているストーリーも存在しています。

これらの修正はKPTの振り返りで「あのストーリーの受け入れはこう書くべきだった」などの意見がでたり、エンジニアからも「これを受け入れに含めてほしい」などオーナーとチームメンバー双方で受け入れ条件をちゃんと確認する習慣もできてきています。

スプリントプランニングの変化

当初の状態から大きく変化したものの1つがこのスプリントプランニングだと思っています。当初はとにかく口頭で「こんなことやる必要あるよね」などの会話を行いそこからプランニングポーカーでストーリーポイントを振っていました。

このやり方でも何度か試すうちにお互いの考えていることの理解が深まり、見積もりを行うポイントは近づいていったのですが、いざスプリントが始まるとプランニング時に想定していた事と異なるケースが発生したり、しまいにはプランニング時に何を話したか忘れるなど、どうしようもない事が発生したりしていました。

これらのことを踏まえてプランニング時に検討するべきストーリーについて設計面までチームで行った上で見積もりを行うようにしています。 設計といっても具体的には実際のストーリーを満たす実装を行うためのドメインモデルやDBモデルをプランニング時にホワイトボードに書き出すなどあくまでラフスケッチレベルの物ですが、これを行うことで見積もりの精度があがったり、プランニング時にチーム全員で一度設計を考えているため実装にスムーズに入れるなどのメリットを感じています。

また副次的な効果で設計に関するノウハウがチーム全員の学びになっているなど各メンバーのスキルアップにもつながる効果が出ていると思っています。

必要に応じたペア・モブプログラミングの実施

スクラムチーム内でのスキルのばらつきを解消するため率先してペア・モブプログラミングを行ってきました。こちらについては現時点でも行っていますが当初ほど頻繁には行っていません。特に言語化したルールのようなものはありませんが、初めて実施しようとすることだったり、実装に自身が無い場合などは今まで通り率先してペア・モブプログラミングを行っています。

頻繁に行われなくなった理由の1つは、前述の通りプランニングで大枠の設計もある程度決めているので実装時に不安に思わなくなってきているというのもあるのかもしれません。

なお、みんまでペアプロする場合はVSCode + Live Shareを組み合わせて行っています。

まとめ

まだスクラムを開始してから1年も経っておらず、改善すべき点はあるのかもしれませんが、途中段階としてアウトプットしたことで様々な変化があったことが改めてわかりました。最後に現時点状況を簡単にまとめます。

KPT

  • Keep 75件
  • Problem 101件
  • Try 39件

スクラムを開始した4月から現時点で累計101件の問題点をチームで上げ、その中ですぐに解決すべきとして改善したものが39件という結果でした。

ベロシティの推移

f:id:curama-tech:20180928095057p:plain

改めて見ると安定しているとは言い難いですが、ベロシティが上がらなかったスプリントの振り返りで原因などはつかめているので良しとしましょう。

我々みんなのマーケットテックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています!興味がある方はぜひ気軽に連絡ください (コーポレートサイト https://www.minma.jp/

くらしのマーケット SREチームの事例紹介

f:id:curama-tech:20180920181955p:plain

こんにちは、SREチームの千代田です。

今回はくらしのマーケットにおけるSREチームの事例を紹介します。

SREチームについて

みんなのマーケットでは今年、SREチームを新設しました。

背景としてはいくつかありますが、

  • Dev VS Opsの対立を避け、高速なサイクルでデプロイを安全に行えるようにしたい
  • オペレーションを自動化し、サービスが拡大しても少人数で回せるようにしたい
  • 本番環境だけでなく、開発速度向上のために開発環境の改善を進めたい
  • 障害対応できるポジションを増やしたい
  • もともと一部のエンジニアとCTOが対応するといった形で、チームとしての体制が組まれておらず属人化が発生している
  • 情報の共有方法に問題があり、対応したエンジニアしか詳細を知らない
  • 誰かが入れたサービスだけど、それを入れた詳しい人がもういない
  • 昔誰かがやったことなのにそのときの手順が存在しない

などといったものが主な要因として挙げられます。
具体的なこれらの改善策についてはこの後紹介をします。

設立時の目的として、
上記問題点の解決と、「開発業務を行いつつ、運用の自動化」を置きました。
ですが、半年経った今では既存のマイクロサービスの改善など新たな目的も増えており、今後も変化していく予定です。

事例紹介

監視基盤の強化

監視基盤としてもともとはCloudWatchメトリクスを用いていましたが、
計測出来ない値があるなど機能的に足りていないものを補うために、PrometheusとGrafanaを導入しました。
これにより、サーバメトリクスだけでなく、アプリケーションやミドルウェアのメトリクスも取得ができるようになり、監視対象の幅が広がりました。
また、複数の要因によってエラーが出た際に、エラー箇所の切り分けが容易になりました。

ポストモーテムの導入

障害があった際に、ポストモーテムを書いてGitHubへコミットしたうえで、そのURLをSlackで共有するようにしました。
これにより、以前起こった障害が記録されていくと同時に、
全エンジニアは障害がなぜ起きたのか、どのようにして解決されたのか把握できるようになりました。
さらに、失敗から学ぶことにより再発の防止と検知(アラートを導入するなど)にも役立っています。

開発環境の整備

私が入社した際に経験をしたのですが、開発環境の構築に2日ほど時間がかかりました。
原因としては、ドキュメントがあまり更新されておらず足りない手順が存在した為でした。
その為、思い切ってDockerを導入してローカルの開発ではコンテナを使うようにしました。
なぜDockerを利用することにしたかというと、個々人での設定を極力減らしておきたかったのと、
新しく作成するマイクロサービスがDockerを利用するといった理由です。
結果として、導入に成功し、以前より簡単に開発が行えるようになりました。

権限周りの整備

くらしのマーケットではクラウドプロバイダとしてAWSを主に利用しています。
AWSではIAMと呼ばれるAWS リソースへのアクセスを安全に制御するためのウェブサービスが提供されています。
こちらのサービスを使う際に、Naming Ruleが特に決まっておらず、作る人によってバラバラになっていたものをルールを決めて統一しました。
また、権限が適切ではないものに関しては、
必要に応じてリソースレベルやアクションに対してのみ許可をするなど、権限の見直しを実施しました。
さらに、エンジニアによって権限が異なった状態であったものをグループ単位に分割・権限を集約するなどして、
エンジニアによって権限が異なるといった状態を防ぐようにしました。
これにより、新しくエンジニアが入った際にグループへと追加するだけでよくなり、すぐさま開発へ参加できるようになりました。

データベースの移行

くらしのマーケットでは、データベースにPostgresを利用しています。
もともとはRDSを利用していましたが、Auroraにするとパフォーマンスが最大で5倍出るとのことでしたので、移行することを決意しました。
その際、バージョンを上げた場合の動作なども合わせて検証し、Postgres9.4 -> 9.6へのバージョンアップも移行と同時に行いました。
これによってパフォーマンスが向上し、さらにはパフォーマンスインサイトが利用できるようになり、クエリの可視化が行えるようになりました。
また、一部参照系クエリを読み込みエンドポイントに逃すことにより、さらなる高速化も行えました。

CDNの導入

静的なコンテンツを配信する上でくらしのマーケットのサーバへのリクエストの負荷を減らす目的として、CDNを導入しました。
さまざまなCDNサービスがありますが、今回はAWSのCloudFrontを利用しました。
これにより、CloudFront Popular Objects Reportを利用し、
BytesFromMissesを用いてファイルサイズが大きいものを容易に判別するといったことが可能になりました。
また、当初の目的であるリクエストの負荷を減らすことにも成功しました。

コンテナでの開発ツール作成

くらしのマーケットではマイクロサービスアーキテクチャを採用しています。
既存のマイクロサービスは特にコンテナを使っていなかったのですが、
新しいマイクロサービスを作る際にコンテナで実行したいといった話があり、実際にコンテナでの開発基盤を構築することにしました。
コンテナの基盤としてはKubernetesの採用を考えたのですが、
学習コストの高さや、運用面での不安をカバーしきれなかった為、AWSのECSを利用することにしました。
ECSを利用する上で、ネックとなったのが、既存のマイクロサービスとの結合部分でした。
くらしのマーケットの開発環境ではブランチ別の開発環境(通称 tako環境)を利用しています。
ブランチ単位にすべてのマイクロサービス/データベース/キャッシュ...etcが存在するのですが、
ECS側とのルーティングをどのように実装するかいろいろと考えた結果、くらしのマーケットに特化したデプロイツールが必要な為開発を行うことにしました。
AWSのALBのホスト名でのルーティング機能を利用することを前提に、
TargetGroupの作成やRuleの追加もマネージされたデプロイツールをPythonで実装しました。
また、既存の開発環境へデプロイする際に利用されるAnsibleからでも実行しやすいようにコンテナでラップして使えるようにしました。

ログ基盤の改善

くらしのマーケットではログ基盤としてELKスタックを利用しています。
しかしながら、構築されてから時間が経っている為、ログの取りこぼしやELKスタックのバージョンが古いといった問題がありました。
そのため、手始めにログ基盤の改善としてELKスタックのアップグレードを行いました。
さらに、取りこぼされていたログを入れるようにtemplateを改善しました。
また、ECSやALBのログをCloudWatch Logsに流していたのですが、
Kibanaで見れたほうが串刺しにしやすく便利であった為、ElasticSearchへ取り込むようにしました。
もともとエラーの確認ぐらいでしか使われていませんでしたが、今回は新たにVisualize等を追加してもっとログを有効活用できるようにしました。

  • URLに対してのバックエンドでのリクエスト数をカウントして、上位のURLをPieとして表示
  • Producer/Consumerパターンで、Consumerが実行したTask数をLineとして表示

など、さまざまな箇所を改善しました。
結果として、バックエンドでのリクエストが多いものについてはSREチームで改善のissueを作成し、対応するなど以前よりも有効的に活用しています。

現状抱えている課題

現状抱えている課題として次のものが挙げられます。

  • 分散トレーシングが整備されていない
  • perf/DTrace/SystemTapに詳しい人材が不足している
  • 昔書かれたコードで、技術的負債が存在している

おわりに

以上、SREチームの事例としていくつか紹介させていただきました。
事例として参考になればうれしいです。
また、今後もやることがまだまだ山積みになっている認識ですが、少しずつでも改善していければと思っています。

最後までお読みいただき、ありがとうございました。

もし、SREに興味がある、ワタシlinuxチョットデキル、みんまで働いてみたい
といった方がいらっしゃいましたら、ぜひ一度オフィスでお会いしましょう

次回は、エンジニアののりすけさんの予定です。

打ち出し顔文字、下から見るか?横から見るか?

みんなのマーケットでアプリエンジニアとして働いている楊です。

先日アプリ側の絵文字のフィルターを実装してみました。不著名な歴史偽ファンとしての私はその切っ掛けで絵文字について色々調べました。

みなさんご存知ですか。実際絵文字の利用はAndroidならAndroid 4.3からで、iOSなら日本以外の国はiOS 5.0からです(日本は絵文字の発明国としてiOS2.2から)。 そこまでの時代は本ライトブログ(ライトブログとは読む気分が楽なブログであることーーBy Dr.Yang)の本題ーー絵文字の兄貴「顔文字」を使っていました。

顔文字はインタネットへの登場は1982年にカーネギーメロン大学のコンピュータサイエンティストのスコットさんが電子掲示板に初めて「:-)」と「:-(」を使ったのが起源と言われています。 これは横倒しても当時の読者はすぐ「笑顔」と「憂鬱」と識別できました。

相手に対して文書よりもっと受け取りやすいし、生き生きとして見えるし、顔文字の運用はすぐ爆発して行きました。 その中に欧米派の横倒しにした顔文字「:-D」「B-P)」とアジア派の正位置の顔文字「(๑˃́ꇴ˂̀๑)」「(ノ´∀`*)マターリ」二種類が分けています(無理やりですが、タイトルのネタの由来)。

ここまではただごとですが、次の質問をすればいかがでしょうか。

そもそもなぜ人間はこれらの横倒でも正位置でもの記号を顔と繋がっているんですか。

この問題を考えながら下の画像をご覧ください。

ヘイケガニ f:id:curama-tech:20180914142404p:plain

これはヘイケガニと呼ばれるカニの一種です。壇ノ浦の戦い(1185年)で敗れて海に散った平氏の無念をなぞらえ、「平氏の亡霊が乗り移った」という伝説が生まれて(出典:https://ja.wikipedia.org/wiki/%E3%83%98%E3%82%A4%E3%82%B1%E3%82%AC%E3%83%8B)、この人間の怒った顔に似た模様が出ている甲羅を持っているカニを命名しました。

これだけじゃなくて、実際科学者の研究によって、2個の円(目)と一本の線(口)を三角の位置に書いたら、人間は素早くためらいなく「顔」と認めます。

これは偶然ではありません。パレイドリア(英: Pareidolia)という心理現象であることです。ソーシャルネットでよく見たこのような面白い画像もパレイドリア現象です。

面白いネット画像 f:id:curama-tech:20180914142449p:plain

ざっとみる全部表情が誇張された顔です。少し考えてフルーツや野菜、日用品などに認知されました。つまり、人間の脳は視覚的なイメージから顔を認識することは意識よりも速いです。

人間の意識は前頭葉から形成されます。一方、目から出した視覚情報は後頭葉、そして側頭葉、さらに前頭葉へと流れます。パレイドリア現象は後頭葉と側頭葉に行う情報事前処理の結果です。一番大事な情報の一つは顔です。周りの人間の顔はよく現在の状況を示せますから。紡錘状回が早く識別したら、有利な行動が採用できます。

人間の頭 f:id:curama-tech:20180914142509p:plain

紡錘状回の機能について、もう一個の例を挙げたいと思います。大体の人はサッチャー錯視(英: Thatcher illlusion)現象があって、この画像を一見して問題がないです。

サッチャー錯視1 f:id:curama-tech:20180914142530p:plain

正位置に回転したら、左側の顔に目と口は逆転されていると発覚できました。例えばあるひとの紡錘状回は損傷があって、相貌失認の患者になれば、この二つの顔はそのひとの目から見ると特に違うところがありません。顔の各部分の配置が検出できないので、顔は区別できません。

サッチャー錯視2 f:id:curama-tech:20180914142549p:plain

中世ヨーロッパにかなり流行していた茶葉占い(英: tasseography)もパレイドリア現象を利用しました。やり方は飲み残しの茶葉の形を観察して、どのように見えるかによって過去、現在そして未来を占います。

茶葉占い f:id:curama-tech:20180914142609p:plain

このような占いは基本的にパレイドリアに基づいて、実際反映したのは客観的な事実ではなく、主観的な心理です。1920年代にスイスの精神療法家ヘルマン・ロールシャッハは「ロールシャッハ・テスト」を提出しました。被験者にインクのしみを見せて一見で何を想像するか、再度見ると何を想像するかを話せて、その話から被験者の心理状況を分析します。残念ですが、このテストは正直に言ったら、占いの占いで、科学ではありません。

科学の領域に入れられない錯視ですが、芸術にはなかなか大切なものです。イタリアルネサンス時期有名な画家ジュゼッペ・アルチンボルドはパレイドリアを利用して、いろんな珍奇な肖像画を製作しました。400年後のシュルレアリスム画家サルバドール・ダリも関係のない物を集めて顔などを作るのが好きです。

庭師/野菜 f:id:curama-tech:20180914142623p:plain

ちなみに、人間のパレイドリアとは別物ですが、近年人工知能の発展により、パレイドリアはコンピュータビジョンの画像認識分野によく研究されています。画像認識プログラムは顔でないものを誤って顔と認識することをどうやって避けられるのは重要な課題です(めちゃくちゃな内容をいっぱい話して、やっと最後技術の話に回して戻って来ました)。

終わり

今回のライトブログのおかげで、一生使えない日本語をたくさん触れました。

すでに覚えていないですけど(╯‵□′)╯︵┻━┻

みんなのマーケットに興味がありましたら、ぜひこちらまでご連絡ください。

次回はSREエンジニアの千代田さんです。よろしくおねがいします。

最近ちまたでよく聞くWebAssemblyを触ってみた

f:id:curama-tech:20180907001400p:plain

みんなのマーケットでwebエンジニアとして働いている高橋です。

WebAssemblyはブラウザ上で実行できるバイナリフォーマットです。JavaScriptよりも高速に実行できるということは話に聞いていましたが、実際にどのくらい高速になるかは今まで試したことがありませんでした。今回はWebAssemblyに触れてみることから始めて、フィボナッチ数の計算をWebAssemblyとJavaScriptで実行し、それぞれの速度を見ていきます。

環境は

MacBook Pro (Retina, 15-inch, Mid 2014)
2.5 GHz Intel Core i7
16 GB 1600 MHz DDR3

で、Google Chrome 68上にて実行しています。

WebAssemblyをビルドしてみる

WebAssemblyはC/C++やRustからコンパイルすることができます。また、Mozillaが公開しているWebAssembly Studioを用いると、web上でWebAssemblyをビルドすることができます。今回はこれを使ってみます。

アクセスすると Create New Projectというダイアログが開かれるので、Empty C Projectを選んでください。

f:id:curama-tech:20180906211000p:plain 選択すると、

エディタ画面が開きます。 f:id:curama-tech:20180907001347p:plain

すでにCのプロジェクトのボイラープレートが作られているので、フィボナッチ数計算用関数を追加し、main関数から呼び出しましょう。

#define WASM_EXPORT __attribute__((visibility("default")))

int fibo(int n) {
  if (n == 0 || n == 1) {
    return 1;
  }
  return fibo(n - 1) + fibo(n - 2);
}

WASM_EXPORT
int main() {
  return fibo(40);
}

そして、右上のBUild & Runを押すと、ビルド後main.wasmファイルが生成され、 その後wasmが実行されて下のペインに結果が表示されます。(165580141と表示されるはずです。)

このように、WebAssembly Studioを用いると非常に簡単にwasmファイルを作成できます。

wasmファイル、watファイル

バイナリフォーマットのwasmファイルの他、WebAssemblyはデバッグやテストの容易さを担保するためにwasmファイルに変換可能なテキストフォーマットをサポートしています。関数本体はlinear representationという形式で表現されますが、中間形式としてはS式で表現されています(構文はこちらです)。WebAssembly Studioでは、wasmファイルを選択するとS式のテキストフォーマットを.watファイルとして直接編集できます。

f:id:curama-tech:20180907001356p:plain

C言語のソースにあったmain関数は、.watファイルでいうと

(func $main (export "main") (type $t2) (result i32)
  i32.const 40
  call $fibo)

という箇所にあたります。ここで40となっているのはfibo関数に与えた引数です。では、これを試しに5に変更してみましょう。 Runを押すと、右下のペインに何が表示されているでしょうか? 8と表示されていれば正しく反映されています。フィボナッチ数は1, 1, 2, 3, 5, 8なので値も正しそうです。

このmain関数をもう少し見ていきます。S式ということで、Lispのコメント;を借用して注釈をつけると、

(func $main             ; 関数定義
    (export "main")     ; mainをエクスポートする
    (type $t2)          ; 関数の型定義
    (result i32)        ; 返り値は32-bit integer
    i32.const 5         ; 5をスタックに積む
    call $fibo)         ; $fibo関数を呼ぶ

という処理になっていることがわかります。 また、.wasmファイルをダウンロードしてダンプしてみると、

00000: 41 05 00 00 00             | i32.const 5
000006: 10 01 00 00 00            | call 1
00000c: 0b                        | end

となり、S式と対応していることがわかります。

JavaScriptとの速度比較

次に、JavaScriptで記述したフィボナッチ関数と、wasmとの速度を比較してみましょう。

JavaScriptのコードは以下となります。

"use strict";

function run() {
    function fibo(n) {
        if (n == 0 || n == 1) {
            return 1;
        }
        return fibo(n - 1) + fibo(n - 2);
    }
    console.log(fibo(40));
}

それぞれブラウザ上で実行してみると、

JavaScript WebAssembly
fibo(40) 1781ms 523ms

ということで、WebAssemblyのほうが1781/523 = 約3.4倍速いという計測結果になりました。 CPUの負荷が大きい処理については今後はWebAssemblyを利用することによって高速なweb体験をユーザーに提供できるようになるかもしれません。

(おまけ)WebAssemblyをgolangから作成する

もしgo1.11の環境があれば、golangからもwasmを生成することができます。こちらの記事(https://qiita.com/cia_rana/items/bbb4112b480636ab9d87)を参考にgolangからwasmを生成してみました。

コードは、

package main

import "fmt"

func fibo(n int) int {
    if n == 0 || n == 1 {
        return 1
    }
    return fibo(n-1) + fibo(n-2)
}

func main() {
    fmt.Println(fibo(40))
}

そして、以下のコマンドでビルドすることができます。

GOOS=js GOARCH=wasm go build -o ./test.wasm wasm.go

こちらを実行した結果は以下になります。

JavaScript WebAssembly WebAssembly(golang)
fibo(40) 1781ms 523ms 5072ms

なぜかgolangから生成したwasmはJavaScriptよりも実行時間が長いですね。フィボナッチ数の計算だけではなく、 fizzbuzzを大量に実行したりしましたが、golangから生成したwasmはJavaScriptよりも遅いパターンしか計測できませんでした。 (この件に関しては深く追っていませんが、詳しい方は教えていただけると嬉しいです。)

おわりに

今回の記事ではWebAssemblyを紹介してみました。みんなのマーケットに興味がありましたら、ぜひこちらまでご連絡ください。

次回はアプリエンジニアのやんさんです。よろしくおねがいします。