このエントリは、 http2 advent calendar の 5 日目です。
ここまで豪華執筆陣による記事が続いてるところに恐縮です。
(あとChromeのコードへの理解が足りず、間違いがあるかもしれません。ご注意下さい)
Chrome デベロッパーツールのPriority
先日、Chrome 安定版のバージョン47が公開されました。
このバージョンより、デベロッパーツールのネットワークパネルにおいて、Priorityという項目が追加されました(右クリックで追加可能)。
( https://asnokaze.com/test.html )
この Priority とは何なのかについて少々書いてみようと思います。
だらだら書いていくので、結論が知りたい場合は一番下のまとめまで飛ばして下さい。
ResourceLoadPriority
定義
先述のデベロッパーツールのPriorityの項目は、HTTP/1.1の通信においても優先度付されているので直接的にHTTP/2のPriorityを指し示してない事はわかるかと思います。
ここで表示されいてるPriorityは単純にChromeの中での優先度にすぎません。この優先度の定義はBlinkのコード見てみると分かりやすいかと思います。
enum ResourceLoadPriority { // The unresolved priority is here for the convenience of the clients. It // should not be passed to the ResourceLoadScheduler. ResourceLoadPriorityUnresolved = -1, ResourceLoadPriorityVeryLow = 0, ResourceLoadPriorityLow, ResourceLoadPriorityMedium, ResourceLoadPriorityHigh, ResourceLoadPriorityVeryHigh, ResourceLoadPriorityLowest = ResourceLoadPriorityVeryLow, ResourceLoadPriorityHighest = ResourceLoadPriorityVeryHigh, };
コードの定義の通り「Lowest, Low, Medium, High, Highest」の5段階であることが確認できます。
Priority付け
次に各リクエストがどのように優先度付けされるか確認します。同じくBlinkのコードを見てみます。
typeToPriorityメソッドにおいて、Resource::Type毎のデフォルトのPriorityが決められます。
static ResourceLoadPriority typeToPriority(Resource::Type type) { switch (type) { case Resource::MainResource: return ResourceLoadPriorityVeryHigh; case Resource::XSLStyleSheet: ASSERT(RuntimeEnabledFeatures::xsltEnabled()); case Resource::CSSStyleSheet: return ResourceLoadPriorityHigh; case Resource::Raw: case Resource::Script: case Resource::Font: case Resource::ImportResource: return ResourceLoadPriorityMedium; case Resource::LinkSubresource: case Resource::TextTrack: case Resource::Media: case Resource::SVGDocument: return ResourceLoadPriorityLow; case Resource::Image: case Resource::LinkPrefetch: case Resource::LinkPreload: return ResourceLoadPriorityVeryLow; } ASSERT_NOT_REACHED(); return ResourceLoadPriorityUnresolved; }
https://chromium.googlesource.com/chromium/blink/+/master/Source/core/fetch/ResourceFetcher.cpp#60
以下の様なことが分かります
- MainResource(Document)はVeriHigh
- CSSはHigh
- ScriptはMedium
合わせて以下の部分も確認します。
modifyPriorityForExperimentsメソッドにて、Priorityが変更されます。
ResourceLoadPriority FrameFetchContext::modifyPriorityForExperiments(ResourceLoadPriority priority, Resource::Type type, const FetchRequest& request) //中略 // If enabled, drop the priority of all resources in a subframe. if (frame()->settings()->lowPriorityIframes() && !frame()->isMainFrame()) return ResourceLoadPriorityVeryLow; // Async/Defer scripts. if (type == Resource::Script && FetchRequest::LazyLoad == request.defer()) return frame()->settings()->fetchIncreaseAsyncScriptPriority() ? ResourceLoadPriorityMedium : ResourceLoadPriorityLow;
例えばAsync属性の付いたScriptはPriorityがLowに変更されていることが分かります。
再確認
改めてデベロッパーツールを確認します
Type毎に指定されたPriorityになっていることが確認できます。また、ScriptでもPriorityが違うのは、Asyncを指定していたのが理由だと分かりました。
spdy_priority
さて、続いては ResourceLoadPriority と HTTP/2のPriorityの関係を見ていきます。
注意点としては、ChromeではSPDY/4 == HTTP/2として扱っています。そのため、コード上でspdyと出てきたからと言ってSPDY/2, SPDY3の事を指してるとは限らない点に注意です。
定義としても、SPDY version 4 == HTTP2 となっていることが確認できます。
enum SpdyMajorVersion { SPDY2 = 2, SPDY_MIN_VERSION = SPDY2, SPDY3 = 3, HTTP2 = 4, SPDY_MAX_VERSION = HTTP2 };
https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_protocol.h&l=35
HEADERSフレームの生成
では、Chromeの中でHEADERSフレームに優先度を設定している部分を確認します。
scoped_ptr<SpdyFrame> SpdySession::CreateSynStream( SpdyStreamId stream_id, RequestPriority priority, SpdyControlFlags flags, const SpdyHeaderBlock& block) { //(中略) SpdyPriority spdy_priority = ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion()); if (GetProtocolVersion() <= SPDY3) { //(中略) } else { SpdyHeadersIR headers(stream_id); headers.set_priority(spdy_priority); headers.set_has_priority(true);
https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_session.cc&l=1074
CreateSynStreamメソッドですが、HTTP/2のheadersフレームの生成も行っています。
Priorityの設定のキモは、ConvertRequestPriorityToSpdyPriorityメソッドで、リクエストのPriority(多分、最初に説明したResourceLoadPriority)をHTTP/2の世界のSpdyPriorityに変換しているところです。
Weight計算
御存知の通り、HTTP/2ではdependency based priorityという、依存関係と重みを用いた優先度付が行われます。しかし、現在Chromeでは、依存関係を使用しておらず、Weightのみの優先度付を行っています。
そのWeightがどのように決まるかは、ConvertRequestPriorityToSpdyPrioritの実装とあわせて確認していきます。
SpdyPriority ConvertRequestPriorityToSpdyPriority( const RequestPriority priority, SpdyMajorVersion protocol_version) { DCHECK_GE(priority, MINIMUM_PRIORITY); DCHECK_LE(priority, MAXIMUM_PRIORITY); return static_cast<SpdyPriority>(MAXIMUM_PRIORITY - priority); }
https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_http_utils.cc&l=191
uint8 SpdyFramer::MapPriorityToWeight(SpdyPriority priority) { const float kSteps = 255.9f / 7.f; return static_cast<uint8>(kSteps * (7.f - priority)); }
HTTP/2のWeightの最大値 256を7分割し、SpdyPriorityが係数として掛けられる形になる。
答え合わせ
サーバ側でHEADERSフレームのWeightを見てみると、見てきたとおりのWeightになっていることが確認できました。
request line: "GET /test.html HTTP/2.0" weight:256 request line: "GET /test.js?1 HTTP/2.0" weight:183 request line: "GET /test.css?1 HTTP/2.0" weight:220 request line: "GET /test.js?2 HTTP/2.0" weight:147 request line: "GET /test.css?2 HTTP/2.0" weight:220 request line: "GET /test.css?3 HTTP/2.0" weight:220 request line: "GET /test.js?3 HTTP/2.0" weight:183 request line: "GET /a.gif HTTP/2.0" weight:110
まとめ
つまり、TypeとResourceLoadPriorityとspdy_priority(値)とWeightの関係は以下のようになる。
(dependencyないのにLowestでもWeight:100は全然Lowestじゃないのでは?)
余談
Chromeでもdependencyに関する実装が進められている模様
https://codereview.chromium.org/1411383005/
夏風先生の資料が非常に分かりやすいです。
https://speakerdeck.com/summerwind/2-prioritization