Профилирование производительности приложения без установки Visual Studio

Думаю, многие программисты пользовались средствами анализа производительности, удобно интегрированных прямо в Visual Studio. Процесс настолько интуитивно понятен из графического интерфейса, что совсем не приходится задумываться, что в действительности, за сбор статистики работы приложения отвечает консольное приложение VsPerfCmd. Задумываться о том, что «под капотом» может понадобиться, если, к примеру, нужно собрать данные на компьютере без установленной Visual Studio. Сразу в голову приходит аналогия с удаленной отладкой, но в данном случае всё даже проще — специальный сервис просто собирает статистику в файл результатов, который потом можно открыть Visual Studio или добавить к существующим отчетам анализа производительности. Единственное требование — к исследуемым dll и exe файлам должны быть скопированы pdb-файлы, в противном случае проанализировать узкие места не удастся. Итак, первым делом, нужно установить сервис сбора статистики, инсталлятор находится в %VSInstallDir% Team ToolsPerformance ToolsSetups. Для VS 2012 — «C:Program Files (x86)Microsoft Visual Studio 11.0Team ToolsPerformance ToolsSetupsvs_profiler_x64_enu.exe»

Далее — запускаем сервис с нужным типом анализа и параметрами анализируемого приложения(путь и параметры командной строки). Ниже приведен скрипт на Powershell, который позволяет автоматизировать процесс присоединения к процессу и остановки сбора статистики при выходе из приложения. При подобной автоматизации процесс сбора и анализа производительности на удаленной машине не отличается от аналогичного процесса работы прямо из Visual Studio.

$tool = "C:Program Files (x86)Microsoft Visual Studio 11.0Team ToolsPerformance ToolsVSPerfCmd.exe"
$toolProcess = Start-Process $tool -ArgumentList "/Start:Sample /Output:c:perfstats2.vsp /Launch:""$analyzedProgramPath"" /Args:args "
$processName = [io.path]::GetFileNameWithoutExtension($analyzedProgramPath)
for ($i=0; $i -lt 1000; $i = $i+1) {
    $p = get-process ProcessName -ErrorAction SilentlyContinue<
    if (!$p) { break; }

    sleep -s 1 } Write-Host "Stopping collect..." $tool /Shutdown /Analyze:Procid

 

Если нужно подключиться к ранее запущенному процессу, например, к сервису можно пользоваться опций Attach.

 

Как посчитать число строк кода с помощью PowerShell

Узнать число строк кода С# (или LOC — lines of code ) в проекте на C# можно узнать одной строчкой PowerShell:

(dir -Include *.cs -Recurse -Exclude *designer.cs, AssemblyInfo.cs | select-string "^(s*)$" -notMatch).Count

Данная команда проходит по всем .cs файлам в текущем каталоге и его подкаталогах (опция —Recurse) исключая файлы AssemblyInfo.cs и файлы дизайнеров форм. select-string «^(s*)$» -notMatch исключает из подсчета пустые строки

Как избежать повторной подписки на события в C#

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

public class NonDuplicateEventArgs : EventArgs
    {
      public NonDuplicateEventArgs(string message)
      {
        Message = message;
      }

      public string Message { get; set; }
    }

    protected event EventHandler<nonDuplicateEventArgs> _nonDuplicateEvent;

    /// <summary>
    /// Событие с проверкой на добавление обработчиков-дубликатов путем удаления и добавления обработчика заново
    /// </summary>
    public event EventHandler<nonDuplicateEventArgs> NonDuplicateEvent
    {
      add
      {
        // пытаемся удалить обработчик, затем добавляем снова
        _nonDuplicateEvent -= value;
        _nonDuplicateEvent += value;
      }
      remove { _nonDuplicateEvent -= value; }
    }

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

using System.Linq;

 private EventHandler<nonDuplicateEventArgs> _nonDuplicateEventInvocList;

    /// <summary>
    /// Событие с проверкой  на добавление  обработчиков-дубликатов через GetInvocationList
    /// </summary>
    public event EventHandler<nonDuplicateEventArgs> NonDuplicateEventInvocList
    {
      add
      {
        if (_nonDuplicateEventInvocList == null ||
            !_nonDuplicateEventInvocList.GetInvocationList().Contains(value))
        {
          _nonDuplicateEventInvocList += value;
        }
      }
      remove { _nonDuplicateEventInvocList -= value; }
    }

Для того, чтобы решить, что же выбрать в своем приложении, я сделал небольшой тест. Как и ожидалось, метод с GetInvocationList для проверки одного и того же обработчика работает существенно медленнее, чем удаление и добавление — более чем в 5 раз медленнее (11.7 секунд против 2.2)

Action measureTime = (action, actionCaption) =>
{
var time = DateTime.Now;
action();
TimeSpan span = DateTime.Now — time;
Console.WriteLine(«{0} time: {1:0.000}», actionCaption,span.TotalSeconds);
};

int testCount = 10000000;

//замеряем скорость добавления одного и того же подписчика на событие
SmartEvents smartEvents = new SmartEvents();
measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEvent += OnNonDuplicateEvent; } }, "NonDuplicate"); measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEventInvocList += OnNonDuplicateEvent; } }, "NonDuplicateEvent with InvocationList"); [/sourcecode]

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

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

var methods = new EventHandler[]
{
OnNonDuplicateEvent, OnNonDuplicateEvent2,
OnNonDuplicateEvent3, OnNonDuplicateEvent4,
OnNonDuplicateEvent5, OnNonDuplicateEvent6
};
testCount /= methods.Length;//чтобы долго не ждать

Random r = new Random(0);

var list = Enumerable.Range(0, testCount).Select(z => methods[r.Next(methods.Length)]).ToArray();

measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEvent += list[i]; } }, "NonDuplicate for" + methods.Length + "subscribers"); measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEventInvocList += list[i]; } }, "NonDuplicateEvent with InvocationList" + methods.Length + "subscribers"); } [/sourcecode] Полный код решения с тестами можно скачать по ссылке.

Факты о John Skeet

По мотивам топика на stackoverlow подборка фактов о разработчике и авторе книги C# in Depth Джона Скита в стиле фактов о Чаке Норрисе .

  • Обедающие философы ждут, пока Джон Скит ест.
  • Анонимные методы и типы называются Jon Skeet.
  • Код Джона Скита не следует соглашениям о правилах кодирования. Джон Скит — сам конвенция кода.
  • Джон Скит однажды ответил на вопрос за 42 секунды до того, как он был задан.
  • Когда Джон Скит ссылается на null, null содрогается от страха
  • Дональд Кнут носит футболку «Джон Скит — мой братан»
  • Джон Скит решает задачу о коммивояжере за O(1)
  • Джон Скит единственный человек в Top 100 пользователей StackOverflow. Остальные — боты, которых он запрограммировал в промежутках между ответами на вопросы.
  • Джон скит полностью кодировал свой последний проект в Microsot Paint,
  • Когда код Джон Скита не компилируется из-за ошибки, компилятор приносит свои извинения
  • Джон Скит не использует системы контроля версий. Его код не нуждается в ревизиях.
  • Если спросить у Google «гуру программирования» Google предложит: «Возможно вы имели ввиду Jon Skeet?«
  • Может ли Джон Скит задать вопрос на который он сам не сможет ответить? Да. И он сможет на него ответить.
  • У StackOverflow есть исключение типа JonSkeetAskedAQuestionException и оно никогда не порождается.
  • Когда Джон Скит бросает исключение, ничто не может его перехватить.
  • Джон Скит может сделать так, чтобы код на Perl выглядел бы так же как и на Java
  • Джон Скит не нуждается в отладчике — он просто пристально смотрит на код, пока баг сам себя не раскроет.
  • На клавиатуре Джона Скита нет кнопки F1. Компьютер сам просит у него помощи.
  • Джон Скит использует Visual Studio чтобы прожигать DVD-диски.
  • Мозг Джона Скита думает в двоичном коде.
  • Однажды Джон Скит пошел в библиотеку. Теперь эта библиотека динамически скомпонована.

Under the Hood of .NET Memory Management

Компания Redgate представила первую часть книги, посвященной проблемам менеджемента памяти. Первая часть книги «Under the Hood of .NET Memory Management» доступна для скачивания.

Автообновление приложений .NET

В этой статье рассматривается два наиболее простых способа реализации автообновления приложения .Net. Первый способ — с помощью стандартной технологии Microsoft — Click Once, второй — с помощью опенсорного компонента NET Application Updater Component.

Обновление с помощью ClickOnce

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

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

Обновление с помощью NET Application Updater Component

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

1. Создаем новый проект Windows Forms, например с именем MySimpleSample

2. Добавляем проекты AppStart и AppUpdater. Добавляем Reference на на проект AppUpdater. В AppUpdater есть ссылка на класс AppStartConfig, лежащий в проекте AppStart, поэтому проекты AppStart и AppUpdater лучше положить на одном уровне файловой структуры.

3. Добавляем на форму компонент AppUpdater ( Со окна Toolbox-AppUpdater Components). Тут были некоторые проблемы, скорее всего это связано с обновлением проекта до Visual Studio 2010 . UpdateLog хочет писать в файл AppUpdate.log, но текущим каталогом, возвращаемым функцией GetLogFilePath является путь к Visual Studio, куда, естественно запись запрещена. Можно поправить выбор каталога на что-нибудь такое :

DirectoryInfo DI = new DirectoryInfo(Assembly.GetCallingAssembly().Location);

В классе AppDownloader нас будет ожидать еще один сюрприз — захардкоженное имя конфига «AppStart.config». Явная недоделка — исправляем хотя бы на константу для централизации.

Читать далее Автообновление приложений .NET

Когда использовать Parallel.ForEach, а когда PLINQ

Мой перевод статьи «When Should I Use Parallel.ForEach? When Should I Use PLINQ?», опубликованный ранее на Хабрахабре.

Введение

Обычно, при оптимизации программы для многоядерных компьютеров первым шагом является выяснение возможности разделения алгоритма на части, выполняющиеся параллельно. Если для решения задачи необходимо параллельно обрабатывать отдельные элементы из большого набора данных, то первыми кандидатами станут новые возможности параллельности в .NET Framework 4: Parallel.ForEach и Parallel LINQ (PLINQ)

Parallel.ForEach

Класс Parallel содержит метод ForEach, представляющий собой многопоточную версию обычного цикла foreach в C#. Подобно обычному foreach, Parallel.ForEach выполняет итерации над перечислимыми данными (enumerable), но с использованием множества потоков. Одна из более часто используемых перегрузок Parallel.ForEach выглядит следующим образом:

public static ParallelLoopResult ForEach<tsource>(
			 IEnumerable</tsource><tsource> source,
			 Action</tsource><tsource> body)

Ienumerable указывает на последовательность, по которой нужно выполнить итерации, а Action body задает делегат, вызываемый для каждого элемента. Полный список перегрузок Parallel.ForEach можно найти здесь.

PLINQ

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

В статье выделяются различия между этими двумя подходами к параллельности. Разбираются сценарии использования, в которых лучше всего использовать Parallel.ForEach вместо PLINQ и наоборот.

Выполнение независимых операций

Читать далее Когда использовать Parallel.ForEach, а когда PLINQ

Восстановление счетчиков производительности Windows 7

По каким-то магическим причинам некоторые части механизма счетчиков производительности могут быть нарушены. Проявлением этого дефекта будет сообщение «Cannot load Counter Name data because an invalid index » was read from the registry.» при попытке чтения счетчиков производительности средствами .NET Framework. Если на форме находится компонент, то исключение сгенерируется прямо в функции InitializeComponent после вызова PostInit на компоненте отображения счетчиков производительности, что можно считать откровенной недоработкой, так как в этом случае скорее нужно было бы просто отображать текст с ошибкой.

Для того, чтобы починить счетчики производительности мне хватило выполнения команды:

 lodctr /R

Если это не помогает, то нужно обновить счетчики производительности .NET, для этого вызвать команду (Путь указан для 64-битной версии .NET 4, придется указать путь, актуальный в конкретной операционной системе.

 lodctr " c:WindowsMicrosoft.NETFramework64v4.0.30319corperfmonsymbols.ini"

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

Cсылки по теме:

Ошибка "Access to modified closure"

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

 IEnumerable<char> query = "Not what you might expect";

 foreach (char vowel in "aeiou")
    query = query.Where(c => c != vowel);

    Console.WriteLine("Error:");
    foreach (char c in query)
    Console.Write(c); // Not what yo might expect

Казалось бы, все хорошо (если, конечно, не установлен Visual Assist или Resharper, тогда мы увидим предупреждение «Access to modified closure»), однако, после выполнения программы мы получим строчку «Not what yo might expect», удалилась только последняя буква. Чтобы понять, что произошло, нужно вспомнить, во что преобразуется цикл foreach, а преобразуется он в следующее:


IEnumerable<char> vowels = "aeiou";
using (IEnumerator<char> rator = vowels.GetEnumerator())
{
  char vowel;
  while (rator.MoveNext())
  {
    vowel = rator.Current;
    query = query.Where (c => c != vowel);
  }
}

На каждом шаге цикла мы добавляем в качестве условия проверку на неравенство переменной vowel. «Ленивый» оператор where захватывает одну и ту же переменную, поэтому при обращении к последовательности IEnumerable проверка и будет проходить на неравенство только одной переменной, получившей последнее значение. Для того, чтобы получить ожидаемый результат, необходимо добавить внутри цикла локальную переменную — тогда каждый раз будет захватываться новая переменная с новым значением.


foreach (char vowel in "aeiou")
{
  char cur = vowel;
  query = query.Where(c => c != cur);
}

Виртуальный MIDI-кабель

Виртуальный MIDI — кабель может быть полезен, если нужно без лишних затей управлять каким-либо приложением, поддерживающим MIDI-триггеры для выполнения внутренних команд. Представим себе, что у нас есть входной порт MIDI-in, в который можно неким программным образом посылать сигналы. Тогда легко сделать, например, пульт управления через Bluetooth записью звука, скажем в Adobe Audition. Уровень экзотичности вариантов использования зависит только от фантазии разработчика и насущных потребностей.

Небольшое исследование показало, что для Windows 7, пожалуй единственным вариантом является ipMIDI , и бесплатная версия LoopBe1, которой для вышеуказанных целей вполне достаточно — ограничение бесплатной версии в том, что создается только один виртуальный MIDI-порт.

После установки программы в системе появляется один выходной порт MIDI LoopBe Internal и соответствующий ему входной. Используя .NET библиотеку NAudio можно подавать сигналы на выходной порт LoopBe, которые в свою очередь будут переданы на виртуальный порт MIDI-in. Для Adobe Audition MIDI триггеры настраиваются с помощью команды Edit-Keyboard shortcuts and MIDI triggers. На скриншоте ниже показана привязка ноты D3 к действию остановки/продолжения воспроизведения звука.