テキストベースのアセットのエンコードと転送サイズの最適化

ウェブ アプリケーションは、その対象範囲、規模、機能の点で成長を続けています。それは良いことですが、ウェブを充実させるための絶え間ない進歩は、アプリケーションでダウンロードされるデータの量も一定のペースで増え続ける、という傾向も招いています。パフォーマンスを最適にするには、バイト 1 つ 1 つの配信を最適化することが必要です。

データ圧縮 101

不要なリソースをすべて取り除いたら、次の手順はブラウザがダウンロードする必要がある残りのリソースの合計サイズを最小限に抑える、つまり圧縮することです。リソースの種類(テキスト、画像、フォントなど)に応じて、サーバーで有効な汎用ツール、コンテンツの種類別の前処理最適化、デベロッパーの入力を必要とするリソース別の最適化など、自由に利用できるさまざまな技術があります。

パフォーマンスを最適にするには、このすべての技術を組み合わせる必要があります。

TL;DR

  • 圧縮とは、ビット数を削減して情報をエンコードする処理である
  • 不要なデータを取り除くことで常に満足のいく結果を得る
  • さまざまな圧縮技術と圧縮アルゴリズムがある
  • 最適な圧縮を実現するにはさまざまな技術が必要である

データサイズを縮小する処理のことを「データ圧縮」と呼びます。それだけでも難しい研究分野です。多くの人がその生涯をかけてアルゴリズム、技術、最適化に取り組み、圧縮率、圧縮速度、各種圧縮に必要なメモリ容量を向上させてきました。言うまでもなく、このテーマをすべて説明することがここでの目的ではありませんが、重要なのは、圧縮の仕組みと、ページに必要な各種アセットのサイズを縮小するために自由に利用できる技術を、大まかに理解することです。

実践されているこうした技術の主な方針を示すため、この例のためだけに作成した単純なテキスト メッセージ形式を最適化するにはどうすればよいかを検討してみましょう。

# 以下は秘密のメッセージで、key-value 形式の一連のヘッダー、
# 改行、暗号化メッセージの順に構成されています。
format: secret-cipher
date: 04/04/14
AAAZZBBBBEEEMMM EEETTTAAA
  1. メッセージには、「#」の接頭辞で指定される、任意の注釈が含まれている可能性があります。注釈はメッセージの意味やその他の振る舞いには影響しません。
  2. メッセージには、key-value ペアの「ヘッダー」が(「:」で区切られて)含まれている可能性があります。このヘッダーはメッセージの先頭に表示する必要があります。
  3. メッセージで伝送されるのはテキストのペイロードです。

現時点で長さが 200 文字ある上記のメッセージのサイズを削減するにはどうすればよいでしょうか。

  1. コメントは興味深いですが、メッセージの意味に影響しないことがわかっているので、メッセージを送信する際に取り除きます。
  2. ヘッダーを効率的にエンコードするために使用できる高度な技術がありそうです。たとえば、すべてのメッセージに必ず「format」と「date」があるかどうかはわかりませんが、これらがあれば、短い整数 ID に変換しその ID を送信することができます。ただ、このケースに当てはまるかどうかわからないので、とりあえずヘッダーはそのまま残しておきます。
  3. ペイロードはテキストのみです。テキストのコンテンツが実際に何かはわかりませんが(「秘密のメッセージ」を使用していることは明らかですが)、テキストを見ると、かなり冗長性があるようです。文字の繰り返しを送信するのではなく、繰り返されている文字の数を数えることで、もっと効率的にエンコードできるのではないでしょうか。
    • たとえば、「AAA」は「3A」になります。A が 3 つ連続しているという意味です。

この技術を組み合わせると、次のような結果になります。

format: secret-cipher
date: 04/04/14
3A2Z4B3E3M 3E3T3A

新しいメッセージの長さは 56 文字です。つまり、元のメッセージを 72% も圧縮できたことになります。すべて考慮されており、始めたばかりにしては上出来です。

もちろん、判断に迷うこともあります。これは上出来ですが、このことがウェブページの最適化にどう役立つのでしょうか。圧縮アルゴリズムを発明しようとしているわけではありません。発明するわけではありませんが、おわかりのように、最適化するページ上のリソースは変わっても、前処理、コンテキスト別の最適化、コンテンツ別のアルゴリズムなど、使用する技術と考え方はまったく同じです。

縮小化: 前処理とコンテキスト別の最適化

TL;DR

  • コンテンツ別の最適化により、配信するリソースのサイズを大幅に削減できる
  • コンテンツ別の最適化はビルド / リリース サイクルの一部として適用することが推奨される

冗長なデータや不要なデータを圧縮する最も良い方法は、一緒に取り除くことです。もちろん、データをやたらに削除することはできませんが、データ形式とそのプロパティについてコンテンツ別の知識があれば、実際の意味に影響を与えることなくペイロードのサイズを大幅に削減することが可能です。

<html>
  <head>
  <style>
     /* awesome-container is only used on the landing page */
     .awesome-container { font-size: 120% }
     .awesome-container { width: 50% }
  </style>
 </head>

 <body>
   <!-- awesome container content: START -->
    <div>…</div>
   <!-- awesome container content: END -->
   <script>
     awesomeAnalytics(); // beacon conversion metrics
   </script>
 </body>
</html>

上記のシンプルな HTML ページと、そのページに含まれる 3 つの異なるコンテンツの種類(HTML マークアップ、CSS スタイル、JavaScript)を考えてみましょう。コンテンツの種類ごとに、有効な HTML マークアップを作成するためのルール、CSS のルール、JavaScript コンテンツのルールや、コメントを示すためのルールなどは異なります。このページのサイズを縮小するにはどうすればよいでしょうか。

  • コードのコメントはデベロッパーにとっては非常に役立ちますが、ブラウザでコメントを表示する必要はありません。CSS(//)、HTML()、JavaScript(// …)の各コメントを除去するだけで、ページの合計サイズは大幅に縮小されます。
  • 「スマート」な CSS 圧縮ツールがあれば、「.awesome-container」のルール」が効率よく定義されていないことが認識され、他のスタイルに影響を与えずに 2 つの宣言を 1 つにできるため、バイト数はさらに削減されます。
  • 空白(スペースとタブ)は、デベロッパーが HTML、CSS、JavaScript で便宜上使っているものです。タブとスペースをすべて削除できる圧縮ツールもあります。
<html><head><style>.awesome-container{font-size:120%;width: 50%}
</style></head><body><div>…</div><script>awesomeAnalytics();
</script></body></html>

上記の手順を適用すれば、ページは最終的に 406 文字から 150 文字に減ります。圧縮率 63% です。確かにあまり読みやすくはありませんが、読みやすくする必要もありません。元のページは「開発バージョン」として保存しておき、ページをウェブサイトに公開できる準備ができたらいつでも上記の手順を適用できます。

一歩離れてみると、上記の例から重要な点がわかります。汎用の圧縮ツール、いわゆる任意のテキストを圧縮するように作られたツールは、上記のページを圧縮するといった処理には適しているかもしれませんが、コメントの削除や CSS ルールの縮小化など、その他のコンテンツ別のさまざまな最適化は認識していません。前処理、縮小化、コンテキストに対応した最適化はこのような強力なツールになる可能性があるのはこのためです。

同様に、上記の技術は単なるテキストベースのアセット以外にも応用できます。画像、動画などの種類のコンテンツにはすべて、独自の形式のメタデータや各種のペイロードが含まれています。たとえば、カメラで写真を取るたびに、通常、写真にはカメラの設定、場所といった余分な多くの情報が埋め込まれます。アプリケーションによって、このデータが重要になる(写真共有サイトなど)場合もあれば、まったく逆の場合もあります。削除した方がよいかどうかを検討することが必要です。実際、このメタデータがあると、画像ごとに最大数十キロバイト加算される可能性があります。

要するに、アセットの効率性を最適化する最初の手順は、さまざまなコンテンツの種類の一覧を作成し、どのような種類のコンテンツ別の最適化を適用すればサイズを縮小できるかを検討することです。こうすることで、容量を大幅に削減できます。何が最適化になるかを判断したら、次に、その最適化をビルドとリリースのプロセスに追加して自動化します。これが、最適化を確実に実施できる唯一の方法です。

GZIP によるテキストの圧縮

TL;DR

  • GZIP はテキストベースのアセット(CSS、JavaScript、HTML)に対して実行することが推奨される
  • 最新のブラウザはすべて GZIP 圧縮に対応し、自動的にこの圧縮をリクエストする
  • サーバーを GZIP 圧縮に対応するように設定する必要がある
  • 一部の CDN は GZIP に対応していることに特に注意して確認する必要がある

GZIP はどのようなバイトストリームにも適用できる汎用の圧縮ツールです。内部で GZIP は以前表示されたコンテンツの一部を記憶し、重複するデータ フラグメントを効率よく検索して置換することを試みます。関心があれば、詳しい GZIP の説明をご確認ください。ただし、実際のところ、GZIP で最適なパフォーマンスが得られるのはテキストベースのコンテンツで、大きいファイルでは圧縮率は 70~90% になりますが、別のアルゴリズムで既に圧縮されているアセットで GZIP を実行しても、ほとんどまたはまったく改善は見られません。

最新のブラウザはすべて GZIP 圧縮に対応し、すべての HTTP リクエストで GZIP 圧縮のネゴシエーションが自動的に実施されます。ここで必要なことは、クライアントからリクエストされたときに圧縮後のリソースを提供するようにサーバーが正しく設定されていることを確認することです。

ライブラリ サイズ 圧縮後のサイズ 圧縮率
jquery-1.11.0.js 276 KB 82 KB 70%
jquery-1.11.0.min.js 94 KB 33 KB 65%
angular-1.2.15.js 729 KB 182 KB 75%
angular-1.2.15.min.js 101 KB 37 KB 63%
bootstrap-3.1.1.css 118 KB 18 KB 85%
bootstrap-3.1.1.min.css 98 KB 17 KB 83%
foundation-5.css 186 KB 22 KB 88%
foundation-5.min.css 146 KB 18 KB 88%

上記の表は、人気がある JavaScript ライブラリと CSS フレームワークに GZIP を適用した場合の圧縮率を示したものです。圧縮率の範囲は 60~88% です。縮小化されたファイル(ファイル名に「.min」があるファイル)に GZIP を適用すると圧縮率がさらに向上することがわかります。

  1. 最初にコンテンツ別の最適化を適用する: CSS、JS、HTML の縮小化ツール。
  2. GZIP を適用して縮小化後の出力を圧縮する。

一番大切な点は、GZIP 対応であることが、実装する最もシンプルで最も見返りのある最適化の 1 つだ、ということです。残念ながら、多くの人が GZIP の実装を忘れています。ほとんどのウェブサーバーは自動的にコンテンツを圧縮してくれるので、あとは、GZIP 圧縮が有効なすべての種類のコンテンツを圧縮するようにサーバーが正しく設定されていることを確認することだけです。

サーバーにとって最適な設定とはどのようなものでしょうか。HTML5 Boilerplate プロジェクトに、人気の高いすべてのサーバーに対応するサンプル構成ファイルが含まれ、構成フラグと設定ごとに詳しいコメントが記載されています。リストでお気に入りのサーバーを見つけたら、GZIP セクションで探して、推奨されている設定でサーバーが設定されていることをご確認ください。

実サイズと転送サイズの DevTools のデモ

GZIP の動作は、Chrome DevTools を起動して [Network] パネルの [Size] と [Content] 列を調べれば簡単に確認できます。[Size] はアセットの転送サイズを示し、[Content] はアセットの圧縮前のサイズを示しています。上記の例の HTML アセットでは、GZIP により転送時に 24.8 KB 削減されます。

最後にアドバイスを 1 つ。ほとんどのサーバーはユーザーにアセットを提供する際に自動的にそのアセットを圧縮してくれますが、一部の CDN では、GZIP アセットが提供されていることを手動で確認する必要があるため特にご注意ください。サイトを監査し、アセットが実際に圧縮されていることを確認してください。