60FPSへの夢、そしていかにキャッシュを従えるか / 書評「超速! Wepページ速度改善ガイド 使いやすさは「速さ」はじまる」

CAの ahomu/1000ch さんからご恵贈頂き、レビュー書いてくれと頼まれたので、自分のパフォーマンスというものへの思いの丈と共に書評を書こうと思う。

自分も6年ぐらいフロントエンドぐらいやってきたけど、あんまりちゃんと用語定義をしないまま「勘」で調査しがちだったのと、彼ら二人がどの数値見てるんだろう、という視点で読んだ。

gihyo.jp

要約: これはフロントエンドエンジニアの勘を体系化/言語化したものだ

フロントエンドエンジニアにとって Chrome DevTools は日常的にお世話になる、「これナシでは生きていけない」という類のツールだ。だが、これの読み方は癖があり、経験的に蓄積してきた積み重ねがなければやや難しい側面があった。

この本は、フロントエンドエンジニアが今まで経験的に蓄積してきたでろう「勘」を、Webの仕様や各社のUXガイドラインの明確な定義を通して再整理したものだ。

歴史的な話をすると、発想のオリジナルとなった Firebug, Webkitのデバッガの進化とともに一つ一つ機能が拡充されてきたが、今では膨大な機能を有していて、キャッチアップが難しくなってしまっていた。

それらにキャッチアップするマイルストーンとして、このタイミングで本を出す企画のタイミングが適切でうまいなーとも思った。

なぜ速度が大事か

  • モバイルの台頭
  • コンテンツのリッチによる圧迫
  • 得られるモノ
    • ユーザーによるエンゲージメント
    • UX

最近、なぜ dev.to がこんなにも速く、こんなにも自分にとって感動的なのか

って記事を書いたんだけど、ただ速いのがいいというより、そもそも速度が担保されなければ、他の機能を足す余地もそもそもない、というのが自分の持論。

DevTool に詳しくなることで得られること

数値は嘘をつかないが、数値を読み解く知識はまた別途必要だ。そして、数値の意味を知ることで、開発者のメタ認知が変わっていくことで、実際に書くコードも変わってくる。

  • CPU の気持ちになる: メモリプロファイラの読み方を通して、CPUの状態、GCが何を掴んで、何を捨てるかを意識してコードを書けるようになる。またOSSAPIインターフェイスをみてメモリのどこにインパクトを及ぼすか、だいたい察することができるようになる。
  • GPU の気持ちになる: CSS属性の変更が及ぼす影響を通して、フレームレートに悪影響がある属性の扱いに慎重になる。

たとえば will-change 属性はパフォーマンスを意識するのにわかりやすい属性だろう。事前に変化する値を宣言しておくことで、「温めておく」わけだ。

本書もその点を詳しく解説している。

[レイアウト再算出が必要なプロパティ]

透過の合成のアルファブレンドも重いことで有名だが、スクロール時にcss属性を再計算するときと組み合わさって酷いことになりがち。これを検出するには事前に重くなりうることを意識しておく他にない。

ただ、一応気をつけておくべき点として、 Chrome DevTools は Chrome の内部状態の一種のダンプであって、他のブラウザでも おそらくブラウザでCSSGPU命令になる場所はだいぶ異なっているだろう。ただし、同じWebの共通仕様で実装している以上、そこまで乖離はないはず。

昔は Skia Debugger を直接見てたんだけど、いい時代になったなーと思う。

本書の良かった点

  • 世代別GCの解説と、メモリスナップショットを比較して、ヒープ領域の調査でMajor GCに負荷をかけてる部分の特定手法
  • Minor GC の調査
  • 不要なイベントリスナの特定と排除
  • picture 要素でデバイスに応じてレスポンシブに必要な画像を読み込む
  • Service Workerの初歩的な実装パターン。ネットにはコード断片はあるけどまとまった資料は初かも
  • リソースヒントの dis-fetch, preconnect, prefetch, preload, prerender。これらはpreload ぐらいしか現在使えるものはないが、書き得なんで知っておくと良さそう。
  • WebAnimation の解説、日本語だとはじめてみた
  • will-change 命令による compositing 最適化
  • スクロールイベント下のForced Reflow の対策
  • FP - FCP - FMP - TTI がRAILモデルとの対応で用語整理できた
  • サンプルコードがしれっと ES2015
  • 現代でのドメインシャーディングはアンチパターンだよって話

自分が読んでよかった点なんで、初歩的な分は飛ばして読んでいた。

違和感あった点

  • Nodeのインストールについては最小限で勝手にやってくれって感じ
  • 突然 webpack commonchunk の設定例が出てきて気持ちはわかるけど何もかも前提ふっ飛ばしてさすがに唐突で笑った
  • サーバーサイドでの処理の重さは度外視します、現代ではフロントエンドが主です!と宣言してしまってるけど、要件次第なんでここはポジショントークだと言ってほしかった

最後に

「推測するな、計測せよ」を地で行く本。「属人性の高い高度に発達した勘」から体系的な知識への転換。

フロントエンドに関してはネットワークのレイテンシで制御できずYAGNI原則が通じる箇所と通じない箇所がある。それらをこの本で学べると思う。

あと DevTools の UI すぐ変わるから、早いとこ読んだほうがいい。今すぐ買おう!(あとで読んでもいいけど)

この本の知識を皆が知ってたら「なんか重いんですけど〜」って自分に振られることが減っていい世界が来そうだと思いました。完。

#yatteikitv 【mizchi×itopoid】生放送なら凍らないっ! という企画で itopoid とYoutube Live で生放送してきた [#yatteikifm 番外編]


【mizchi×itopoid】生放送なら凍らないっ! #01

生放送で150人ぐらい見てもらえた感じで、その録画です。

メンバー

スピーカー

@itopoid @mizchi

進行・配信

@sageszk

経緯

yatteiki.fm の番外。

itopoid と sageszk と自分は 大学時代からの友人。

mizchi「(yatteiki企画の)やっていき場にいきたいが健康的な時間なのでなかなかいけない」

itopoid「生放送しましょう。場所はmizchiさん家で」

mizchi「は?」

内容

  • 例の凍結騒ぎ
  • itopoidのキャリア相談
  • インターネット総合

一番エモかったシーン

「他人と違うことやるだけだったら簡単だよね。それにどう価値を付けるかが大事なわけじゃん」

その他

やはりHTML/DOMは再発明されるべきじゃないか

と思う次第である。以下理由。

JavaScript, GUI設計の今

JSはそのプラットフォーム特性上、あらゆる言語の使用者の、あらゆる不満が集まる場所で、ヘイトを集めやすい環境だと思う。近年は npm というプラットフォームの登場でエコシステムが生まれ、思いつく限りあらゆるメソッドが適用されてきた。貧弱な言語基盤だが、その中で生き残った方法論が、今一番GUIの課題を上手く扱えている、と自分は考えている。

React/Redux や Angular によって、Flux/MVVMという抽象パターンが枯れてきたように思う。(この際、現場はまだ jQuery だぞ、みたいな話は無視する)。要は View は State の写像である、ということに尽きる。State はシリアライズ可能(JSON)で、Flux Action/Rx.Observable の Event Stream を入力とし、それぞれの状態はシリアライズ可能で、あらゆる状態が入力値によって予測可能(参照透明)で、決定的である。

要は、メタな設計というレイヤーでは、データの扱いを記述することが、アプリケーションを記述することと同義になりつつある。Redux も Rx も Event Stream からどうスナップショットを切り取るかの一つの方法論に過ぎない。(まだ新しい発明の余地はある、と思うが)

そこでは mutable なものは悪で、GUIの設計からは排除されるべきものになった。View とは本質的にシングルトン(哲学的には、そこで発生する「体験」もシングルトン)だが、そこに対する副作用は厳密に管理されなければいけない。でないと破綻する。ここで「発見」されたのは関数型的な手続き、FRPである。まあFRPは詳しく掘り下げるとボロが出る長くなるので、今回は置いておく。

Virtual DOM によるアーキテクチャの変化、最適化の限界

Virtual DOM の発明によって、State の写像である View の状態遷移は、人間の手を離れた。jQuery 時代のスクレイピング的手法から、自動的な木の構築が行われ、残されたのは、その破棄/生成のライフサイクルに対するフックになった。勿論、細かいインタラクションの最適化はまだ人間の手の余地が残されているが、管理すべき対象は大幅に減り、個人的にはウェブ開発の産業革命以前と以降のような劇的な変化だと思っている。詳しくは3年前に書いた なぜ仮想DOMという概念が俺達の魂を震えさせるのかを読んでほしい。

「賢い」データバインディングも結局はVirtualDOMとやることが同じである。木の枝ごとに値の監視範囲を知っていて、必要なパラメータだけを書き換える。DOMを書き換えた結果、レンダラに負荷がかかる。要はどちらも再生成するスコープが小さければ小さいほど、GPUを介して発行される Layouting や Painting のパフォーマンスが良くなる。当然だ。

ここで問題となるのは、結局 Virtual DOM は、HTML/DOM とは別の世界で定義された構造である、という本質的な部分だ。Virtual DOM は木に対してプリミティブな命令セットすぎるHTML/DOMを効率よくハックするために生まれたメタ概念であり、実際にはDOMのパラメータを変更した結果として描画命令が発行される。その為、逐次的なパラメータの更新は、ステップごとにその変更を通してサイズや位置が変更され、親は自身の大きさのバウンディングボックスの再計算を必要とし、それがルート要素まで連鎖し、まあ色々なんやかんやあって命令に対し影響範囲を予測することが難しい。

これに対する解として、同期フレーム内での DOMへの副作用をバッチにする requestAnimationFrame や、予め変更があることを通知しておく css の will-change属性 がある。

しかし、 document.write の存在(広告業界はいつ使うのをやめるんだ)や、float 属性が物事をややこしくしている。同期的に割り込まれたり、再計算範囲が事前にわからないそれらは、Webの黎明期に大きな役割を果たしたが、今や大いなる負債となって、最適化を妨げている。(と思っているが、本職のブラウザ開発者はまた別の意見がありそう。きいてみたい)

問題1: 参入障壁が大きく民主的ではない

問題の一つ。後方互換を配慮して積み重ねていた結果、実装が困難で、一部のパワーのあるベンダーしかブラウザを実装できない状態になっており、結果としてブラウザの開発は参入障壁が大きくあまり民主的ではない。

ChromeWebkit は体裁としてオープンソースだが、一部のベンダ内にノウハウが偏っているし、ビルドやユニットテストに掛かる時間も膨大で、分散ビルド環境を持たないと開発に参加しにくい。

この実害として、僕は数年前まで vimperator というFirefox 拡張を愛用していたが、アドオン自体のコンセプトがもはやChrome に追従する Firefox の方向性と相容れなくなったため、アップデートごとにAPIが削除され、都度代替を探すということをしてるうちになんだか使いづらいものになってしまった。

問題2: セマンティクスは既に滅んでいる

問題の一つ。今のWebに、もはやHTML/CSSにセマンティクスなどはない。多重に重ねられた div と span が CSS の属性を通して何らかの意味を持っていて、外部から意図を読み取ることは困難だ。HTML5の、header, content, h1, main(:roll) 属性によってなんらかのセマンティクスを与えることが出来るも、今や付け焼き刃だ。

Googleクローラーは、おそらく最低限のセマンティクスを読み取った後は、各要素のバウンディングボックスの上からの出現順と全体を占める比重をみて、その中身のテキストの解析結果に重みを付けている、と予想できる。

解決のアプローチ

たぶん、ブラウザ開発の中の人達はそんなこと当然わかってて、何度も試作し、仕様を煮詰め、そして実装的な課題や、あるいは政治的な部分で諦めてきただろう。xhtml ですらダメだったのだ。でもその失敗は、時代的なものも大きかったと思う。数多の技術革新を経て、IE11 が EOLで、MS がIEでの反省の色を見せ、Safari が世界的に Android に追い詰められて標準化圧力が強い今、やってみるべきではないか。

第二次ブラウザ戦争はとりあえず Chrome の勝利ということで終わったが、モバイルの隆盛で、ブラウザというものが危機に瀕している。僕はブラウザとWebとその民主主義が好きなので死んでほしくはない、と思っている。

ブラウザが生きのこるのに必要なのは、後方互換を切り捨てた高速なレンダラー基盤と、そのためのシンプルな仕様なのではないか、というのが自分の思うところ。

既存の解決策1: WebComponents

WebComponents はタブ内で既存のUIスレッドから切り離された ShadowRoot 下に新しいページコンテキストを生成する。また自分でエレメント要素に名前を付けられるので、セマンティクスが今よりはマシだ。要はお行儀の良いiframeである。これによって全く別の最適化が施せる、余地がある。

ただ、Google肝いりであるはずの Polymer は、単純にパフォーマンスが悪い、大量のポリフィルが必要でオーバーヘッドが増す、HTML Import 周辺でおそらく合意がとれない、といった課題が山積している。リッチなアプリケーション基盤としても、WebComponentsが生成した中身のSEOが、どうクロールされ、どう扱われるかの声明がGoogleから出ていないので、前述の問題を含めて、個人的に使う気にはなれない。

既存の解決策2: AMP

最初に言っておく。AMP はGoogleの邪悪なHTMLサブセットだ。だがやりたいことは痛いほど分かる。要はレンダラーが予測可能な命令セットだけで、事前に最適化してしまいたいのだ。それにGoogle側のキャッシュプレロードが付いてくる。

AMPを使った場合、事前に動的なレイアウティングを排除され、すべての要素の width/height が事前に確定することが要求される。 amp-* のタグを使って組み上げるのは、 WebComponents を使った体験に非常に近い。近いというか中身見る限りそのものだが。

既存の解決策3: React Native

なぜここで React Native の名が出るか、疑問に思う人も多いだろう。だが自分はこれが、Webを作り直す場合、この発展系が一番筋がいいのではないか、と思っている。

ここで React Native のコードを見てみよう。

// https://facebook.github.io/react-native/docs/props.html#content
import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class Greeting extends Component {
  render() {
    return (
      <Text>Hello {this.props.name}!</Text>
    );
  }
}

export default class LotsOfGreetings extends Component {
  render() {
    return (
      <View style={{alignItems: 'center'}}>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => LotsOfGreetings);

知るべきは、View という抽象要素が、flex layout で動くことだ。これがどう達成されているかというと、yoga というflex layout の座標計算を行うレイアウトエンジンを下敷きになっている。flex layout は近年のCSSの課題が一通り解決されていて、一応この規格で今現在のレスポンシブなUIは一通り実現できる。

yue ってのもある。これは Electron の zcbens 氏が開発した cross platform な GUI ライブラリで、これも yoga エンジンの上に構築されている。おそらくは Electron で足りないパーツを補うためのものだろう。

AMPはHTMLとして書き込まれるものがコンポーネントごとに独立して稼働するのに対し、ReactNative は、それを生成するJS側が上位概念になる。AMPと違って自分でコンポーネントを自分で定義できるのが大きい。(AMPはやはり、ホワイトリストコンポーネントしか使えないのが全く民主的ではないのが限界ではある)

レンダラーの提案

言いたいのは、ReactNative 最高って話じゃなくて、レンダラーの性能を最大限に引き出しつつ、十分な自由度でウェブアプリケーション開発基盤を作るなら、ReactNative周辺の発想が下敷きになるだろう、ということだ。毎フレーム document.body.innerHTML = ... で書き換えるのではなく、レンダラーに向けた最重要エレメントとして、今のDOMではなく、 VDOM 要素とその差分更新を使う。

document.body.render(<View><text>Hello!</text></View>)

こんなAPIとすればよい。そしてそれは全部非同期で行われるとする。そして、その過程で密にレンダラー への副作用を最適化すれば良い。また、差分更新アルゴリズム自体をネイティブで記述できるので、高速化が見込まれる。課題があるとすれば、毎度巨大なステートツリーを生成する際のオーバーヘッドだろうか。

ReactNativeのView要素はAndoridのView, iOSのUIViewを抽象していてこういう名前になっているが、単に flex layout が動けばいいのなら、別に div でもいい。<html next> みたいな定義を見たらこちらに切り替える、とか。例えばね。

この手法、もう一つ嬉しい点があって、実は画面遷移にJSを使わなくてもいい。同一オリジンないなら常にVDOMアルゴリズムを適用する、という仕様があれば、サーバーサイドで生成したHTMLを効率よく差分適用できる。大多数であろう、ヘッダだけ再利用して、コンテンツ書き換え、みたいなことが自然に行われる。

夢があると思いませんか。

ここで予想される批判は、Webは複雑化するのをやめ、ティム・バーナーズ=リーが目指したであろう、セマンティックなHTMLに回帰せよ、という主張。理解できるし気持ちはわかる。が、現状無理がある。Webに要求されるインタラクションや組版タイポグラフィの水準は、もはやそこを遥かに越えていて、アプリケーションプラットフォームとして機能しないといけない時代に来ている。

少なくとも、「アプリケーションの」プラットフォームとしては、HTMLの別規格、あるいはサブセットとして、新しく生まれ変わる必要があるのではないか、というのが自分の主張である。

Facebook はブラウザを出すんじゃないだろうか

僕は数年以内に Facebook がブラウザを出すと思っている。その際に重要視してくるのは、ネイティブアプリと遜色ない体験を、一つのブラウザ内でシームレスに遷移できることだと思う。そのための基盤がReactで、少なくともその実験はしているのは間違いない。

その証拠と言っては何だが、 この前の BSD+Patent の騒動で、結局 ReactNative はライセンスを変更してない。これは対Google、対Apple の布石だと思っている。いや、元々センシティブなライブラリなので防衛的になるのもあるだろうが、単なるUIライブラリとしてはFacebookが力を入れすぎている、という感覚が昔からあった。だが、これが新ブラウザの布石なら納得行く話ではないだろうか。

書いてて思ったけど陰謀論っぽい。ちょっと落ち着こう…

まとめ

  • ウェブフロントエンドの設計論ではHTML/DOMの後方互換性が足を引っ張るようになってきた
  • HTMLサブセットの提案は、AMPなどですでにGoogleの目指すところとなっている
  • ReactNative の方向でスクリプティングが発達すれば、少なくともレンダラーはネイティブアプリと遜色なくなる
  • 今このタイミングで適切な次世代のWebの仕様を提案できれば、一発あるかもしれない

なんか興がのってでかいテーマで書いてしまったが、思考実験と言うか、話半分に聞いてほしい。Googleの中の人とかに話を聞いてみたいなぁ。

Redux は 概念的に Rx のサブセットであるという話

この資料のアレ。

mizchi.hatenablog.com

Reducer は単なる (State, Action) => State の関数で、redux.combineReducers は複数の reducer を名前空間でマップした新しい reducer にするもの。

Rx分かる人、Redux分かる人向けに、 redux.combineReducers を実装して、Rx.Observable.scan で reducer として実際に動くコードを書いた。

const Rx = require('rx')

const combineReducers = reducerMap => {
  const initialState = Object.entries(
    reducerMap
  ).reduce((acc, [key, reducer]) => {
    return Object.assign({}, acc, {
      [key]: reducer(undefined, { type: '__init' })
    })
  }, {})
  return (state = initialState, action) => {
    console.log('run combined reducer')
    return Object.entries(reducerMap).reduce((acc, [key, reducer]) => {
      return Object.assign({}, acc, {
        [key]: reducer(state[key], action)
      })
    }, {})
  }
}

const ADD = 'add'
const add = n => ({ type: ADD, payload: n })

const initialState = {
  value: 0
}

const counter = (state = initialState, action) => {
  switch (action.type) {
    case ADD:
      return { value: action.payload + state.value }
    default:
      return state
  }
}

const rootReducer = combineReducers({ counter })

const actions = Rx.Observable.from([add(1), add(2), add(3)])
const scanned = actions.scan(rootReducer, undefined) // need undefined to pass with reducer on init

scanned.subscribe(x => {
  console.log('subscribe', x)
})

最後の subscribe の console の代わりに ReactDOM.render にすれば、純粋関数な React Component が render できて、それをHoCとしてラップしてるのが ReactRedux.Provider というわけ。

Rx は 直接 Event Stream (Observale, Action Stream) を扱うけど、Redux は1つのEventを手がかりに前後の状態だけを記述するので、扱うスコープを限定したRx とも言える。スナップショットを確定するために 2つ以上または時系列的に差がある Stream 中の Event を使わないといけない場合、 Redux Middleware が出てくるけど、これをシュッと綺麗に書けるのは Rx の方。ただほとんどのケースでReduxのスコープの狭め方は有用。

今、我々は、 GUI の設計について 何を考えるべきか

というテーマで ToKyoto.js ― Kyoto.js in Tokyo - connpass で Redux Rx FRP らへんの 話をしてきました。

これ一個会場で指摘された間違いがあって、 reducer = observable.reduce と書いてるところは reducer = observable.scan です。

@amagitakayoshi / @pastak お疲れ様でした。

redux の repatch middleware を実装しようとしたメモ

色々コンテキスト略。自分用の作業メモ。

jaystack/repatch を redux の middleware に移植しようとしたが、いくつか問題があった。

  • ActionCreator が reducer を返す、という基本コンセプトを維持するとする
  • しかし repatch はストアがシングルトン前提なので、そのまま移植すると combineReducers で破綻する
  • ので reducer関数の参照を action.type として使って、reducer を当てる対象としてスコープをコントロールできるのでは
  • というのを考えたが、dispatch 時にreducer参照が必要になってしまい、どう考えてもAPIがきれいにならない
  • よく考えたら、 connect の第二引数で bindActionCreators する時にdispatchと関数一覧を渡してどうにか出来るのでは?
  • ということで * as actions するとして、 規約 で default を reducer、残りを ActionCreator と決め打ってしまうと綺麗になるかもしれない

練った結果、こういうのが作れるのがわかった

// reducers/index.js
import { combineReducers } from 'redux'
import counter from './counter'
export default combineReducers({counter})

// reducers/counter.js
import { createReducer } from 'apatcher-redux'

export type Counter = {
  value: number
}

export const addAsync = (n: number) => async (state: Counter) => ({
  value: state.value + n
})

export default createReducer({ value: 0 })

// containers/Counter.js
import * as counter from '../reducers/counter'
import * as foo from '../reducers/foo'
import { bindReducersToActions } from 'apatcher-redux'

connect(
  (state: Counter) => state,
  bindReducersToActions({counter, foo})
)(function A(props) {
  return (
    <div>
      <button onClick={() => props.counter.addAsync(1)}>Add 1</button>
      <button onClick={() => props.counter.addAsync(2)}>Add 2</button>
    </div>
  )
})

問題はあって

  • 暗に default が reducer としてたり、ファイルスコープに規約を持ち込んでしまってるのがあまりよくなさそう
  • connect 側から見てactionsに異常に型が付けづらい
  • テスト時に専用のコンテキストを組み立てる手間が多くて面倒
  • reduxに対して部分的に使えるようにしたところで、パラダイムが違いすぎて混ぜるのは見通しがすごく悪い

これ実装したところで生の redux 使うのとどっちが楽かというところで、結局強いメリットを示せないのではないか、という結論になって一晩寝かせて考えたほうが良さそう。

Google Document の音声認識入力が思ってたよりすごかった

はいえーとあの google の音声入力のテストをやってみてるんですけどこれめっちゃすごいですねなんかここまで認識精度良いと思わなかったあの文字の改行とかそこだけちょっと自分でやんないといけないんですけどそれ以外は全然不満がないですねこれなにかコマンドとかあるのかなやそうでもないか何がやりたいかというと discord でちょっと仕事で使ってみたくてボイスチャットチャンネルに没頭*1参加させて録音させてそのデータを google のドキュメントとして音声で食わせて文字起こしさせればあの会議とかねリモートワークとかですごい便利なんじゃないかなと思って文字認識 api ってちょっと公開されてるかわかんないんだけどこういう api って google あんまりね有料 api 脱退後悔*2しきれなかったりっていうイメージあるんだよねまあ google ドキュメントを使わせるためのインセンティブやっぱりそういうところがあるんだろうなって後僕がめっちゃ噛んだりしたところも勝手に飛ばしてくれてるんですよこれ喋りながら結構混*3んでるんだけど多分 google が正しいと思う認識する文字列として適正じゃないってことでね甲大*4部分がスキップされてるんだよね文字起こし分を見てるとかんだって言うのがちょっとうまくいかないなやってもねこれも最終的に人間がテレビ*5調整するって言うフレーズ*6が必要になるんだなるのは仕方ないんだけどこれ見れば何か吉田か*7全然思い出せるし多分これすごいよ3-を使ってみると良いんじゃないかなと言う感じです


Speech API - 音声認識  |  Google Cloud Platform

Google $0.006/15s だったから30分の会議の音声データを送りつけても72円

*1:BOT

*2:で公開

*3:

*4:かんだ

*5:手で

*6:フェーズ

*7:喋ったか