Swift + UICollectionView + WebP
SDWebImage
がWebPを簡単に扱うオプションを提供していたので使ってみました。(主にパフォーマンス調査)
pod 'SDWebImage/WebP'
を足せばSDWebImageで勝手にWebPを扱えるようになります。
使い方は、SDWebImageを普段使うような感じで、
imageView.setImageWithURL(NSURL(string: "http://path/to/image.webp"))
WebPは目に見えてサイズが小さくなるので、パフォーマンス厨にはたまりません。
WebP と JPEG のサイズ比較
- WebP: http://d3lncrho1w0yzl.cloudfront.net/photo1.100x133.2642bytes.webp (2642 bytes)
- JPEG: http://d3lncrho1w0yzl.cloudfront.net/photo1.100x133.jpg (12,016 bytes)
と、使っている画像では5xのサイズの開きがあります。
また、同じ画像を使っていますが、キャッシュしないように細工をしてありますので、下にスクロールしている分は全てWebからです。
SS
結果
もしかしたらWebPのほうが、JPEGよりも速いかも。
WebPはソフトウェアデコードが必要なのでオーバーヘッドがありますが、もしかしたら十分速いかも。
調査中です
#パフォーマンスについて追記
WebP: CPU ~20-30%, Network ~20KB/s
JPEG: CPU ~20%, Network ~130KB/s
ほとんどUICollectionVIew + SDWebImageによる負荷です。
スレッドが沢山分かれてるのに、表示順が保たれているのは何故だろう。
実装を読まなければ分からないですね。
WebPのほうが体感的に速いかも…
手元にあるiPhoneはネットワークがちょっとイカれてるので、WebPのほうが速いです。
WebPは画像サイズが小さければそこまで負荷が無いっぽい。
どれくらいオーバーヘッドが有るのか、プロファイリングをしなければ…(TODO)
追記: ローカルのファイル(2KB, 100x133)でやってみた。
ロードされた瞬間にレンダリング出来てしまい、普通の画像と変わらない。
デコードは十分速いのかも。
追記: ローカルの22KB, 640x853のなめこ画像でやると、流石に重い。(スクロールにかくつきが見られる)
まあ、640x853のファイルをCollectionViewで3列で読むなんてことはそうそう無いと思うけど…(横幅640って、iPhoneだとフルサイズなので)
ローカルで読むためにSDWebImageの中のUIImage+WebP.m
を単体で使っているのだけど、スレッドを一つだけでやっていたのが原因。
スレッドを分ける実装をすればカクツキ治ると思われる。
が、実装が面倒なのでいつかやりたいリストへ突っ込んでおく。
追記: 試しに単品でやってみると、ディレイを感じないレンダリングだった。
JPEGだとこのクラスの画像は60KBほど。
劣化を抑えれば、100KB越え。
CollectionViewでの利用はわからないけど、フルスクリーン時の画像では効果がありそう。
ひとくくりにして言うと、2-5KBくらいのWebPなら、たくさん表示してもJPEGと比べてもパフォーマンスは劣化しない。
ような気がする。(感覚)
また、フルスクリーン表示の画像はWebPのほうが
ついでに、ネットワークパフォーマンスについて
理解が浅いけど書いてみる。
記事がやみ鍋みたいになってきた。
考慮すべきこと
- リクエスト数(RTT) -> SPDY
- 同時リクエスト数制限
- TCP Slow Start -> SPDY
- もっと言えば、余裕があるときに画像をPrefetchする
リクエスト数。
モバイルのリクエストはとんでもなくオーバーヘッドが大きいため、出来るだけ削った方が無難。
このアプリケーションでは一画面に3x5=15ものリクエストを飛ばしている。
ネットワークのプロファイリングが出来ていないため、なんとも言えないが、今回の場合ほとんどがリクエストのオーバーヘッド。(100Mbps回線で100kB/sしか使ってない…)
SPDY喋れば、割と解決する。
または、もし画像の配列がわかっていれば、画像をconcatしておいてそれを配信するとか…
同時リクエスト数制限。
同一ドメインに対しての、同時リクエスト数の制限。
正確には分からないけど、iOSは5とかだったはず。(っぽい -> http://stackoverflow.com/questions/15089331/maximize-the-number-of-simultaneous-http-downloads)
つまり、どれだけチューニングしても単一のドメインから配信している限り5個しか同時にリクエスト出来ない=画像を取って来れない
挙動を見ている限り、これは大きく影響してそうな感じ。
Native Appでのドメインシャーディング。変に聞こえるけども、試してみよう。
(改めて見るとやっぱり、5個ずつ取って来ているような挙動だな…)
TCP Slow Start
これは、通信を始めるときに小さい通信量から始めて、どんどん一回の量を大きくしていくアルゴリズム。
普通のHTTP通信ならTCP Slow Startを必ず使っている。
最初の通信量はデスクトップの場合、おおよそ14KB
iOSの数字、もしかしたら小さく設定してあるかもしれないので、12KBの画像は収まりきらないかもしれない。
本来は、回線が速いので、ぶっちゃけ2KBも12KBもあまり変わらないはず。
が、手元の4S 3Gで見ている限りJPEGのほうがワンテンポ遅いので、影響があるということだろう…
(ていうか、TCP Slow Startはkeep-alivedのTCP通信の際どうなるのだろう?不勉強。)
SPDY喋ると抜本的に解決される問題です。
画像のPrefetch。
didScrollのdelegateをもらえるので、それで頑張って色々計算してPrefetchをしたい。
単純にリクエスト飛ばしてキャッシュを期待するだけで済めば良いのだけど、アルゴリズムが面倒。
Prefetchが終わる前に画像が表示されてしまう場合、二重にリクエストを飛ばすことになる。
Prefetchの終了を待ってから画像を表示する賢いViewCellが必要。
とても面倒くさい。
⇒observe bindingすればいいですね。
書きなぐったので、ぐちゃぐちゃ。
あとで整理します。多分。
とりあえずドメインシャーディングと、prefetchをやってみよう。
あとは、DNS lookupとTCP handshakeのための通信をスプラッシュスクリーン時にやるとか出来たら、ファーストビューもっと速くなるのに…
追記:
WebP(2KB)
JPEG(12KB)
CollectionView15枚の画像全てレンダリング。つまり、初期化の画像表示にどれくらい時間がかかるか測った。
iPhone 4S + Wifi
WebP => 0.9s前後
JPEG => 1.3s前後
iPhone 4S + 3G
WebP => 3.0s前後
JPEG => 4.1s前後
3Gモジュールがどことなくイカれているので、常識的な数字ではないと思う…
iPhone 5s(Simulate) + Wifi
WebP => 0.5s前後
JPEG => 0.5s前後
また、スクロールを速くして一度に表示される画像の数を増やしてみると、WebPもJPEGも同じような遅延が見られた。
WebPのソフトウェアデコードの処理かと思ったら、JPEGも遅くなる。
CollectionViewか、またはSDWebImageのオーバーヘッドだと思われる。
追記:
30-50KBくらいまでのオーダのWebPなら4Sでもすぐにレンダリング出来る(オーバーヘッドは0.1s未満)
200KBくらいになると、1s-2sくらいかかり、現実的ではない。(CPU利用率も100%になってしまう…)
31KB(640x853): http://d3lncrho1w0yzl.cloudfront.net/photo1.640x.raw.webp
200KB(2448x3264): http://d3lncrho1w0yzl.cloudfront.net/photo1.raw.webp
が、2448xもの画像を、iPhone等で利用することはないだろう…
追記:
980xの55KBのWebPは、iPhone 4Sだと少し引っ掛かる。具体的には、一瞬だけCPU100%になる。描画は、0.1-0.2秒くらい。
5系だとOK
iPadなら余裕。
iPadは横幅980くらいなので980x。
Retina対応だと2048xくらいになり、WebPでは200KB程度になる。
厳しいかな?と思いきや、iPad RetinaはCPUが強力であまり引っ掛からない。
総じて、デバイスに適切な解像度の画像を配信すれば、デコードは問題にならないように感じました。
55KB(980x): http://d3lncrho1w0yzl.cloudfront.net/photo1.980x.raw.webp
200KB(2448x): http://d3lncrho1w0yzl.cloudfront.net/photo1.raw.webp
結論:
WebPに対して前向きになった