綺麗にAndroidのUIとビジネスロジックのコードを分離する方法についてなやむ
若干高速化とは話がズレますが、最近趣味でAndroidのアプリを書くことが多いのですが、さくっとコードが汚くなっていくので何故だろうと考えてみた、という話です。
コードの何が汚いのかを説明する前に、状況の説明をしますが
自分はAndroidのコードを以下の3分類に分けて開発しています。
- (1)画面制御用ロジック : Fragment、Activity、Adapter(PagerAdapterなど)
- (2)ビジネスロジック : Service
- (3)データアクセスロジック(DB/SharedPreferenceなど) : Accessor
(1)は基本的にはFragmentに書いています。動的に要素数が変わる処理ならAdapterを作る必要もあります。(2)はServiceという独自クラスを定義して書いています。(3)はAccessorという独自クラスです。
図で説明すると、こんな感じ(字が汚くてゴメンナサイ
で、画面制御とビジネスロジックが分けづらい部分が出てくるんですね。
たとえばリストで選択したものをダウンロードして表示する、というロジックを考えたときに
- ダウンロードする対象をリストから選択する
- loading中アイコンを出す
- 非同期でネットワークからダウンロードする
- ダウンロードが成功したらコンテンツを画面に表示する
- ダウンロード失敗したら画面にリトライしてくださいというメッセージとともにエラー表示を出す
3でAsyncTaskを生成してdoInBackgroundでダウンロードサービスを呼び出し、4、5の処理はAsyncTaskのonPostExecuteで処理するということになるのですが、ダウンロードしなかったらエラーを出す、というのも立派なビジネスロジックなので画面の制御用のコードが入ってくるAsyncTaskではなくビジネスロジック側に持たせたいんですね。
それで何とかすっきり書きたいなあ、と悩んでいました。
Webプログラミングで同じようなことに悩んだ記憶が無いのですが、Webの場合は画面制御は全体処理の 最後 に静的なHTMLを吐き出して動的な処理があればJavaScriptにパラメータを渡してあげる、というものでした。最後にレンダリングすればいいので、ビジネスロジックを呼び出してView用のデータオブジェクトを作って、それを最後にテンプレート(VelocityとかSmartyとか)にぶち込む、という感じになります。ビジネスロジックを動かすタイミングとUIに反映させるタイミングが重ならないのでWebプログラミングの場合、両者がくっついてしまうという問題に悩まされることは少ないようです。
Androidの場合、ビジネスロジックの途中にUXの向上のために画面の細かい制御を入れる必要があって、それがビジネスロジックと画面制御ロジックの間を曖昧にしているためコードが汚くなりがちなのではないかと考えました。
同じ問題に悩まされている人いないかなと調べたところ
とか大体似たような悩みのような気がしますね。ただ個人開発にはDDDは大袈裟な気がするので導入は悩みますが
とかも参考になりそう。というわけでこの辺を明日からソースコードを読んでみたいと思います(時間切れ)。
AMQPとは何なのか
Rabbit MQを理解するためにAMQPとは何か、から調べてみた
まずAMQPはAdvanced Message Queuing Protocolの略語で、アプリケーション層のバイナリプロトコルらしい
色々なメッセージングアプリケーションやコミュニケーションパターンを効果的にサポートするために設計されたらしい(とWikipediaに書いてある)
RabbitMQ以外にもいろんなQueueが対応している
AMQP仕様のバージョンについて
最新は1.0(Final)だが、AMQPで使われているのは0-9-1らしい。
ここにも書いてあるとおり0.9-1から1.0になる際にmessage brokerの部分がごそっと消えていて、Exchangesが消えたりProducerやConsumerじゃなくてtargetsとsourcesになっていたり大きな変更をされている。変わりすぎていて困る……が、RabbitMQが準拠しているのは0-9-1の方のようなので0-9-1の方を調べてみよう
GREEさんのサイトの説明がわかりやすいので引用するが要するにBindingにあるルールを元にExchangeがメッセージを特定のMessageQueueに割り振るということらしい
他にもメソッド(操作)の定義やメッセージ本体の仕様の定義、メッセージフローの仕様などあるが、それらはまた次回に
参考文献
- Advanced Message Queuing Protocol(Wikipedia) http://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol
- AMQP.org https://www.amqp.org/
- AMQPによるメッセージング(土井さん) http://labs.gree.jp/blog/2010/06/262/
- RabbitMQ のドキュメントを拾い読みしたのでメモと Ruby から RabbitMQ を使う Bunny を試してみた http://qiita.com/inokappa/items/4478145ef3b6595259b3
- RabbitMQ AMQP 0-9-1 Model Explained https://www.rabbitmq.com/tutorials/amqp-concepts.html
LINE Developer Days 2015に行ってきたメモ
あわせて読みたい
- 【全部入り】LINE DEVELOPER DAY_2015 Tokyo まとめまとめ #linedevday - Togetterまとめ
- LINE DEVELOPER DAY_2015 Tokyo「LINE Platform Development Chronicle」レポート #linedevday | Developers.IO
- LINE DEVELOPER DAY_2015 Tokyo「ビッグデータを活用するための分析プラットフォーム」レポート #linedevday | Developers.IO
- LINE Platform Development Chronicle by TOMU TSURUHARAさん 参加メモ
Opening CEO 出澤さん
LINEは世界でもっとも早く成長しているアプリの1つである
最初のブレークスルーは無料通話とスタンプ
1億8千万ユーザ、170億コミュニケーション以上
日本では5800万ユーザ
GameだけでなくEntertainment領域でもPlatformを作っている
LINE関連アプリで76アプリ9億ダウンロード
これからGlobalとLifeの2点においてビッグプレイヤーに対して追いつき追い抜かなければいけない
LINE Global Culture朴 CTOの話
8カ国にブランチがあり、協力して開発している。東京、福岡、大連、ソウルなどなど
Autonomous Teams
LINEが大事にしていることとして、主体的に動けるチームである、ということを目指している
Cold-caseプロジェクト
開発をしているとバックログが溜まっていくが。バックログを消化するため年に一度、未消化のバックログを集めてプロジェクトとして立ち上げる。
それをCold-Caseプロジェクトと言う。エンジニアで協力してバックログを片付ける
普通自分が経験できなかった領域のプロジェクトに参加することもできる
Remote Collaboration
遠隔地のチームと一緒に作業をするため、課題の可視化、議論の可視化が基本となる
- 色々喋っていたけどメモできず
Challenge and Contribution
機能開発をしていくにあたり、スケーラビリティやパフォーマンスを基本として考える必要がある
安定していなくてもLINEの水に合えばオープンソースを入れて、Contributeしていく
Trust & Respect
信頼と尊重がベースとなり、各自が自主的に動いていくことに価値を置いている
Trust & Respect ↓ Responsibility-basedPlanning & Self-directing Work & Peer Code Review ↓ Positive Peer Pressure & Co-worker Review
Global Player
Global Enginnerを目指す人はみなシリコンバレーに向かっている
が、シリコンバレーとの競争が激しいからこそ日本発のグローバルサービスを作っていきたい
感想
信頼をベースにしたマネジメントフリーな組織を作っている、というのはとても興味深い話だと思う。
人材の質がある程度のレベルを超えたのを揃えないと難しい話ではあるが、達成できている、ということなのかな
LINEメッセンジャー基盤の話
LINE遠征隊
現地に行って、どういう使われ方をしているのかを把握する。そのチームをLINE遠征隊と呼んでいる
開発者は様々な地域に遠征している
現地と同じネットワーク環境を整備して、スタンプの送受信とか写真の利用とかをしている
飛行機内のWifiが不安定だったので飛行機の中でテストした。
日本と海外の違いとしてモバイルネットワークの通信環境の品質差がある
IDLE → CELL_DCH → CELL_FACH
実際のメッセージの前に先にサーバにウォームアップ用のパケットを送っておくことによって3G環境下のユーザ体験を改善する
日本の場合は問題ないが、海外の低速な回線では非常に意味がある
上記の技術を3G WARMUP と呼んでいる
グローバルなメッセンジャーの特徴としてPakistanからの発信がPakistanよりもSaudiArabiaへの発信の方が多い
そういう国の事情を考慮して実装をする必要がある
スペインでAndroidのバッテリー消耗が激しいという注文が来ていた。素早く通知して欲しい、長い間待ち受けて欲しいという命題を満たす必要がある
メッセンジャーがサーバと通信する頻度を減らしたり、というような細かいチューニングを1年かけてやった
ネットワーク的な位置の近さを実現するためにPOP(Point to Peer)を設置する
LINEアプリ→Frontend→Backendという3層構造となっている
開発言語は基本的にJavaを使用しているが、POPについてはErlangを使用してPOPしている
ユーザ体験の向上について
ユーザがどう思っているのかを知らなければいけない
AppStoreやGoogle Playのレビューを集計して、ある程度定量化して調べられるようにしている
それにより改善のプロセスを早めている
イベントの送信によってイベントがあってシステムからわかるようになる
AppReviewとEvent Analyticsの2つからーザの志向を分析している
まとめ
現地に行って同じ環境を作るというエモーショナルなアプローチとアナリティクスのアプローチの両方のアプローチをとっている
感想
グローバルなサービスを作るために各国のネットワーク回線や端末品質、文化などを知っておく必要がある、という話
Erlangでfront serverを作るというのは自分には無い発想だけど、生Cで書くよりは良さそうだ。
なぜErlangなのかをもっと深掘りして話を聞いてみたい
アプリレビュー分析などは自社サービスで作らなくてもありそうな気はするけど以外と無いのが不思議
Line Platform Developer Chronicle
LINEサーバサイドの開発をしているつるはらさん
- LINEメッセージング基盤の進化
- LINE流マイクロサービス
LINEメッセージング基盤の進化の歴史について
2011年6月 リリース。開発開始から2ヶ月でリリース
Polling+Push通知の問題点として以下のような問題があった * Pushは自社でコントロールできないのでメッセージの通知が遅れる * pollingをずっとやっているので、無駄なリクエストが大量に来る
ClientとServerの間にgatewayという層を容易した
ClientがGatewayにfetchをリクエストするとGatewayはServerにたいしてコネクションを確立し、Serverからレスポンスが返ってくるのを待つ
Gateway層はnginxと拡張モジュールで層を作っている
拡張モジュールでsegmentation fault地獄におちいった
同期処理が上手くいっていなかった。共有メモリらへん?
やはり安定性に問題があったのでErlangを採用してnginxの層を置きかえた
Erlangはホットスワップ機能があり、並行性や分散性に優れるのでとても良い
LEGYという名前のLINE Event Delivery Gatewayというものを作った
2012年7月 にconnection数が多すぎるという問題になった。
Connectionにまつわる問題としてProtocolがHTTPでLong Pollingのコネクションを貼り続けている
API用コネクションもLong Polling用コネクションも必要だったのでたくさんコネクションを貼る必要があった
SPDYを採用した
ClientとLEGY間の通信が
1 Connection Multiplex ヘッダ圧縮
で行われるようになり、メリットが大きかった
2012年10月頃 に海外でのパフォーマンスを追求した。
Clientは最寄りのLEGYに接続する。LEGYは専用線でApplication Serverと接続をする
LEGY-メインサーバは距離が離れていて遅いので、LEGYは返事を待たずにClientにレスポンスを返す。それによってClient側のUXが向上する
LINEのサービスをどのように実装してきたか
スピード & 機能 & 品質を満たさなければならない
マイクロサービス的に開発をしている。モノシリックな場合、優秀な人が入ってきても全体を把握しないと開発できない。マイクロサービスとして作成し、その間をAPIなりJob Queueなりで繋いでいる
Talk-Serverはメッセージング・ソーシャルグラフの機能のみ実装している
それ以外は別のサービスとして動いていて、開発プロセスやデプロイフローもそれぞれ独自である
Talk-ServerはJava、公式アカウント系の機能はScalaなど、ものによって色々なツールを使っている
バックエンドサービス:認証管理、Abusing、Push通知、イベント処理、外部連携gateway、などなど
マイクロサービスは単にサービスを分けて開発すればいい、というものではない。組織に関する問題も一緒に考える必要がある
LINE流マイクロサービスを支える組織
組織図や会社をまたぐアドホックなチームを形成し、独立して裁量を持たせないと単にサービスを分割しただけではコミュニケーションコストが増加してしまう
チームは短期間でiterationをまわし、目的を達成したら縮小あるいは解散する
LINE流マイクロサービスを支えるプロトコル管理
Apache Thrift、Protobuf、REST
ドキュメントでAPIの仕様の定義をして、これを使ってください、というやり方もあるがそうではなくApacheThriftで仕様を定義している
Apache ThriftはIDLでのプロトコル定義から各種言語のStubを吐いてくれる
LINE流マイクロサービスを支えるファシリティ
github→Jenkins→maven repository→PMC(内製のデプロイツール)→Server→IMON(モニタリングツール)
といった開発フローが共有されている
今やっていること、今後やっていくこと
マルチデータセンター
各拠点にはフロントエンドサーバしかない。
マイクロサービスの発展
Talk-Serverの機能がふくれ上がっているのでマイクロサービス化していかなければならない
具体的なところは午後のセッションで説明する
HBaseとRedisを使った100億超/日メッセージを処理するLINEのストレージ
聞いたけどHBaseの知識が乏しく説明できる自信が無いのでパス
4年に渡るLINE Androidアプリの進化とチャレンジ
2011.4 開発開始。Androidアプリの開発経験もないが一ヶ月半でリリース
秘訣はない。試行錯誤が多い
印象にある苦労したことは デザインの適用 。デザイナが少ないため、iPhoneのデザインをAndroidに適用した。Androidは画面解像度など端末の種類が多いので、画像サイズを9-patchで適切に変更する必要があった。画面下にコントロールが集まるなど、Androidのアプリとしては違和感のある配置だった。
現在 iPhoneとAndroidでデザインが異なっている。9-patchもデザイナーがやってくれる
当初はGoole PlayのDeveloper Consoleを使っていたが以下のような問題があった
- コードを難読化しているので、クラッシュレポートが読みづらい
- クラス名などで検索したい
- BTSと連動したい
- 任意の場所でレポートしたい
特に最後の問題が重要で、そもそもクラッシュをすべきではない。不明の異常が起こった場合でもクラッシュはしないようにしたい、その場合も任意の場所でレポートさせる必要がある
そのため独自のCrash Reporting System を用意している(Treasure DataSDKなどで同様のことができそう)。専用のSDKも用意している。難読化を元に戻してからスタックトレースを投げている
ソースコードのsubmoduleによる分割
ソースコードを一箇所で管理すると、開発・運用規則などのコミュニケーションがめんどいので、gitのsubmoduleを利用してソースコードを分離した。
main repositoryから機能ごとにsubmoduleを作ることにより、コミュニケーションコストを削減した。
リリースのときと共通サブモジュールの変更のときだけ意識合わせが必要
開発進行方法 について以下のような問題があった * 同じようなクラスが作成される * チーム間で知識が共有されない
新しい機能を開発するときに、一時的なチームを作り、終わればそのメンバーは解散するようにしていた
2013年
通信方法の改善
大きな修正項目として通信方法の改善を行った(実は2012年から継続してやっていた)
接続するサーバの数が増えていった。公式アカウント、メッセージ管理、スタンプ情報などなどのサーバにアクセスする必要があったが、それぞれのサーバにコネクションを貼るのはコストが高い。
Gatewayサーバが手前に入ることになった。1つのコネクションを使い回すので簡単にリクエストを送信できる
HTTP pipeliningを使うが、1,2,3と投げたときに2番目が遅くなると3番目も合わせて遅くなる
↑説明用の写真が欲しい
SPDYは1,3,2と受けとることができる
自社製Pushサービス
Google Play Serviceを利用できない端末がある。(子供向けのらくらくフォンなど)
Google Play Serviceが使えないと → 位置情報が使えない、Google Mapが使えない、GCMが使えない
対策として自社で開発したPushサービスを利用している。状況によって、GCMと自社Push Serviceを切り替えて使用している
GCMを利用できないと判断した場合のみ自社のpush serviceを利用している
低スペック端末に対する最適化
最初はデフォルトのイメージを表示し、ダウンロードしたイメージと切りかえているが、fade inのアニメーションを使っている
低スペック端末の場合、fade inが重くスムーズに切りかわらない
CPUの速度に問題がある場合、画面効果を切り替えている
バッテリー容量に対する最適化
サーバとの通信の話をしていたがメモできず
現在 開発メンバー:22名 ソースコード量:437000行 コミット:200 commits/week
ビッグデータを活用するための分析プラットフォーム
データ分析について
- Collecting(集約)
- リーズナブルなデータの収集
- 大量データの保持
- Reporting(報告)
- 柔軟なデータの集計
- わかりやすいチャートでの可視化
- Analyzing(分析)
- 簡便で高速なデータの抽出
異なるニーズ
- エンジニアの声
- UIなんてどうでもいい
- 生のデータに触りたい
- プランナーの声
- KPI
- きれいなグラフで見たい
- Excelでダウンロードできる
Forエンジニア
サーバ→LogCollector→[Realtimeanalyzing(Norikia)→Notification Dashboard] →[Data lake(Hadoop)→ Query UI]
LogCollector = Fluentd
Norikra(リアルタイム集計処理システム)
Shib/ShibUI(Webベースのクエリツール)
Forプランナー
- データを可視化
- データのダウンロード
- NoSQL
Hadoop
大規模分散データ処理環境
Hive/Presto: PrestoはHiveより高速なのでBIツールのバックエンドに使用している
PrestogresというPrestoをPostgresのように見えるコネクタを使っている
DWHはInfiniDBを使っている いまいち何でだっけというのがわからん
InfiniDBはカラム型分散データベースで、フロントエンドからはMySQLとして認識される
BIツール
IBM Cognosを使っている。レポートオーサリングツール
ヒートマップのような複雑なものも作れる
Pentaho:OLAP型の多次元分析システム
重要視すること
- データはなるべく隠蔽する
- ユーザトラッキングはしない ユーザ数が多すぎて苦労の割にメリットがないのでやっていない
グローバル化で見えてきた課題
- サービスの変化
- グローバル化
- 多様化
- 長寿化
- 人の変化
- プランナーの増加
結果的にKPIが増加する
サービス x メジャー x ディメンションで種類が爆増してしまう。
KPIが増加し、忙しくなる → 誰もKPIをつぶさに見なくなる
KPIなんて人手で見なくても良い。KPIの変化を伝えることをツールでサポートしてあげれば良い
KPIモニタリングシステムを開発している
- すべてのKPIのトレンド分析の自動化
- 時系列から予測値を算出する
- 異常値を検出してアラート
Spark Clusterを使っている
- KPIの時系列データを分析して予測モデルを構築し、Kafkaに保存する
- 予測値と実際値を比較し、大幅に異なる場合通知する
まとめ
Akka ActorとAMQPで作るLINEメッセージングパイプライン
LINEのメッセージングとは * 人と人(一般アカウントと一般アカウント) * 人とシステム(一般アカウントと公式アカウント)
公式アカウントはたくさん友達がいて、一気にブロードキャストする必要がある
なのでトラフィックがバーストしやすい
LINEメッセージングパイプライン
デバイス -> Talk -> Fethcher -> 受信キュー → Pusher → 外部システム ← FanOut← 送信キュー ← APIサーバ ← 外部システム
- 1: 受信キュー(RabbitMQ) AMQPを使っている
- 2: Pusher
- 3: FanOut
メッセージングならではの要件として * 送信した順序で受信側に到達しなければならない
通常のProducer/Workerパターンだと処理順序が保証されない。そこで1Q1C(1 Queue 1 Consumer)という構成にしなければならない
Consumerが死ぬとキューが溜まりつづけるのでFailOverする必要がある。
AMQP ACK
これからの話としてAMQPの仕組みとして↓のような状態遷移をするという前提知識が必要
実装例
* メッセージキュー(3) * コンシュマー(4) * コーディネーションキュー(キューの名前を入れる)
コンシュマーはコーディネーションキューからメッセージキューの名前を取りにいく。このとき、コンシュマーはackを返さないであえてキューデータの状態をunackedのままにする。
特定のコンシュマーがダウンした時にキューデータがunlockされるので空いたメッセージキューが待機中のconsumerに割り振られる
課題 スループットの改善
micro batch処理をしていたがstreaming処理に変更した
並行プログラミングがあるが、職人芸的なやり方になるのでScalaのAkkaを使った
Akkaのアクターモデルについて
actorがmailboxをもっていて、順に処理していく
古くからあるthread and lockingだと定期的にlockingしないといけないから大変
有限ステートマシンとしてのアクター
- Receive Functionの切り替えが可能
- 内部ステートを引き継ぎつつ、状況に応じて異なる動作を定義できる
- 複雑なメッセージフローを
Supervisorによるエラー処理フローの分離
Akka ActorによるPusherの実装
後で説明を埋める
メッセージ送信をFIFOからラウンドロビンに変更する
ネットワークのQOSのラウンドロビンと同じものを実装している
なぜ非同期技術が重要なのか
- マイクロサービスの採用によりサービス間通信の重要性が高まった
- 同期サービスは連鎖的な障害が起きやすい
Web Performance APIについて
Fast-Forward Performance – The Future Looks Bright
を見て使ってみたのでメモを取りました。
Navigation Timing
ユーザのネットワーク帯域やページ全体のロード時間の遅延を測るためのAPIです
var perf = performance.timing; console.log("load elapsed sec: " + (perf.loadEventStart - perf.navigationStart) + "sec");
loadEventStartやnavigationStart以外にもいろいろな値が取れるようです
詳細な仕様はこちら
High Resolution Timing
ミリセカンドよりももっと短い秒数を扱うのに利用します
var current_time = performance.now();
便利かと言われると通常のWeb開発ではそこまでの精度は必要ない気もしますね
開発ツール(Chrome Developer Toolとか)のDeveloperにとっては必要な情報かも
Page Visiblity
pageのフォーカスが外れたタイミングでvisiblitychangeイベントが発生します
document.addEventListener('visibilitychange', function(event) { if (document.hidden) { console.log(“document is not bisible); } else { console.log(“document is visible); } });
スマホだとCPU時間を消費することによるバッテリー消耗を防ぐのに利用できそうです。
後で紹介するResource Hintでページのprerenderingを指定した場合のJSの実行抑止なども可能です
Resource Timing API
Resourceを取得処理にかかった時間を出力できます
var img = performance.getEntriesByType("resource")[0]; var ttfb = parseInt(img.responseStart - img.startTime), // リソースの最初の1byteが返ってくるまでの時間 total = parseInt(img.responseEnd - img.startTime); // レスポンスの完了までの時間 console.log(“Time To First Byte:"+ ttfb + "\n" + "Total Time: " + total);
Time To First Byte:8 Total Time: 10
Performance Timeline
リソース取得時のパフォーマンスを見ることができます
var perf = performance.getEntries();
{ "responseEnd": 134.0240000281483, "responseStart": 132.1299999835901, "requestStart": 127.05100001767278, "secureConnectionStart": 0, "connectEnd": 123.49000002723187, "connectStart": 123.49000002723187, "domainLookupEnd": 123.49000002723187, "domainLookupStart": 123.49000002723187, "fetchStart": 123.49000002723187, "redirectEnd": 0, "redirectStart": 0, "initiatorType": "link", "duration": 10.534000000916421, "startTime": 123.49000002723187, "entryType": "resource", "name": "http://localhost:63342/tuning-blog/bower_components/bootstrap/dist/css/bootstrap.css" }
Battery Status
バッテリーの充電量を確認できるらしいです。
Chrome ver39で実行してみましたが、何故か動かず
var battery = navigator.battery || navigator.webkitBattery || navigator.mozBattery || navigator.msBattery; if (battery) { console.log("Battery charging? " + battery.charging ? "Yes" : "No"); console.log("Battery level: " + battery.level * 100 + " %"); console.log("Battery charging time: " + battery.chargingTime + " seconds"); console.log("Battery discharging time: " + battery.dischargingTime + " seconds"); };
User Timing
マークした所の実行時時間を取得し、表示することができます
performance.mark("start"); loadSomething(); performance.mark("end"); performance.measure("measureIt", "start", "end"); var markers = performance.getEntriesByType("mark"); var measurements = performance.getEntriesByName("measureIt"); console.log("Markers: ", markers); console.log("Measurements: ", measurements); function loadSomething() { // some crazy cool stuff here :) console.log(1+1); }
実行結果
Beacon
Beaconをメッセージ付きで非同期で送ることができます。
navigator.sendBeacon("http://twainy.github.io/tuning-blog/beacon.html", "beaconmessage");
POSTメッセージとして送られるようです。ユーザ動作無しでPOSTデータ流せるのでCSRFチェックしておかないとセキュリティ上若干危険な感じもしますね。
Animation Timing
setTimeOutやsetIntervalではないくrequestAnimationFrameを使うとCPU使用率や電力消費などが有利になります
画面のリフレッシュレートなども考慮した処理がおこなわれるらしいです。
http://ie.microsoft.com/TEStdrive/Graphics/RequestAnimationFrame/Default.html#
Resource Hints
リソースにヒントを与えることでリソースの取得タイミングを調整することができます
<link rel="dns-prefetch" href="//host_to_resolve.com"> <link rel="subresource" href="/javascript/mydata.json"> <link rel="prefetch" href="/images/labrador.jpg"> <link rel="prerender" href="//example.org/page_2.html">
dns-refetchはDomain Nameの事前解決が出来ます
subresourceは現在表示しようとしているページにとって必須のリソースの場合に指定することによって、ロード時にすぐにリソースがフェッチされるようになります。
prefetchは将来的に必要なリソースに使用することが推奨されます。低優先度でデータが取得されます
prerenderは裏でページのレンダリングをします。ページに含まれるアセットも同時に取得されます。ビーコンを指定しているとレンダリング時にカウントされてしまうため、 PageVisibility APIを使用してJSの実行を遅延する必要があります。GETの様な”safe”なページだけ事前レンダリングできます。POSTについては指定することができません
参考文献
- Fast-Forward Performance http://calendar.perfplanet.com/2014/fast-forward-performance-the-future-looks-bright/
- Web Performance API http://www.w3.org/wiki/Web_Performance/Publications
- Preconnect, Prefetch, Prerenderer https://docs.google.com/presentation/d/18zlAdKAxnc51y_kj-6sWLmnjl6TLnaru_WH0LJTjP-o/edit#slide=id.g33a803cd_4_320
ロックフリーアルゴリズムについて理解する - 逐次一貫性
逐次一貫性(sequential consitency)とは何か
最初に逐次一貫性の概念についてまず理解しよう
逐次一貫性はコンピュータシステムに関するメモリ一貫性モデルの一つであり、定義をWikipediaから引用すると「どのような実行結果も、すべてのプロセッサがある順序で逐次的に実行した結果と等しく、かつ、個々のプロセッサの処理順序がプログラムで指定された通りであること」とのことだ。
何のことだかよくわからんが、並列処理中の実行結果が常に逐次的に実行した場合と同等となる、ということのようだ。
例えば下記のようなコードがあるとして
std::atomic<int> X(0), Y(0); int r1, r2; void thread1() { X.store(1); r1 = Y.load(); } void thread2() { Y.store(1); r2 = X.load(); }
どのような実行結果も、すべてのプロセッサがある順序で逐次的に実行した結果と等しく とはXとYの結果が{X,Y} = {0, 1} , {1, 0}, {1, 1} のどれかは起こり得るが{0,0}は論理的に起こり得ないので起こってはならない、ということ
個々のプロセッサの処理順序がプログラムで指定された通りである とは例えばthread1ならばX.store(1)とr1=Y.load()の実行順序が入れ変わらない、ということ
あまりにも当たり前のように感じるが、マルチコア環境では逐次一貫性が満たされないことが多い。
リオーダリング(reordering)
マルチコア環境では必ずしもメモリ操作が順番通りに行われるとは限らない。
例えば以下のような指揮があった場合、
x = A; y = B;
プロセッサがAとBという値を取得しようとした場合、Aはプロセッサのキャッシュに存在せず、Bがプロセッサのキャッシュに存在した場合には実行結果の取得するタイミングが入れ替わってしまう
もちろんAの結果が返ってくるのを待ってからBの結果を取得しても良いが、待ち時間の分パフォーマンスが劣化するので必ずしも待つのが適切とは言えない
このように状況により命令の実行順序が入れ替わることを リオーダリング(reordering) と言う
x86メモリモデルにおけるリオーダリング
では実環境ではどのようなケースでリオーダリングが起こりがちなのかを見ていこう。
x86メモリモデルでは TSO(total-store order model) を守っている
TSOとは「ロード同士は順序を守る。ストア同士は順序を守り、ストアはロードを追い越さない。」という規則であり、同一プロセッサ内での順序一貫性(processor consistency)は維持されているが、一方でマルチプロセッサに置いては命令の順序が補償されない書き込み直後に読み込み命令を出しても最新のものが取得できるとは限い
下記は以前に示したコードだが、マルチプロセス環境ではX.storeやY.storeがメモリに反映される前にX.loadとY.loadの両方が実行され、r1 = 0, r2 = 0となることがあり得る、よってこのままでは逐次一貫性が無いということになる
std::atomic<int> X(0), Y(0); int r1, r2; void thread1() { X.store(1); r1 = Y.load(); } void thread2() { Y.store(1); r2 = X.load(); }
メモリバリア
メモリのリオーダリングを避けるための手法としてメモリバリア(メモリフェンス)とわれるメモリ操作の順序性を制限するCPU命令がある。
高レベル言語ではメモリバリアを使って実装される同期プリミティブ(synchronization primitive)を使って同期処理を実装することになる。同期プリミティブはmutexやセマフォなどの並列実行のための仕組みのことである。
まとめ
- 逐次一貫性とは「どのような実行結果も、すべてのプロセッサがある順序で逐次的に実行した結果と等しく、かつ、個々のプロセッサの処理順序がプログラムで指定された通りであること」である
- マルチプロセス環境ではリオーダリングにより逐次一貫性が保たれないケースが存在する
- リオーダリングを防ぐためにメモリバリアを利用する。メモリバリアはCPU命令であり、mutexやセマフォなどの実装に使われている
参考文献
- Wikipedia「逐次一貫性」 http://ja.wikipedia.org/wiki/%E9%80%90%E6%AC%A1%E4%B8%80%E8%B2%AB%E6%80%A7
- Wikipedia「メモリバリア」 http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%90%E3%83%AA%E3%82%A2
- メモリモデル?なにそれ?おいしいの? http://yohhoy.hatenablog.jp/entry/2014/12/21/171035
- An Introduction to Lock-Free Programming http://preshing.com/20120612/an-introduction-to-lock-free-programming/
- Understanding Violations of Sequential Consistency https://corensic.wordpress.com/2011/06/13/understanding-violations-of-sequential-consistency/
- C++11's atomic and volatile, under the hood on x86 http://brooker.co.za/blog/2013/01/06/volatile.html