高速日記

ソフトウェアの高速化・チューニングの話について好き勝手にまとめていくブログです

綺麗にAndroidのUIとビジネスロジックのコードを分離する方法についてなやむ

若干高速化とは話がズレますが、最近趣味でAndroidのアプリを書くことが多いのですが、さくっとコードが汚くなっていくので何故だろうと考えてみた、という話です。

コードの何が汚いのかを説明する前に、状況の説明をしますが

自分はAndroidのコードを以下の3分類に分けて開発しています。

  • (1)画面制御用ロジック : Fragment、Activity、Adapter(PagerAdapterなど)
  • (2)ビジネスロジック : Service
  • (3)データアクセスロジック(DB/SharedPreferenceなど) : Accessor

(1)は基本的にはFragmentに書いています。動的に要素数が変わる処理ならAdapterを作る必要もあります。(2)はServiceという独自クラスを定義して書いています。(3)はAccessorという独自クラスです。

図で説明すると、こんな感じ(字が汚くてゴメンナサイ

image

で、画面制御とビジネスロジックが分けづらい部分が出てくるんですね。

たとえばリストで選択したものをダウンロードして表示する、というロジックを考えたときに

  1. ダウンロードする対象をリストから選択する
  2. loading中アイコンを出す
  3. 非同期でネットワークからダウンロードする
  4. ダウンロードが成功したらコンテンツを画面に表示する
  5. ダウンロード失敗したら画面にリトライしてくださいというメッセージとともにエラー表示を出す

3でAsyncTaskを生成してdoInBackgroundでダウンロードサービスを呼び出し、4、5の処理はAsyncTaskのonPostExecuteで処理するということになるのですが、ダウンロードしなかったらエラーを出す、というのも立派なビジネスロジックなので画面の制御用のコードが入ってくるAsyncTaskではなくビジネスロジック側に持たせたいんですね。

それで何とかすっきり書きたいなあ、と悩んでいました。

Webプログラミングで同じようなことに悩んだ記憶が無いのですが、Webの場合は画面制御は全体処理の 最後 に静的なHTMLを吐き出して動的な処理があればJavaScriptにパラメータを渡してあげる、というものでした。最後にレンダリングすればいいので、ビジネスロジックを呼び出してView用のデータオブジェクトを作って、それを最後にテンプレート(VelocityとかSmartyとか)にぶち込む、という感じになります。ビジネスロジックを動かすタイミングとUIに反映させるタイミングが重ならないのでWebプログラミングの場合、両者がくっついてしまうという問題に悩まされることは少ないようです。

Androidの場合、ビジネスロジックの途中にUXの向上のために画面の細かい制御を入れる必要があって、それがビジネスロジックと画面制御ロジックの間を曖昧にしているためコードが汚くなりがちなのではないかと考えました。

同じ問題に悩まされている人いないかなと調べたところ

konifar.hatenablog.com

とか大体似たような悩みのような気がしますね。ただ個人開発にはDDDは大袈裟な気がするので導入は悩みますが

github.com

とかも参考になりそう。というわけでこの辺を明日からソースコードを読んでみたいと思います(時間切れ)。

AMQPとは何なのか

Rabbit MQを理解するためにAMQPとは何か、から調べてみた

まずAMQPはAdvanced Message Queuing Protocolの略語で、アプリケーション層のバイナリプロトコルらしい

色々なメッセージングアプリケーションやコミュニケーションパターンを効果的にサポートするために設計されたらしい(とWikipediaに書いてある)

image

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に割り振るということらしい image

他にもメソッド(操作)の定義やメッセージ本体の仕様の定義、メッセージフローの仕様などあるが、それらはまた次回に

参考文献

LINE Developer Days 2015に行ってきたメモ

あわせて読みたい

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のレビューを集計して、ある程度定量化して調べられるようにしている

image

それにより改善のプロセスを早めている

イベントの送信によってイベントがあってシステムからわかるようになる

AppReviewとEvent Analyticsの2つからーザの志向を分析している

まとめ

現地に行って同じ環境を作るというエモーショナルなアプローチとアナリティクスのアプローチの両方のアプローチをとっている

感想

グローバルなサービスを作るために各国のネットワーク回線や端末品質、文化などを知っておく必要がある、という話

Erlangでfront serverを作るというのは自分には無い発想だけど、生Cで書くよりは良さそうだ。

なぜErlangなのかをもっと深掘りして話を聞いてみたい

アプリレビュー分析などは自社サービスで作らなくてもありそうな気はするけど以外と無いのが不思議

Line Platform Developer Chronicle

LINEサーバサイドの開発をしているつるはらさん

  • LINEメッセージング基盤の進化
  • LINE流マイクロサービス

LINEメッセージング基盤の進化の歴史について

2011年6月 リリース。開発開始から2ヶ月でリリース

image

Polling+Push通知の問題点として以下のような問題があった * Pushは自社でコントロールできないのでメッセージの通知が遅れる * pollingをずっとやっているので、無駄なリクエストが大量に来る

ClientとServerの間にgatewayという層を容易した

image

ClientがGatewayにfetchをリクエストするとGatewayはServerにたいしてコネクションを確立し、Serverからレスポンスが返ってくるのを待つ

Gateway層はnginxと拡張モジュールで層を作っている

拡張モジュールでsegmentation fault地獄におちいった

image

同期処理が上手くいっていなかった。共有メモリらへん?

やはり安定性に問題があったのでErlangを採用してnginxの層を置きかえた

Erlangホットスワップ機能があり、並行性や分散性に優れるのでとても良い

image

LEGYという名前のLINE Event Delivery Gatewayというものを作った

image

2012年7月 にconnection数が多すぎるという問題になった。

Connectionにまつわる問題としてProtocolがHTTPでLong Pollingのコネクションを貼り続けている

API用コネクションもLong Polling用コネクションも必要だったのでたくさんコネクションを貼る必要があった

image

SPDYを採用した

image

ClientとLEGY間の通信が

1 Connection Multiplex
ヘッダ圧縮

で行われるようになり、メリットが大きかった

2012年10月頃 に海外でのパフォーマンスを追求した。

image

Clientは最寄りのLEGYに接続する。LEGYは専用線でApplication Serverと接続をする

image

LEGY-メインサーバは距離が離れていて遅いので、LEGYは返事を待たずにClientにレスポンスを返す。それによってClient側のUXが向上する

LINEのサービスをどのように実装してきたか

スピード & 機能 & 品質を満たさなければならない

マイクロサービス的に開発をしている。モノシリックな場合、優秀な人が入ってきても全体を把握しないと開発できない。マイクロサービスとして作成し、その間をAPIなりJob Queueなりで繋いでいる

Talk-Serverはメッセージング・ソーシャルグラフの機能のみ実装している

それ以外は別のサービスとして動いていて、開発プロセスやデプロイフローもそれぞれ独自である

Talk-ServerはJava、公式アカウント系の機能はScalaなど、ものによって色々なツールを使っている

image

バックエンドサービス:認証管理、Abusing、Push通知、イベント処理、外部連携gateway、などなど

マイクロサービスは単にサービスを分けて開発すればいい、というものではない。組織に関する問題も一緒に考える必要がある

LINE流マイクロサービスを支える組織

image

組織図や会社をまたぐアドホックなチームを形成し、独立して裁量を持たせないと単にサービスを分割しただけではコミュニケーションコストが増加してしまう

チームは短期間でiterationをまわし、目的を達成したら縮小あるいは解散する

LINE流マイクロサービスを支えるプロトコル管理

Apache Thrift、Protobuf、REST

image

ドキュメントでAPIの仕様の定義をして、これを使ってください、というやり方もあるがそうではなくApacheThriftで仕様を定義している

Apache ThriftはIDLでのプロトコル定義から各種言語のStubを吐いてくれる

LINE流マイクロサービスを支えるファシリティ

image

 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のアプリとしては違和感のある配置だった。

現在 iPhoneAndroidでデザインが異なっている。9-patchもデザイナーがやってくれる

image

当初は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エンジニア

  • 凝ったことをする必要がない
  • クエリを自由に発行できる
  • APIによるバッチ処理との連携ができる

image

サーバ→LogCollector→[Realtimeanalyzing(Norikia)→Notification Dashboard]
                  →[Data lake(Hadoop)→ Query UI]

LogCollector = Fluentd

Norikra(リアルタイム集計処理システム)

Shib/ShibUI(Webベースのクエリツール)

Forプランナー

  • データを可視化
  • データのダウンロード
  • NoSQL

image

Hadoop

大規模分散データ処理環境

HDFS/MapReduce/YARN

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メッセージングパイプライン

image

デバイス -> Talk -> Fethcher -> 受信キュー → Pusher → 外部システム
               ← FanOut← 送信キュー ← APIサーバ ← 外部システム
  • 1: 受信キュー(RabbitMQ) AMQPを使っている
  • 2: Pusher
  • 3: FanOut

メッセージングならではの要件として * 送信した順序で受信側に到達しなければならない

通常のProducer/Workerパターンだと処理順序が保証されない。そこで1Q1C(1 Queue 1 Consumer)という構成にしなければならない

Consumerが死ぬとキューが溜まりつづけるのでFailOverする必要がある。

AMQP ACK

これからの話としてAMQPの仕組みとして↓のような状態遷移をするという前提知識が必要

image

実装例

image * メッセージキュー(3) * コンシュマー(4) * コーディネーションキュー(キューの名前を入れる)

コンシュマーはコーディネーションキューからメッセージキューの名前を取りにいく。このとき、コンシュマーはackを返さないであえてキューデータの状態をunackedのままにする。

特定のコンシュマーがダウンした時にキューデータがunlockされるので空いたメッセージキューが待機中のconsumerに割り振られる

課題 スループットの改善

image

micro batch処理をしていたがstreaming処理に変更した

並行プログラミングがあるが、職人芸的なやり方になるのでScalaのAkkaを使った

Akkaのアクターモデルについて

actorがmailboxをもっていて、順に処理していく

古くからあるthread and lockingだと定期的にlockingしないといけないから大変

Scala/Javaで使用できる並行プログラミングモデル

有限ステートマシンとしてのアクター

  • Receive Functionの切り替えが可能
  • 内部ステートを引き継ぎつつ、状況に応じて異なる動作を定義できる
  • 複雑なメッセージフローを

Supervisorによるエラー処理フローの分離

image

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以外にもいろいろな値が取れるようです

image

詳細な仕様はこちら

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);
}

実行結果

image

Beacon

Beaconをメッセージ付きで非同期で送ることができます。

           navigator.sendBeacon("http://twainy.github.io/tuning-blog/beacon.html", "beaconmessage");

POSTメッセージとして送られるようです。ユーザ動作無しでPOSTデータ流せるのでCSRFチェックしておかないとセキュリティ上若干危険な感じもしますね。

image

Animation Timing

setTimeOutやsetIntervalではないくrequestAnimationFrameを使うとCPU使用率や電力消費などが有利になります

画面のリフレッシュレートなども考慮した処理がおこなわれるらしいです。

http://ie.microsoft.com/TEStdrive/Graphics/RequestAnimationFrame/Default.html#

Resource Hints

リソースにヒントを与えることでリソースの取得タイミングを調整することができます

image

<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については指定することができません

参考文献

ロックフリーアルゴリズムについて理解する - 逐次一貫性

逐次一貫性(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();
}

image

メモリバリア

メモリのリオーダリングを避けるための手法としてメモリバリア(メモリフェンス)とわれるメモリ操作の順序性を制限するCPU命令がある。

高レベル言語ではメモリバリアを使って実装される同期プリミティブ(synchronization primitive)を使って同期処理を実装することになる。同期プリミティブはmutexやセマフォなどの並列実行のための仕組みのことである。

まとめ

  • 逐次一貫性とは「どのような実行結果も、すべてのプロセッサがある順序で逐次的に実行した結果と等しく、かつ、個々のプロセッサの処理順序がプログラムで指定された通りであること」である
  • マルチプロセス環境ではリオーダリングにより逐次一貫性が保たれないケースが存在する
  • リオーダリングを防ぐためにメモリバリアを利用する。メモリバリアはCPU命令であり、mutexやセマフォなどの実装に使われている

参考文献