Советы и рекомендации по повышению производительности в приложениях .NET

 

Эммануэль Шанцер
Microsoft Corporation

Август 2001 г.

Сводка: Эта статья предназначена для разработчиков, которые хотят настроить свои приложения для обеспечения оптимальной производительности в управляемом мире. Примеры кода, пояснения и рекомендации по проектированию рассматриваются для баз данных, приложений Windows Forms и ASP, а также для конкретных языков советы по Microsoft Visual Basic и управляемому C++. (25 печатных страниц)

Содержимое

Общие сведения
Советы по повышению производительности для всех приложений
Советы по доступу к базе данных
Советы по повышению производительности для приложений ASP.NET
Советы по переносу и разработке в Visual Basic
Советы по переносу и разработке на управляемом C++
Дополнительные ресурсы
Приложение. Стоимость виртуальных вызовов и выделений

Общие сведения

Этот технический документ предназначен для разработчиков, которые пишут приложения для .NET и ищут различные способы повышения производительности. Если вы являетесь разработчиком, который не знаком с .NET, вы должны быть знакомы как с платформой, так и с выбранным языком. Этот документ основан исключительно на этих знаниях и предполагает, что программист уже знает достаточно, чтобы запустить программу. Если вы переносите существующее приложение в .NET, рекомендуется ознакомиться с этим документом, прежде чем приступить к переносу. Некоторые из приведенных здесь советов полезны на этапе разработки и предоставляют сведения, о которых следует знать, прежде чем приступить к переносу.

Этот документ разделен на сегменты с советами, упорядоченными по типу проекта и разработчика. Первый набор советов является обязательным для чтения для написания на любом языке и содержит советы, которые помогут вам с любым целевым языком в среде CLR. Ниже приведен соответствующий раздел с советами для ASP. Второй набор советов упорядочен по языку и посвящен конкретным советам по использованию управляемого C++ и Microsoft® Visual Basic®.

Из-за ограничений по расписанию время выполнения версии 1 (версии 1) сначала было ориентировано на самые широкие функциональные возможности, а затем работать с оптимизацией в особых случаях позже. Это приводит к возникновению нескольких случаев, когда производительность становится проблемой. Таким образом, в этой статье рассматривается несколько советов, которые предназначены для того, чтобы избежать этого случая. Эти советы не будут актуальны в следующей версии (vNext), так как эти случаи систематически выявляются и оптимизируются. Я укажу их, как мы идем, и это до вас, чтобы решить, стоит ли это усилий.

Советы по повышению производительности для всех приложений

При работе со средой CLR на любом языке следует помнить о нескольких советах. Они актуальны для всех и должны быть первой линией защиты при решении проблем с производительностью.

Создание меньшего количества исключений

Создание исключений может быть очень дорогостоящим, поэтому убедитесь, что вы не создаете много из них. Используйте Perfmon, чтобы узнать, сколько исключений вызывает приложение. Вас может удивить тот факт, что в некоторых областях приложения возникает больше исключений, чем ожидалось. Для повышения степени детализации можно также программно проверка номер исключения с помощью счетчиков производительности.

Поиск и разработка далекого кода с большим количеством исключений может привести к достойной победе производительности. Имейте в виду, что это не имеет ничего общего с блоками try/catch: плата взимается только при возникновении фактического исключения. Вы можете использовать любое количество блоков try/catch. Безвозмездное использование исключений — это то, где вы теряете производительность. Например, следует держаться подальше от таких действий, как использование исключений для потока управления.

Вот простой пример того, как могут быть дорогостоящие исключения: мы просто пройдем цикл For , создав тысячи или исключения, а затем завершим. Попробуйте закомментировать оператор throw, чтобы увидеть разницу в скорости: эти исключения приводят к огромным издержкам.

public static void Main(string[] args){
  int j = 0;
  for(int i = 0; i < 10000; i++){
    try{   
      j = i;
      throw new System.Exception();
    } catch {}
  }
  System.Console.Write(j);
  return;   
}
  • Остерегайтесь! Время выполнения может создавать исключения самостоятельно! Например, Response.Redirect() создает исключение ThreadAbort . Даже если вы не создаете явным образом исключения, вы можете использовать функции, которые это делают. Убедитесь, что вы проверка Perfmon, чтобы получить реальную историю, и отладчик, чтобы проверка источник.
  • Для разработчиков Visual Basic: Visual Basic включает проверку целого числа по умолчанию, чтобы убедиться, что такие вещи, как переполнение и деление на ноль, вызывают исключения. Вы можете отключить эту функцию, чтобы повысить производительность.
  • При использовании COM следует помнить, что HRESULTS может возвращать в виде исключений. Убедитесь, что вы внимательно отслеживаете их.

Создание блюзгих вызовов

Фрагментный вызов — это вызов функции, выполняющий несколько задач, например метод, который инициализирует несколько полей объекта. Это следует просматривать при чатных вызовах, которые выполняют очень простые задачи и требуют нескольких вызовов для выполнения задач (например, установка каждого поля объекта с помощью другого вызова). Важно выполнять блоки, а не болтливые вызовы между методами, в которых издержки выше, чем для простых вызовов методов внутри appDomain. Вызовы P/Invoke, взаимодействия и удаленного взаимодействия несут накладные расходы, и вы хотите использовать их экономно. В каждом из этих случаев следует попытаться спроектировать приложение таким образом, чтобы оно не зависела от небольших частых вызовов, которые несут такую нагрузку.

Переход происходит при каждом вызове управляемого кода из неуправляемого кода и наоборот. Время выполнения очень упрощает для программиста взаимодействие, но это происходит по цене производительности. При переходе необходимо выполнить следующие действия.

  • Маршалинг данных
  • Исправление соглашения о вызовах
  • Защита регистров, сохраненных вызывающим абонентом
  • Переключение режима потоков, чтобы сборка мусора не блокировали неуправляемые потоки
  • Создание кадра обработки исключений при вызовах в управляемый код
  • Управление потоком (необязательно)

Чтобы ускорить переход, попробуйте использовать P/Invoke, когда это возможно. Накладные расходы — всего 31 инструкция плюс стоимость маршалинга, если требуется маршалинг данных, и только 8 в противном случае. COM-взаимодействие гораздо дороже, принимая свыше 65 инструкций.

Маршалирование данных не всегда является дорогостоящим. Примитивные типы почти не требуют маршалинга, а классы с явным макетом также дешевы. Реальное замедление происходит при переводе данных, например при преобразовании текста из ASCI в Юникод. Убедитесь, что данные, передаваемые через управляемую границу, преобразуются только в том случае, если это необходимо. Может оказаться, что, просто согласовав определенный тип данных или формат в вашей программе, можно сократить много затрат на маршалинг.

Следующие типы называются преобразуемыми, то есть их можно копировать непосредственно через управляемую или неуправляемую границу без маршалинга: sbyte, byte, short, ushort, int, uint, long, ulong, float и double. Их можно передать бесплатно, а также ValueTypes и одномерные массивы, содержащие непреобразуемые типы. Песчаные детали маршалинга можно изучить далее на библиотека MSDN. Я рекомендую внимательно прочитать его, если вы тратите много времени маршалинга.

Проектирование с использованием ValueTypes

Используйте простые структуры, когда это возможно, и когда вы не делаете много боксов и распаковки. Ниже приведен простой пример, демонстрирующий разницу в скорости.

using System;

namespace ConsoleApplication{

  public struct foo{
    public foo(double arg){ this.y = arg; }
    public double y;
  }
  public class bar{
    public bar(double arg){ this.y = arg; }
    public double y;
  }
  class Class1{
    static void Main(string[] args){
      System.Console.WriteLine("starting struct loop...");
      for(int i = 0; i < 50000000; i++)
      {foo test = new foo(3.14);}
      System.Console.WriteLine("struct loop complete. 
                                starting object loop...");
      for(int i = 0; i < 50000000; i++)
      {bar test2 = new bar(3.14); }
      System.Console.WriteLine("All done");
    }
  }
}

При выполнении этого примера вы увидите, что цикл структуры выполняется на порядок быстрее. Однако важно остерегаться использования ValueTypes при обращении с ними как с объектами. Это добавляет дополнительные бокс и распаковки накладные расходы на программу, и может в конечном итоге стоить вам больше, чем если бы вы застряли с объектами! Чтобы увидеть это в действии, измените приведенный выше код, чтобы использовать массив foos и баров. Вы увидите, что производительность более или менее равна.

Компромиссы ValueType гораздо менее гибки, чем объекты, и в конечном итоге снижает производительность при неправильном использовании. Вы должны быть очень осторожны, когда и как вы их используете.

Попробуйте изменить приведенный выше пример и сохранить foos и гистограммы в массивах или хэш-диаграммах. Вы увидите, что увеличение скорости исчезнет, только с одной операции бокса и распаковки.

Вы можете отслеживать, насколько сильно вы упаковываете и распаковку, просматривая выделения сборок мусора и коллекции. Это можно сделать с помощью внешней среды Perfmon или счетчиков производительности в коде.

Подробное обсуждение ValueTypes см. в разделе Рекомендации по производительности Run-Time Технологий в платформа .NET Framework.

Добавление групп с помощью AddRange

Используйте AddRange , чтобы добавить всю коллекцию, а не каждый элемент в коллекции итеративно. Почти все элементы управления и коллекции Windows имеют методы Add и AddRange , и каждый из них оптимизирован для разных целей. Добавление полезно для добавления одного элемента, в то время как AddRange имеет дополнительные издержки, но выигрывает при добавлении нескольких элементов. Ниже приведены лишь некоторые классы, поддерживающие функции Add и AddRange.

  • StringCollection, TraceCollection и т. д.
  • HttpWebRequest
  • UserControl
  • ColumnHeader

Обрезка рабочего набора

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

Отслеживание рабочего набора сложно, и, вероятно, может быть предметом всей статьи. Вот несколько советов, которые помогут вам:

  • Используйте vadump.exe для отслеживания рабочего набора. Это рассматривается в другом техническом документе, в котором рассматриваются различные средства для управляемой среды.
  • Обратите внимание на Perfmon или Счетчики производительности. Они могут предоставлять подробные отзывы о количестве загружаемых классов или количестве методов, которые получают JITed. Вы можете получить сведения о том, сколько времени вы проводите в загрузчике или какой процент времени выполнения тратится на разбиение по страницам.

Использование циклов For для итерации строк версии 1

В C# ключевое слово foreach позволяет выполнять действия по элементам списка, строки и т. д., а также выполнять операции с каждым элементом. Это очень мощное средство, так как оно действует как перечислитель общего назначения для многих типов. Компромиссом для этого обобщения является скорость, и если вы в значительной степени полагаетесь на итерацию строк, следует использовать цикл For . Так как строки являются простыми массивами символов, их можно выполнять с гораздо меньшими затратами, чем другие структуры. JIT достаточно умен (во многих случаях), чтобы оптимизировать проверку границ и другие вещи внутри цикла For , но запрещено делать это на foreach walks. В итоге в версии 1 цикл For для строк выполняется в пять раз быстрее, чем при использовании foreach. В будущих версиях это изменится, но для версии 1 это определенный способ повысить производительность.

Ниже приведен простой метод тестирования, демонстрирующий разницу в скорости. Попробуйте запустить его, а затем удалите цикл For и раскомментируйте оператор foreach . На моем компьютере цикл For занял около секунды и около 3 секунд для оператора foreach .

public static void Main(string[] args) {
  string s = "monkeys!";
  int dummy = 0;

  System.Text.StringBuilder sb = new System.Text.StringBuilder(s);
  for(int i = 0; i < 1000000; i++)
    sb.Append(s);
  s = sb.ToString();
  //foreach (char c in s) dummy++;
  for (int i = 0; i < 1000000; i++)
    dummy++;
  return;   
  }
}

КомпромиссыForeach гораздо более удобочитаемыми, и в будущем он станет таким же быстрым, как цикл For для особых случаев, таких как строки. Если только работа со строками не является реальной производительностью, слегка беспорядочный код может не стоить.

Использование StringBuilder для сложных операций со строками

При изменении строки время выполнения создаст новую строку и вернет ее, оставив исходный объект для сборки мусора. В большинстве случаев это быстрый и простой способ сделать это, но когда строка постоянно изменяется, это становится бременем для производительности: все эти выделения в конечном итоге станут дорогостоящими. Ниже приведен простой пример программы, которая добавляет к строке 50 000 раз, а затем использует объект StringBuilder для изменения строки на месте. Код StringBuilder выполняется гораздо быстрее, и при его выполнении он сразу становится очевидным.

namespace ConsoleApplication1.Feedback{
  using System;
  
  public class Feedback{
    public Feedback(){
      text = "You have ordered: \n";
    }
    public string text;
    public static int Main(string[] args) {
      Feedback test = new Feedback();
      String str = test.text;
      for(int i=0;i<50000;i++){
        str = str + "blue_toothbrush";
      }
      System.Console.Out.WriteLine("done");
      return 0;
    }
  }
}
namespace ConsoleApplication1.Feedback{
  using System;
  public class Feedback{
    public Feedback(){
      text = "You have ordered: \n";
    }
    public string text;
    public static int Main(string[] args) {
      Feedback test = new Feedback();
      System.Text.StringBuilder SB = 
        new System.Text.StringBuilder(test.text);
      for(int i=0;i<50000;i++){
        SB.Append("blue_toothbrush");
      }
      System.Console.Out.WriteLine("done");
      return 0;
    }
  }
}

Попробуйте взглянуть на Perfmon, чтобы узнать, сколько времени экономится без выделения тысяч строк. Просмотрите счетчик "% времени в сборке мусора" в списке память .NET CLR. Вы также можете отслеживать количество сохраненных выделений, а также статистику коллекций.

Компромиссы= Создание объекта StringBuilder связано с некоторыми издержками как во времени, так и в памяти. На компьютере с быстрой памятью stringBuilder становится полезным, если вы выполняете около пяти операций. Как правило, я бы сказал, что 10 или более строковых операций являются оправданием для накладных расходов на любом компьютере, даже медленнее.

Приложения предварительной компиляции Windows Forms

Методы имеют значение JITed при первом использовании, что означает, что вы платите больший штраф за запуск, если приложение выполняет много вызовов методов во время запуска. Windows Forms использовать много общих библиотек в ОС, и затраты на их запуск могут быть гораздо выше, чем у других типов приложений. Хотя это не всегда так, предварительная компиляция Windows Forms приложений обычно приводит к победе в производительности. В других сценариях обычно лучше разрешить JIT-файлу позаботиться о нем, но если вы являетесь разработчиком Windows Forms, вы можете взглянуть на них.

Корпорация Майкрософт позволяет предварительно компилировать приложение, вызывая ngen.exeметод . Вы можете выполнить ngen.exe во время установки или перед распространением приложения. Безусловно, имеет смысл запускать ngen.exe во время установки, так как вы можете убедиться, что приложение оптимизировано для компьютера, на котором оно устанавливается. Если вы запускаете ngen.exe перед отправкой программы, вы ограничиваете оптимизацию теми, которые доступны на вашем компьютере. Чтобы дать вам представление о том, насколько может помочь предварительная компиляция, я запускаю неофициальный тест на своем компьютере. Ниже приведено время холодного запуска для ShowFormComplex, приложения winforms с примерно сотнями элементов управления.

Состояние кода Time
Платформа JITed

ShowFormComplex JITed

3,4 с
Предварительно скомпилированная платформа, ShowFormComplex JITed 2,5 с
Предварительно скомпилированные платформы, ShowFormComplex Precompiled 2.1sec

Каждый тест был выполнен после перезагрузки. Как видите, Windows Forms приложения используют множество методов заранее, что значительно выигрывает производительность предварительной компиляции.

Использование массивов jagged — версия 1

JIT версии 1 оптимизирует массивы массивов (просто массивы массивов) более эффективно, чем прямоугольные массивы, и разница вполне заметна. Ниже приведена таблица, демонстрирующая повышение производительности, полученное в результате использования массивов неровных массивов вместо прямоугольных в C# и Visual Basic (чем большее число, тем лучше):

  C# Visual Basic 7
Назначение (неровно)

Назначение (прямоугольное)

14.16

8.37

12.24

8.62

Нейронная сеть (неровная)

Нейронная сеть (прямоугольная)

4.48

3.00

4.58

3.13

Числовая сортировка (неровная)

Числовая сортировка (прямоугольная)

4.88

2.05

5.07

2.06

Эталон назначения — это простой алгоритм назначения, основанный на пошаговом руководстве по количественному принятию решений для бизнеса (Гордон, Прессман и Кон; Prentice-Hall; не печатается). Тест нейронной сети выполняет ряд шаблонов в небольшой нейронной сети, и числовая сортировка является понятной. В совокупности эти показатели производительности являются хорошим показателем реальной производительности.

Как видите, использование массивов с массивами может привести к значительному увеличению производительности. Оптимизации массивов с массивами будут добавлены в будущие версии JIT, но для версии 1 вы можете сэкономить много времени, используя массивы с массивами.

Размер буфера операций ввода-вывода должен быть от 4 ДО 8 КБ

Почти для каждого приложения буфер от 4 ДО 8 КБ обеспечит максимальную производительность. Для очень конкретных экземпляров вы можете получить улучшение от большего буфера (например, загрузка больших изображений прогнозируемого размера), но в 99,99% случаев это будет тратить только память. Все буферы, производные от BufferedStream, позволяют задать любой размер, но в большинстве случаев 4 и 8 обеспечивают оптимальную производительность.

Будьте в поиске возможностей асинхронного ввода-вывода

В редких случаях вы можете воспользоваться преимуществами асинхронных операций ввода-вывода. Одним из примеров может быть скачивание и распаковка ряда файлов: вы можете считывать биты из одного потока, декодировать их на ЦП и записывать в другой. Для эффективного использования асинхронных операций ввода-вывода требуется много усилий, и это может привести к снижению производительности, если сделать это неправильно. Преимущество заключается в том, что при правильном применении асинхронные операции ввода-вывода могут обеспечить более высокую производительность в десять раз.

Отличный пример программы, использующий асинхронные операции ввода-вывода, доступен на библиотека MSDN.

  • Следует отметить, что для асинхронных вызовов возникает небольшая нагрузка на безопасность: при вызове асинхронного вызова состояние безопасности стека вызывающего объекта фиксируется и передается в поток, который фактически выполняет запрос. Это может не беспокоить, если обратный вызов выполняет много кода или если асинхронные вызовы не используются чрезмерно

Советы по доступу к базе данных

Философия настройки доступа к базе данных заключается в том, чтобы использовать только необходимые функции и проектировать на основе "отключенного" подхода: установить несколько соединений последовательно, а не держать одно подключение открытым в течение длительного времени. Необходимо принять во внимание это изменение и спроектировать его.

Корпорация Майкрософт рекомендует использовать N-уровневую стратегию для обеспечения максимальной производительности, а не прямое подключение клиента к базе данных. Рассматривайте это как часть вашей философии проектирования, так как многие из технологий оптимизированы для использования сценариев с множественной усталостью.

Использование оптимального управляемый поставщик

Сделайте правильный выбор управляемого поставщика, а не на универсальный метод доступа. Существуют управляемые поставщики, написанные специально для многих разных баз данных, таких как SQL (System.Data.SqlClient). Если вы используете более универсальный интерфейс, например System.Data.Odbc, при использовании специализированного компонента, вы потеряете производительность при работе с дополнительным уровнем косвенного обращения. Используя оптимальный поставщик, вы также можете говорить на другом языке: управляемый клиент SQL говорит TDS с базой данных SQL, обеспечивая значительное улучшение по сравнению с универсальным OleDbprotocol.

Выбор средства чтения данных в наборе данных, когда это возможно

Используйте средство чтения данных всякий раз, когда вам не нужно хранить данные. Это позволяет быстро считывать данные, которые можно кэшировать по желанию пользователя. Средство чтения — это просто поток без отслеживания состояния, который позволяет считывать данные по мере их поступления, а затем удалять их без сохранения в наборе данных для более подробной навигации. Потоковый подход работает быстрее и имеет меньшие издержки, так как вы можете сразу же приступить к использованию данных. Следует оценить, как часто требуются одни и те же данные, чтобы решить, имеет ли смысл кэширование для навигации. Ниже приведена небольшая таблица, демонстрирующая разницу между DataReader и DataSet в поставщиках ODBC и SQL при извлечении данных с сервера (большее число лучше):

  ADO SQL
DataSet 801 2507
DataReader 1083 4585

Как видите, максимальная производительность достигается при использовании оптимального управляемого поставщика вместе со средством чтения данных. Если вам не нужно кэшировать данные, использование средства чтения данных может значительно повысить производительность.

Использование Mscorsvr.dll для компьютеров mp

Для автономных приложений среднего уровня и серверных приложений убедитесь, что mscorsvr используется для многопроцессорных компьютеров. Mscorwks не оптимизирован для масштабирования или пропускной способности, в то время как версия сервера имеет несколько оптимизаций, которые позволяют ей масштабироваться при наличии нескольких процессоров.

Использование хранимых процедур по возможности

Хранимые процедуры — это высокооптимизированные средства, которые при эффективном использовании позволяют повысить производительность. Настройте хранимые процедуры для обработки вставок, обновлений и удалений с помощью адаптера данных. Хранимые процедуры не нужно интерпретировать, компилировать или даже передавать от клиента, а также сокращать нагрузку на сетевой трафик и сервер. Обязательно используйте CommandType.StoredProcedure вместо CommandType.Text.

Будьте осторожны с динамическими строками подключения

Пул подключений — это полезный способ повторного использования подключений для нескольких запросов, а не оплаты затрат на открытие и закрытие подключения для каждого запроса. Это делается неявно, но вы получаете один пул для каждой уникальной строки подключения. Если вы создаете строки подключения динамически, убедитесь, что строки идентичны каждый раз, чтобы происходит объединение в пул. Также имейте в виду, что при делегировании вы получите один пул на каждого пользователя. Существует множество параметров, которые можно задать для пула подключений, и вы можете отслеживать производительность пула, используя perfmon для отслеживания таких вещей, как время отклика, транзакции в секунду и т. д.

Отключение функций, которые вы не используете

Отключите автоматическое зачисление транзакций, если оно не требуется. Для управляемый поставщик SQL это выполняется с помощью строки подключения:

SqlConnection conn = new SqlConnection(
"Server=mysrv01;
Integrated Security=true;
Enlist=false");

При заполнении набора данных адаптером данных не получите сведения о первичном ключе, если это не требуется (например, не устанавливайте MissingSchemaAction.Add с ключом):

public DataSet SelectSqlSrvRows(DataSet dataset,string connection,string query){
    SqlConnection conn = new SqlConnection(connection);
    SqlDataAdapter adapter = new SqlDataAdapter();
    adapter.SelectCommand = new SqlCommand(query, conn);
    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
    adapter.Fill(dataset);
    return dataset;
}

Избегайте автоматически созданных команд

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

Остерегайтесь устаревшей архитектуры ADO

Имейте в виду, что при выполнении команды или вызова fill на адаптере возвращается каждая запись, указанная в запросе.

Если серверные курсоры абсолютно необходимы, их можно реализовать с помощью хранимой процедуры в t-sql. Избегайте там, где это возможно, поскольку реализации на основе серверных курсоров не очень хорошо масштабируются.

При необходимости реализуйте разбиение по страницам без отслеживания состояния и без подключения. Вы можете добавить дополнительные записи в набор данных, выполнив следующие действия:

  • Наличие сведений о PK
  • Соответствующим образом измените команду select адаптера данных и
  • Вызывающая заливка

Экономьте наборы данных

Помещайте в набор данных только необходимые записи. Помните, что набор данных хранит все свои данные в памяти и что чем больше данных вы запрашиваете, тем дольше они будут передаваться по сети.

Использовать последовательный доступ как можно чаще

С помощью средства чтения данных используйте CommandBehavior.SequentialAccess. Это важно для работы с типами данных BLOB-объектов, так как позволяет считывать данные из провода небольшими блоками. Хотя одновременно можно работать только с одной частью данных, задержка при загрузке большого типа данных исчезает. Если вам не нужно одновременно работать со всем объектом, использование последовательного доступа обеспечит гораздо более высокую производительность.

Советы по повышению производительности для приложений ASP.NET

Агрессивное кэширование

При проектировании приложения с использованием ASP.NET убедитесь, что вы разрабатываете с прицелом на кэширование. В серверных версиях ОС имеется множество вариантов настройки использования кэшей на стороне сервера и клиента. В ASP есть несколько функций и средств, которые можно использовать для повышения производительности.

Кэширование выходных данных — сохраняет статический результат asp-запроса. Указывается с помощью директивы <@% OutputCache %> :

  • Duration — элемент time существует в кэше.
  • VaryByParam — изменяется записи кэша в зависимости от параметров Get/Post.
  • VaryByHeader — отличается от записей кэша в зависимости от заголовка HTTP.
  • VaryByCustom — зависит от записей кэша в разных браузерах.
  • Переопределите для изменения в зависимости от того, что вы хотите:
    • Кэширование фрагментов. Если невозможно сохранить всю страницу (конфиденциальность, персонализация, динамическое содержимое), можно использовать кэширование фрагментов для хранения ее частей для более быстрого получения в дальнейшем.

      a) VaryByControl — изменяется кэшированные элементы в зависимости от значений элемента управления.

    • API кэша — обеспечивает очень кратную степень детализации для кэширования, сохраняя хэш-таблицы кэшированных объектов в памяти (System.web.UI.caching). Он также:

      a) Включает зависимости (ключ, файл, время)

      б) Автоматически истекает срок действия неиспользуемых элементов

      в) Поддерживает обратные вызовы

Интеллектуальное кэширование может обеспечить отличную производительность, и важно подумать о том, какой тип кэширования вам нужен. Представьте себе сложный сайт электронной коммерции с несколькими статическими страницами для входа, а затем множеством динамически создаваемых страниц, содержащих изображения и текст. Вы можете использовать кэширование выходных данных для этих страниц входа, а затем кэширование фрагментов для динамических страниц. Например, панель инструментов может быть кэширована как фрагмент. Для повышения производительности можно кэшировать часто используемые образы и стандартный текст, которые часто появляются на сайте с помощью API кэша. Подробные сведения о кэшировании (с примером кода) проверка веб-сайт ASP. NET.

Использовать состояние сеанса только в том случае, если необходимо

Одной из чрезвычайно мощных функций ASP.NET является его возможность хранить состояние сеанса для пользователей, например корзину для покупок на сайте электронной коммерции или журнал браузера. Так как эта функция включена по умолчанию, вы платите за память, даже если ее не используете. Если вы не используете состояние сеанса, отключите его и сэкономите накладные расходы, добавив <@% EnabledSessionState = false %> в asp. Это связано с несколькими другими вариантами, которые описаны на веб-сайте ASP. NET .

Для страниц, которые считывают только состояние сеанса, можно выбрать EnabledSessionState=readonly. Это дешевле, чем полное состояние сеанса чтения и записи, и полезно, если вам нужна только часть функциональных возможностей и вы не хотите платить за возможности записи.

Использовать состояние просмотра только в том случае, если необходимо

Примером состояния представления может быть длинная форма, которую пользователи должны заполнить: если они нажмет кнопку Назад в браузере, а затем вернутся, форма останется заполненной. Если эта функция не используется, это состояние потребляют память и производительность. Возможно, самым большим снижением производительности здесь является то, что сигнал кругового пути должен передаваться по сети каждый раз при загрузке страницы для обновления и проверки кэша. Так как он включен по умолчанию, необходимо указать, что вы не хотите использовать состояние представления с <@% EnabledViewState = false %>. Ознакомьтесь с дополнительными сведениями о просмотре состояния на веб-сайте ASP. NET , чтобы узнать о других параметрах и параметрах, к которым у вас есть доступ.

Избегайте STA COM

Модель COM apartment предназначена для работы с потоками в неуправляемых средах. Существует два типа com-модели apartment: однопотоковая и многопоточная. Служба MTA COM предназначена для многопоточности, в то время как STA COM использует систему обмена сообщениями для сериализации запросов потоков. Управляемый мир является свободным потоком, и использование однопотокового подразделения COM требует, чтобы все неуправляемые потоки по существу совместно использовали один поток для взаимодействия. Это приводит к значительному повышению производительности, и этого следует избегать, когда это возможно. Если вы не можете перенести COM-объект Apartment в управляемый мир, используйте <@%AspCompat = "true" %> для страниц, которые их используют. Более подробное описание STA COM см. в библиотека MSDN.

Пакетная компиляция

Перед развертыванием большой страницы в Интернете всегда выполняйте пакетную компиляцию. Это можно инициировать, выполнив один запрос к странице для каждого каталога и дождавшись, пока ЦП снова не будет простаивать. Это предотвращает увязку веб-сервера с компиляциями, а также пытается обслуживать страницы.

Удаление ненужных http-модулей

В зависимости от используемых функций удалите из конвейера неиспользуемые или ненужные модули HTTP. Освобождение добавленной памяти и потраченных впустую циклов может обеспечить небольшой импульс скорости.

Избегайте функции autoeventwireup

Вместо того чтобы полагаться на autoeventwireup, переопределите события из страницы. Например, вместо написания метода Page_Load() попробуйте перегрузить метод public void OnLoad(). Это позволяет во время выполнения выполнять createDelegate() для каждой страницы.

Кодирование с помощью ASCII, если не требуется UTF

По умолчанию ASP.NET настраивается для кодирования запросов и ответов в формате UTF-8. Если приложение нуждается в ASCII, устраните накладные расходы на UTF, что может привести к возврату нескольких циклов. Обратите внимание, что это можно сделать только для каждого приложения.

Использование процедуры оптимальной проверки подлинности

Существует несколько различных способов проверки подлинности пользователя, и некоторые из них дороже, чем другие (в порядке увеличения стоимости: None, Windows, Forms, Passport). Убедитесь, что вы используете самый дешевый, который лучше всего соответствует вашим потребностям.

Советы по переносу и разработке в Visual Basic

С Microsoft Visual Basic 6 до Microsoft®® Visual Basic®® 7 многое изменилось, и карта производительности изменилась вместе с ней. Из-за дополнительных функциональных возможностей и ограничений безопасности среды CLR некоторые функции просто не могут выполняться так же быстро, как в Visual Basic 6. На самом деле, есть несколько областей, в которых Visual Basic 7 получает прерывная версия своего предшественника. К счастью, есть две хорошие новости:

  • Большинство наихудших замедлений происходит во время однократных функций, таких как загрузка элемента управления в первый раз. Стоимость есть, но вы платите ее только один раз.
  • Существует множество областей, в которых Visual Basic 7 работает быстрее, и эти области, как правило, лежат в функциях, повторяющихся во время выполнения. Это означает, что выгода со временем растет, и в некоторых случаях перевешивает одноразовые затраты.

Большинство проблем с производительностью возникают в тех областях, где время выполнения не поддерживает функцию Visual Basic 6, и ее необходимо добавить, чтобы сохранить эту функцию в Visual Basic 7. Работа вне среды выполнения выполняется медленнее, что делает использование некоторых функций гораздо дороже. Яркая сторона заключается в том, что вы можете избежать этих проблем с небольшими усилиями. Существует две main области, требующие работы по оптимизации производительности, и несколько простых настроек, которые можно выполнить здесь и там. Вместе они помогают обойти проблемы с производительностью и воспользоваться преимуществами функций, которые гораздо быстрее работают в Visual Basic 7.

Обработка ошибок

Первая проблема — обработка ошибок. Это значительно изменилось в Visual Basic 7, и есть проблемы с производительностью, связанные с изменением. По сути, логика, необходимая для реализации OnErrorGoto и Resume , является чрезвычайно дорогостоящей. Я предлагаю взглянуть на код и выделить все области, в которых используется объект Err или любой механизм обработки ошибок. Теперь взгляните на каждый из этих экземпляров и посмотрите, можно ли переписать их для использования try/catch. Многие разработчики считают, что в большинстве случаев они могут легко выполнить преобразование, чтобы попробовать и поймать , и они должны увидеть хорошее повышение производительности в своей программе. Эмпирическое правило: "Если вы можете легко увидеть перевод, сделайте это".

Ниже приведен пример простой программы Visual Basic, которая использует On Error Goto по сравнению с версией try/catch .

Sub SubWithError()
On Error Goto SWETrap
  Dim x As Integer
  Dim y As Integer
  x = x / y
SWETrap:  Exit Sub
  End Sub
 
Sub SubWithErrorResumeLabel()
  On Error Goto SWERLTrap
  Dim x As Integer
  Dim y As Integer
  x = x / y 
SWERLTrap:
  Resume SWERLExit
  End Sub
SWERLExit:
  Exit Sub
Sub SubWithError()
  Dim x As Integer
  Dim y As Integer
  Try    x = x / y  Catch    Return  End Try
  End Sub
 
Sub SubWithErrorResumeLabel()
  Dim x As Integer
  Dim y As Integer
  Try
    x = x / y
  Catch
  Goto SWERLExit
  End Try
 
SWERLExit:
  Return
  End Sub

Увеличение скорости заметно. SubWithError() принимает 244 миллисекунд с помощью OnErrorGoto и только 169 миллисекунд с помощью try/catch. Вторая функция принимает 179 миллисекунд по сравнению с 164 миллисекундами для оптимизированной версии.

Использование ранней привязки

Вторая проблема касается объектов и преобразования типов. Visual Basic 6 выполняет много работы по поддержке приведения объектов, и многие программисты даже не знают об этом. В Visual Basic 7 это область, из которой можно сжать большую производительность. При компиляции используйте раннюю привязку. Это указывает компилятору, что необходимо вставить приведение типов , только если явно указано. Это имеет два основных эффекта:

  • Странные ошибки легче отслеживать.
  • Ненужные приведения исключаются, что приводит к значительному повышению производительности.

Если вы используете объект, как если бы он был другого типа, Visual Basic будет принуждать объект за вас, если вы не укажете его. Это удобно, так как программисту приходится беспокоиться о меньшем коде. Недостаток заключается в том, что эти приведения могут делать неожиданные вещи, и программист не имеет никакого контроля над ними.

Существуют случаи, когда требуется использовать позднее связывание, но в большинстве случаев, если вы не уверены, вы можете обойтись с ранней привязкой. Для программистов Visual Basic 6 это может быть немного неудобно на первый взгляд, так как вам приходится беспокоиться о типах больше, чем в прошлом. Это должно быть легко для новых программистов, и люди, знакомые с Visual Basic 6, быстро подберут его.

Включение строгих и явных параметров

С помощью Option Strict on вы защищаете себя от непреднамеренной поздней привязки и обеспечиваете более высокий уровень дисциплины программирования. Список ограничений, присутствующих в Option Strict, см. в библиотека MSDN. Предостережение заключается в том, что все сужающие приведения типов должны быть явно указаны. Однако это само по себе может выявить другие разделы кода, которые выполняют больше работы, чем вы думали ранее, и это может помочь вам устранить некоторые ошибки в процессе.

Option Explicit является менее строгим, чем Option Strict, но по-прежнему заставляет программистов предоставлять дополнительные сведения в своем коде. В частности, необходимо объявить переменную перед ее использованием. При этом вывод типа перемещается из времени выполнения во время компиляции. Это устраняет проверка приводит к повышению производительности.

Рекомендуется начать с явного параметра, а затем включить параметр Option Strict. Это защитит вас от большого количества ошибок компилятора и позволит постепенно приступить к работе в более строгой среде. При использовании обоих этих параметров вы обеспечиваете максимальную производительность приложения.

Использование сравнения двоичных файлов для текста

При сравнении текста используйте двоичное сравнение вместо сравнения текста. Во время выполнения издержки гораздо меньше для двоичного файла.

Сведите к минимуму использование Format()

По возможности используйте toString() вместо format(). В большинстве случаев он предоставляет вам необходимые функции с гораздо меньшими затратами.

Использование Charw

Используйте charw вместо char. Среда CLR использует Юникод для внутренних целей, и char необходимо преобразовать во время выполнения, если он используется. Это может привести к значительной потере производительности, и указание, что символы являются полным словом long (использование charw) исключает это преобразование.

Оптимизация назначений

Используйте exp += val вместо exp = exp + val. Так как exp может быть произвольно сложным, это может привести к большому объему ненужных работ. Это заставляет JIT оценивать обе копии exp, и во многих случаях это не требуется. Первая инструкция может быть оптимизирована гораздо лучше, чем вторая, так как JIT может избежать двойной оценки exp .

Избегайте ненужного косвенного обращения

При использовании byRef вместо фактического объекта передаются указатели. Много раз это имеет смысл (например, функции с побочным эффектом), но они не всегда нужны. Передача указателей приводит к большему косвенному обращению, что происходит медленнее, чем доступ к значению, которое находится в стеке. Если вам не нужно проходить через кучу, лучше избегать этого.

Размещение сцеплений в одном выражении

Если у вас есть несколько сцеплений в нескольких строках, попробуйте прикрепить их все к одному выражению. Компилятор может оптимизировать, изменив строку на месте, обеспечивая скорость и увеличение памяти. Если операторы разделены на несколько строк, компилятор Visual Basic не создаст MSIL, чтобы разрешить объединение на месте. См. пример StringBuilder, рассмотренный ранее.

Включить операторы return

Visual Basic позволяет функции возвращать значение без использования оператора return . Хотя Visual Basic 7 поддерживает это, явное использование возвращаемого значения позволяет JIT-файлу выполнять несколько больше оптимизаций. Без оператора return каждой функции предоставляется несколько локальных переменных в стеке для прозрачной поддержки возвращаемых значений без ключевое слово. Сохранение этих действий усложняет оптимизацию JIT и может повлиять на производительность кода. Просмотрите функции и при необходимости вставьте возвращаемый результат . Он вообще не изменяет семантику кода и помогает ускорить работу приложения.

Советы по переносу и разработке в управляемом C++

Корпорация Майкрософт ориентирована на управляемый C++ (MC++) на определенный набор разработчиков. MC++ не является лучшим инструментом для каждого задания. Прочитав этот документ, вы можете решить, что C++ не является лучшим инструментом и что затраты на компромисс не оправдывающие преимуществ. Если вы не уверены в MC++, есть много хороших ресурсов , которые помогут вам принять решение Этот раздел предназначен для разработчиков, которые уже решили, что они хотят каким-то образом использовать MC++ и хотят знать об аспектах производительности.

Для разработчиков C++ работа с управляемым C++ требует принятия нескольких решений. Вы переносите старый код? Если да, вы хотите переместить все это в управляемое пространство или планируете реализовать оболочку? Я собираюсь сосредоточиться на варианте "порт-все" или иметь дело с написанием MC++ с нуля для целей этого обсуждения, так как это сценарии, в которых программист заметит разницу в производительности.

Преимущества управляемого мира

Самой мощной функцией управляемого C++ является возможность смешивания и сопоставления управляемого и неуправляемого кода на уровне выражений. Ни один другой язык не позволяет сделать это, и есть некоторые мощные преимущества, которые вы можете получить от него при правильном использовании. Я рассмотрим некоторые примеры этого позже.

Управляемый мир также дает вам огромный дизайн выигрывает, в том, что многие распространенные проблемы заботятся за вас. Управление памятью, планирование потоков и приведение типов можно при желании оставить во время выполнения, что позволяет сосредоточить энергию на части программы, которым она нужна. С помощью MC++ вы можете выбрать именно тот объем управления, который вы хотите сохранить.

Программисты MC++ обладают роскошью возможности использовать серверную часть Microsoft Visual C7® (VC7) при компиляции в IL, а затем использовать JIT поверх этого. Программисты, которые привыкли работать с компилятором Microsoft C++, привыкли к молниеносным действиям. JIT был разработан с разными целями и имеет разные сильные и слабые стороны. Компилятор VC7, не связанный с временными ограничениями JIT- кода, может выполнять определенные оптимизации, которые не может использовать JIT, такие как анализ всей программы, более агрессивное встраивание и инрегистрация. Существуют также некоторые оптимизации, которые можно выполнять только в типобезопасных средах, оставляя больше места для скорости, чем позволяет C++.

Из-за разных приоритетов в JIT некоторые операции выполняются быстрее, чем раньше, а другие — медленнее. Есть компромиссы, которые вы делаете для безопасности и языковой гибкости, и некоторые из них не дешево. К счастью, есть вещи, которые программист может сделать, чтобы свести к минимуму затраты.

Перенос: весь код C++ может компилироваться в MSIL

Прежде чем продолжить, важно отметить, что вы можете скомпилировать любой код C++ в MSIL. Все будет работать, но нет никакой гарантии типа-безопасности, и вы платите маршалинга штраф, если вы делаете много взаимодействия. Почему полезно выполнять компиляцию в MSIL, если вы не получаете никаких преимуществ? В ситуациях, когда вы переносите большую базу кода, это позволяет постепенно переносить код по частям. Вы можете тратить время на перенос большего объема кода, а не создавать специальные оболочки для склеивания перенесенного и еще не перенесенного кода, если вы используете MC++, и это может привести к большой победе. Это делает перенос приложений очень чистым процессом. Чтобы узнать больше о компиляции C++ в MSIL, ознакомьтесь с параметром компилятора /clr.

Однако простое компиляция кода C++ в MSIL не обеспечивает безопасность и гибкость управляемого мира. Вам нужно писать в MC++, а в версии 1 это означает отказ от некоторых функций. Приведенный ниже список не поддерживается в текущей версии среды CLR, но может быть в будущем. Корпорация Майкрософт сначала выбрала поддержку наиболее распространенных функций, а для отправки пришлось сократить некоторые другие функции. Нет ничего, что препятствует их добавлению позже, но в то же время вам придется обойтись без них:

  • Множественное наследование
  • Шаблоны
  • Детерминированное завершение

Вы всегда можете взаимодействовать с небезопасным кодом, если вам нужны эти функции, но вы будете платить за производительность за маршалинг данных туда и обратно. И помните, что эти функции можно использовать только в неуправляемом коде. Управляемое пространство не знает об их существовании. Если вы решили перенести код, подумайте о том, насколько вы полагаетесь на эти функции в своем проекте. В некоторых случаях редизайн слишком дорогой, и вы захотите придерживаться неуправляемого кода. Это первое решение, который вы должны принять, прежде чем начать взлом.

Преимущества MC++ по сравнению с C# или Visual Basic

При использовании неуправляемого фона MC++ сохраняет большую часть возможностей для обработки небезопасного кода. Возможность MC++ плавно сочетать управляемый и неуправляемый код предоставляет разработчику большую мощность, и вы можете выбрать, где градиент будет находиться при написании кода. С одной стороны, вы можете написать все на прямом, неисправном языке C++ и просто скомпилировать с помощью /clr. С другой стороны, вы можете написать все в виде управляемых объектов и справиться с языковыми ограничениями и проблемами производительности, упомянутыми выше.

Но реальная сила MC++ приходит, когда вы выбираете где-то между ними. MC++ позволяет настраивать некоторые показатели производительности, присущие управляемому коду, предоставляя точный контроль над тем, когда следует использовать небезопасные функции. В C# есть некоторые из этих функций в небезопасных ключевое слово, но она не является неотъемлемой частью языка и гораздо менее полезна, чем MC++. Давайте рассмотрим несколько примеров, демонстрирующих более детальную степень детализации, доступную в MC++, и мы поговорим о ситуациях, когда она пригодится.

Универсальные указатели byref

В C# можно получить адрес только некоторых членов класса, передав его в параметр ref . В MC++ указатель byref является конструкцией первого класса. Вы можете взять адрес элемента в середине массива и вернуть его из функции:

Byte* AddrInArray( Byte b[] ) {
   return &b[5];
}

Мы используем эту функцию для возврата указателя на символы в System.String с помощью нашей вспомогательной процедуры и даже можем циклически перебирать массивы, используя следующие указатели:

System::Char* PtrToStringChars(System::String*);   
for( Char*pC = PtrToStringChars(S"boo");
  pC != NULL;
  pC++ )
{
      ... *pC ...
}

Вы также можете выполнить обход связанного списка с внедрением в MC++, взяв адрес поля "next" (что нельзя сделать в C#):

Node **w = &Head;
while(true) {
  if( *w == 0 || val < (*w)->val ) {
    Node *t = new Node(val,*w);
    *w = t;
    break;
  }
  w = &(*w)->next;
}

В C# вы не можете указать на "Head" или взять адрес поля "next", поэтому у вас есть особый случай, в который вы вставляете в первом месте, или если "Head" имеет значение NULL. Кроме того, необходимо постоянно искать один узел в коде. Сравните это с тем, что будет создавать хороший C#:

if( Head==null || val < Head.val ) {
  Node t = new Node(val,Head);
  Head = t;
}else{
  // we know at least one node exists,
  // so we can look 1 node ahead
  Node w=Head;
while(true) {
  if( w.next == null || val < w.next.val ){
    Node t = new Node(val,w.next.next);
    w.next = t;
    break;
  }
  w = w.next;
  }
}         

Доступ пользователей к упакованным типам

Проблема с производительностью, распространенная в языках OO, — это время, затраченное на настройку и распаковку значений. MC++ обеспечивает гораздо больший контроль над этим поведением, поэтому вам не придется динамически (или статически) распаковывать для доступа к значениям. Это еще одно улучшение производительности. Просто поместите __box ключевое слово перед любым типом, чтобы представить его упаковав форму:

__value struct V {
  int i;
};
int main() {
  V v = {10};
  __box V *pbV = __box(v);
  pbV->i += 10;           // update without casting
}

В C# необходимо распаковать в "v", а затем обновить значение и снова включить в объект Object:

struct B { public int i; }
static void Main() {
  B b = new B();
  b.i = 5;
  object o = b;         // implicit box
  B b2 = (B)o;            // explicit unbox
  b2.i++;               // update
  o = b2;               // implicit re-box
}

Коллекции STL и управляемые коллекции — версия 1

Плохая новость: В C++ использование коллекций STL часто было так же быстро, как написание этой функции вручную. Платформы CLR работают очень быстро, но они страдают от проблем с боксом и распаковки: все является объектом, и без поддержки шаблонов или универсальных шаблонов все действия должны проверяться во время выполнения.

Хорошая новость: в долгосрочной перспективе вы можете поспорить, что эта проблема уйдет, так как универсальные шаблоны добавляются к времени выполнения. Код, который вы развертываете сегодня, будет испытывать повышение скорости без каких-либо изменений. В краткосрочной перспективе можно использовать статическое приведение, чтобы предотвратить проверка, но это уже небезопасно. Рекомендуется использовать этот метод в жестком коде, где производительность крайне важна, и вы определили две или три горячие точки.

Использование управляемых объектов Stack

В C++ вы указываете, что объект должен управляться стеком или кучей. Вы по-прежнему можете сделать это в MC++, но есть ограничения, о которых вы должны знать. Среда CLR использует ValueTypes для всех объектов, управляемых стеком, и существуют ограничения на возможности ValueTypes (например, без наследования). Дополнительные сведения см. в библиотека MSDN.

Угловой вариант: остерегайтесь непрямых вызовов в управляемом коде — версия 1

Во время выполнения версии 1 все непрямые вызовы функций выполняются в собственном коде и поэтому требуют перехода в неуправляемое пространство. Любой непрямой вызов функции может выполняться только из собственного режима, что означает, что все косвенные вызовы из управляемого кода нуждаются в переходе от управляемого к неуправляемому. Это серьезная проблема, когда таблица возвращает управляемую функцию, так как затем необходимо выполнить второй переход для выполнения функции. По сравнению со стоимостью выполнения одной инструкции call , стоимость в пятьдесят-сто раз медленнее, чем в C++!

К счастью, при вызове метода, который находится в классе сборки мусора, оптимизация устраняет это. Однако в конкретном случае обычного файла C++, скомпилированного с помощью /clr, возврат метода будет считаться управляемым. Так как эта функция не может быть удалена путем оптимизации, вы столкнулись с полной стоимостью двойного перехода. Ниже приведен пример такого случая.

//////////////////////// a.h:    //////////////////////////
class X {
public:
   void mf1();
   void mf2();
};

typedef void (X::*pMFunc_t)();


////////////// a.cpp: compiled with /clr  /////////////////
#include "a.h"

int main(){
   pMFunc_t pmf1 = &X::mf1;
   pMFunc_t pmf2 = &X::mf2;

   X *pX = new X();
   (pX->*pmf1)();
   (pX->*pmf2)();

   return 0;
}


////////////// b.cpp: compiled without /clr /////////////////
#include "a.h"

void X::mf1(){}


////////////// c.cpp: compiled with /clr ////////////////////
#include "a.h"
void X::mf2(){}

Этого можно избежать несколькими способами.

  • Преобразование класса в управляемый класс ("__gc")
  • Удалите косвенный вызов, если это возможно
  • Оставьте класс скомпилированным как неуправляемый код (например, не используйте /clr).

Минимизация попаданий в производительность — версия 1

Существует несколько операций или функций, которые просто дороже в MC++ в JIT версии 1. Я перечислим их и объясню, а затем поговорим о том, что вы можете с ними сделать.

  • Абстракции. Это область, в которой медленный серверный компилятор C++ выигрывает в значительной степени по сравнению с JIT-интерфейсом. Если поместить int в класс в целях абстракции и получить к нему доступ исключительно как int, компилятор C++ может практически ничего не сделать. Вы можете добавить в оболочку множество уровней абстракции, не увеличивая затраты. JIT не может занять время, необходимое для устранения этих затрат, что делает глубокие абстракции более дорогими в MC++.
  • Плавающая запятая. JIT-файл версии 1 в настоящее время не выполняет все оптимизации, связанные с FP, как серверная часть VC++, что делает операции с плавающей запятой более дорогостоящими.
  • Многомерные массивы— JIT-код лучше подходит для обработки массивов с массивами, чем многомерные массивы, поэтому вместо них используйте массивы с массивами.
  • 64-разрядная арифметика — в будущих версиях в JIT-файл будут добавлены 64-разрядные оптимизации.

Что вы можете сделать

На каждом этапе разработки можно выполнить несколько действий. В MC++ этап проектирования, возможно, является наиболее важной областью, так как он определяет, сколько работы вы в конечном итоге выполняете и сколько производительности вы получите взамен. При написании или переносе приложения необходимо учитывать следующее:

  • Определите области, в которых используется несколько наследование, шаблоны или детерминированное завершение. Вам придется избавиться от них или оставить эту часть кода в неуправляемом пространстве. Подумайте о стоимости перепроектирования и определите области, которые можно перенести.
  • Поиск горячих точек производительности, таких как глубокие абстракции или вызовы виртуальных функций в управляемом пространстве. Для этого также потребуется проектное решение.
  • Найдите объекты, которые были указаны как управляемые стеком. Убедитесь, что их можно преобразовать в ValueTypes. Пометьте остальные объекты для преобразования в объекты, управляемые кучей.

На этапе программирования следует знать о более дорогостоящих операциях и о вариантах, с которыми вы работаете. Одна из самых приятных вещей в MC++ заключается в том, что вы заранее узнаете обо всех проблемах с производительностью, прежде чем приступать к написанию кода. Это полезно для анализа работы в дальнейшем. Однако при коде и отладке все еще есть некоторые настройки, которые можно выполнить.

Определите, в каких областях интенсивно используются арифметические, многомерные массивы или функции библиотеки с плавающей запятой. Какие из этих областей критически важны для производительности? Используйте профилировщики, чтобы выбрать фрагменты, в которых накладные расходы стоят больше всего, и выбрать оптимальный вариант:

  • Весь фрагмент хранится в неуправляемом пространстве.
  • Используйте статические приведения для доступа к библиотеке.
  • Попробуйте настроить поведение бокса и распаковки (описано далее).
  • Кодируйте собственную структуру.

Наконец, поработайте над тем, чтобы свести к минимуму количество переходов. Если у вас есть неуправляемый код или вызов взаимодействия, который находится в цикле, сделайте весь цикл неуправляемым. Таким образом, вы будете платить за переход только дважды, а не за каждую итерацию цикла.

Дополнительные ресурсы

Ниже приведены следующие разделы, посвященные производительности в платформа .NET Framework.

Ознакомьтесь с будущими статьями, которые в настоящее время находятся в разработке, включая обзор принципов проектирования, архитектуры и программирования, пошаговое руководство по средствам анализа производительности в управляемом мире и сравнение производительности .NET с другими корпоративными приложениями, доступными сегодня.

Приложение. Стоимость виртуальных вызовов и выделений

Тип вызова # Вызовов/с
Не виртуальный вызов ValueType 809971805.600
Не виртуальный вызов класса 268478412.546
Виртуальный вызов класса 109117738.369
Вызов ValueType Virtual (Obj Method) 3004286.205
Вызов ValueType Virtual (переопределенный метод Obj) 2917140.844
Тип загрузки по новому (нестатическое) 1434.720
Тип загрузки по новому (виртуальные методы) 1369.863

Примечание. Тестовый компьютер — это piII 733 МГц под управлением Windows 2000 Professional с пакетом обновления 2 (SP2).

На этой диаграмме сравниваются затраты, связанные с различными типами вызовов методов, а также затраты на создание экземпляра типа, содержащего виртуальные методы. Чем больше число, тем больше вызовов и экземпляров в секунду. Хотя эти цифры, безусловно, зависят от разных компьютеров и конфигураций, относительная стоимость выполнения одного вызова по другому остается значительной.

  • Не виртуальный вызов ValueType. Этот тест вызывает пустой не виртуальный метод, содержащийся в ValueType.
  • Не виртуальный вызов класса. Этот тест вызывает пустой не виртуальный метод, содержащийся в классе.
  • Виртуальный вызов класса. Этот тест вызывает пустой виртуальный метод, содержащийся в классе.
  • Вызов ValueType Virtual (Obj Method). Этот тест вызывает ToString() (виртуальный метод) для ValueType, который использует метод объекта по умолчанию.
  • Вызов ValueType Virtual (Overridden Obj Method). Этот тест вызывает ToString() (виртуальный метод) для ValueType, переопределяющего значение по умолчанию.
  • Load Type by Newing (Static) (Load Type by Newing (Static) (Тип загрузки по новой версии (static) — этот тест выделяет место для класса только со статическими методами.
  • Тип загрузки по newing (виртуальные методы). Этот тест выделяет место для класса с виртуальными методами.

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

  • JIT не может использовать встроенные виртуальные методы, поэтому вы потеряете потенциальную оптимизацию, если избавитесь от не виртуальных методов.
  • Выделение пространства для объекта с виртуальными методами выполняется немного медленнее, чем выделение для объекта без них, так как необходимо выполнить дополнительную работу по поиску места для виртуальных таблиц.

Обратите внимание, что вызов не виртуального метода в ValueType выполняется более чем в три раза быстрее, чем в классе, но как только вы рассматриваете его как класс , вы потеряете. Это характерно для ValueTypes: рассматривайте их как структуры, и они быстро освещаются. Относиться к ним как к классам, и они мучительно медленные. ToString() — это виртуальный метод, поэтому перед вызовом структуры необходимо преобразовать в объект в куче. Вместо того, чтобы быть в два раза медленнее, вызов виртуального метода для ValueType теперь в восемнадцать раз медленнее! Мораль истории? Не рассматривайте ValueType как классы.

Если у вас есть вопросы или комментарии по поводу этой статьи, обратитесь к Клаудио Колдато, руководитель программы по вопросам платформа .NET Framework проблем с производительностью.