Несмотря на стремительное развитие интернета, проблема оптимизации веб-приложений стоит также остро, как и во времена dialup-подключений по советской вермишели.

В сентябре 2006 года Питер Севчик (Peter Sevcik) и Ребекка Ветцель (Rebecca Wetzel) из компании NetForecast опубликовали документ, именуемый "Field Guide to Application Delivery Systems" («Полевое руководство по системам доставки приложений»). Основное внимание в документе было уделено улучшению производительности приложений в глобальных сетях (wide area network – WAN), и в нем было приведено уравнение

yrav , где

  • R - время ответа. Общее время с запроса страницы пользователем (обычно переходом по ссылке и т.п.) до полной визуализации страницы на его компьютере. Обычно измеряется в секундах.
  • Полезная нагрузка - общее число байтов, отправленных обозревателю, включая разметку и все ресурсы (такие как файлы CSS, JS и изображений).
  • Пропускная способность - скорость передачи данных обозревателю и от него. Может быть асимметрична и представлять несколько скоростей, если данная страница создается из нескольких источников. Обычно усредняется для создания единого выражения пропускной способности в байтах в секунду.
  • AppTurns - число файлов ресурсов, требуемых страницей. Они включают файлы CSS, JS, изображений и любые другие файлы, извлекаемые обозревателем в процессе визуализации страницы. Уравнение учитывает страницу HTML отдельно, добавляя время приема-передачи (round-trip time – RTT) перед выражением AppTurns.
  • RTT - время, необходимое для приема и передачи данных, вне зависимости от числа переданных байтов. Каждый запрос тратит минимум одно RTT для самой страницы. Обычно измеряется в миллисекундах.
  • Cs - время вычислений на сервере. Время, уходящее у кода на запуск, извлечение данных из базы данных и составление ответа, отправляемого обозревателю. Измеряется в миллисекундах.
  • Cc - время вычислений на клиенте. Время, уходящее у обозревателя на визуализацию HTML на экране, исполнение JavaScript, применение правил CSS, итд.

Этот пост будет началом серии статей по оптимизации производительности вашего веб-приложения.

Рассмотрим первый параметр, влияющий на время загрузки страницы – полезная нагрузка или объем полезных данных.

Архивирование(сжатие) контента страницы

Первое, что мы можем сделать - это сжать наш html, css и js код с помощью GZip или Deflate(~40% лучше, чем gzip) сжатия. Не все браузеры принимают сжатый контент, поэтому необходимо проверять, содержит ли запрос заголовок Accept-Encoding со значением “gzip” или “deflate”:

   1: private bool CanGZip(HttpRequest request) 
   2: { 
   3:         string acceptEncoding = request.Headers["Accept-Encoding"]; 
   4:         if (!string.IsNullOrEmpty(acceptEncoding) && 
   5:              (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"))) 
   6:             return true; 
   7:         return false; 
   8: }

Программно сжать контент позволяет пространство имен System.IO.Compression

   1: public static byte[] Compressor(byte[] buffer, string encodingType) 
   2:      { 
   3:          using (MemoryStream memStream = new MemoryStream()) 
   4:          { 
   5:              Stream compress = null; 
   6:  
   7:              // Choose the compression type and make the compression 
   8:              if (String.Equals(encodingType, "gzip", StringComparison.Ordinal)) 
   9:              { 
  10:                  compress = new GZipStream(memStream, CompressionMode.Compress); 
  11:              } 
  12:              else if (String.Equals(encodingType, "deflate", StringComparison.Ordinal)) 
  13:              { 
  14:                  compress = new DeflateStream(memStream, CompressionMode.Compress); 
  15:              } 
  16:              else 
  17:              { 
  18:                  // No compression 
  19:                  return buffer; 
  20:              } 
  21:  
  22:              compress.Write(buffer, 0, buffer.Length); 
  23:              compress.Dispose(); 
  24:  
  25:              return memStream.ToArray(); 
  26:          } 
  27:      } 

После чего вам остается лишь добавить сжатый поток в ответ пользователю:

   1: HttpContext.Response.OutputStream.Write(compressedData, 0, compressedData.Length);

Если же у вас есть доступ к IIS консоли, то вы можете настроить сжатие на уровне IIS(версии 7):

iis_compression1 

iis_compr2

Очистка JS и CSS кода

Также мы можем очистить наш css и js код от лишних символов.

Например, следующий css код

   1: body {
   2:   font-size:14px;
   3:   font-family:Arial;
   4: }
   5: h1 {
   6:   font-size:14px;
   7:   font-family:Arial;
   8: }

можно преобразовать в следующую строку

   1: body,h1{font:14px arial}
 

Основной принцип подобных алгоритмов – это превращение человеко-читемого кода в сжатый код понятный лишь компилятору.

Для javascript кода наибольшей популярностью пользуются два инструмента JSMin (автор: Douglas Crockford) и Packer (автор: Dean Edward). Алгоритмы упаковки обоих инструментов обрабатывают(obfuscate) любой js код, но только при условии, что вы корректно завершаете все javascript-выражения символом “;” (включая объявление функций).

Для css кода вы можете воспользоваться утилитой под названием CSSTidy или одним из множества подобных онлайн-сервисов.

Оптимизация графики

Допустим в вашей базе данных, на жестком диске или любом другом хранилище расположена ваша любимая фотография размером 200x400 пикселей (24 Kb). Но вам необходимо отобразить ее на странице в виде изображения размером 100х200 пикселей. В таком случае, подобный код

   1: <img src="/images/mybestphoto.png" width="100" height="200" alt="My Best Photo" />

решит вашу проблему, но при этом размер вашей фотографии останется прежним - 24 Kb. Хотя, если бы вы сжали(изменили размер) ее в фотопопе, то "вес" файла существенно бы уменьшился.

Для того, чтобы достичь подобного эффекта, вам необходимо программно, на сервере сжать вашу графику. Примерно таким образом:

   1: public void SaveResizedImage(string imagePath, int width, int height)
   2: {
   3:         int newResizeimageId;
   4:         // Читаем графический файл с жесткого диска
   5:         FileStream fs = File.OpenRead(imagePath);
   6:         byte[] data = new byte[fs.Length];
   7:         fs.Read(data, 0, data.Length);
   8:         MemoryStream ms = new MemoryStream(data);
   9:         Bitmap bmp = new Bitmap(ms);
  10:         System.Drawing.Image imgPhoto = (Image)bmp;
  11:         // Настраиваем Encoder, для преобразования графики
  12:         Encoder qualityEncoder = Encoder.Quality;
  13:         EncoderParameters parameterList = new EncoderParameters(1);
  14:         EncoderParameter qualityParameter = new EncoderParameter(qualityEncoder, 50L);
  15:         parameterList.Param[0] = qualityParameter;
  16:         // По расширению файла определяем кодек
  17:         ImageCodecInfo codec = GetCodecFromExtension(Path.GetExtension(imagePath)); //GetCodecFromExtension в этом листинге не приведена.
  18:         // Создаем пустую канву. Измененная графика будет записана в эту канву.
  19:         Bitmap bmPhoto = new Bitmap(width, height);
  20:         bmPhoto.SetResolution(10, 10);
  21:         Graphics grPhoto = Graphics.FromImage(bmPhoto);
  22:         grPhoto.SmoothingMode = SmoothingMode.HighSpeed;
  23:         grPhoto.InterpolationMode = InterpolationMode.High;
  24:         grPhoto.PixelOffsetMode = PixelOffsetMode.HighSpeed;
  25:         grPhoto.DrawImage(imgPhoto, new Rectangle(0, 0, width, height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel);
  26:         // Сохраняем файл в память, а затем убеждаемся, что все ресурсы освобождены корректно.
  27:         MemoryStream mm = new MemoryStream();
  28:         bmPhoto.Save(mm, codec, parameterList);
  29:         imgPhoto.Dispose();
  30:         bmPhoto.Dispose();
  31:         grPhoto.Dispose();
  32:         //Сохраняем массив байт измененной графики куда необходимо. 
  33:         newResizeimageId = SaveImageToDb2(mm.GetBuffer());
  34:         mm.Dispose();
  35: }

Хочу обратить ваше внимание на строчку

   1: bmPhoto.SetResolution(10, 10);

Где мы можем изменить качество нашей графики, установив новое разрешение. Результаты могут быть примерно такими

avatar avatar_0 avatar_2 avatar_5 avatar_8
2.58 kB 2.86 kB (+10%) 1.98 kB
(-23%)
1.55 kB
(-39%)
672 bytes
(-74%)

Обратите внимание на то, что после небольшого ухудшения качества, размер изображения наоборот - возрос. Дальнейшее же уменьшения разрешения привело к уменьшению размера файла. Также учтите, что, чем больше первоначальный размер изображения, тем эффективней будет сжатие без визуальных потерь качества. Ухудшать качество небольших картинок, как видите, смысла нет.

Отключаем ViewState

Возможно вы подумали, о нехитром свойстве страницы EnableViewState="false". Отключение ViewState с помощью вышеупомянутого свойства действительно уменьшит размер скрытого поля, но не избавит вас от него. В отдельных случаях полезно и вовсе избавиться от ViewState. Сделать это можно, переопределив метод SavePageStateToPersistenceMedium, и “забыв” вызвать в нем базовый метод.

   1: protected override void SavePageStateToPersistenceMedium(object state) 
   2: { 
   3:         //base.SavePageStateToPersistenceMedium(state); 
   4: }

Избавляемся от длинных ID, сгенерированных ASP.NET

К сожалению, не существует способа избавиться от длинных ID в debug-версии вашего веб-сайта. Но когда ваш проект уже готов и скомпилирован, то вы можете обработать сборки, обфускатором. При этом, следует указать обфускатору заменять все имена контролов на более короткие. Таким образом вместо ctl00_body_WidgetPanelsLayout_WidgetContainer65_WidgetBodyPanel, ваша панель будет иметь id равное, например А. Будьте осторожны с подобной оптимизацией – ваш javascript код может не работать, если функционал привязан к заранее заданным ID. Хотя, если вы использовали что-то похожее на следующий код

   1: $get('<%= TextBox1.ClientID %>')

то обфускатор не нарушит вашего функционала.

Полезные ссылки

Reduce the weight of stylesheets by 35% at runtime

HTTP compression in ASP.NET 2.0

Compression and performance - GZip vs. Deflate

Compress your pages, css, js and WebResources.axd files for better performance

Online Tool compares Packer, JSMin, Dojo, and YUI Compressor

Speed Up Your Site: Optimize your CSS

CSS Optimization: Make Your Sites Load Faster for Free