Почему моя программа С# быстрее работает в профилировщике?

У меня есть относительно большая система (до 25000 строк) для мониторинга устройств, связанных с радио. Он показывает графики и такие, используя последнюю версию ZedGraph. Программа кодируется с использованием С# на VS2010 с Win7. Проблема заключается в следующем:

  • Когда я запускаю программу из VS, она работает медленно
  • Когда я запускаю программу из встроенного EXE, она работает медленно
  • когда я запускаю программу, хотя Performance Wizard/CPU Profiler, он запускает Blazing Fast.
  • когда я запускаю программу из встроенного EXE, а затем запускаю VS и прикрепляю профилировщик к любому другому процессу, моя программа ускоряется!

Я хочу, чтобы программа всегда выполнялась так быстро!

Каждый проект в решении установлен в RELEASE, Debug неуправляемый код отключен, Define DEBUG и TRACE константы отключены, оптимизировать код - я тоже пытался, уровень предупреждения - я тоже пытался, подавить JIT - я пробовал либо, Короче, я пробовал все решения, уже предлагаемые в StackOverflow - ни один не работал. Программа медленна вне профилировщика, быстро в профилировщике. Я не думаю, что проблема в моем коде, потому что он становится быстрым, если я присоединяю профилировщик к другому, не связанному с ним процессу!

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

ОБНОВЛЕНИЯ 1 - 8 следуйте

-------------------- Update1: --------------------

Проблема кажется не связанной с ZedGraph, потому что она все еще проявляется после того, как я заменил ZedGraph своим собственным основным рисунком.

-------------------- Update2: --------------------

Запуск программы на виртуальной машине, программа все еще работает медленно, а запуск профайлера с хост-компьютера не ускоряется.

-------------------- Update3: --------------------

Запуск начального экрана на видео также ускоряет программу up!

-------------------- Update4: --------------------

Если я открою окно настроек графического драйвера Intel (эта вещь: http://www.intel.com/support/graphics/sb/img/resolution_new.jpg) и просто постоянно наведите курсор на кнопки, чтобы они светились и т.д., моя программа ускоряется!. Он не ускоряется, если я запускаю GPUz или Kombustor, но без разгона на GPU - он остается постоянным 850 МГц.

-------------------- Update5: --------------------

Тесты на разных машинах:

- На моем Core i5-2400S с Intel HD2000 пользовательский интерфейс работает медленно, а загрузка процессора составляет ~ 15%.

- На коллеге Core 2 Duo с Intel G41 Express пользовательский интерфейс работает быстро, но загрузка процессора составляет ~ 90% (что тоже не нормально)

- На Core i5-2400S с выделенным Radeon X1650 пользовательский интерфейс работает быстро, загрузка процессора составляет ~ 50%.

-------------------- Update6: --------------------

Отладка кода, показывающая, как я обновляю один граф (graphFFT является инкапсуляцией ZedGraphControl для удобства использования):

public void LoopDataRefresh() //executes in a new thread
        {
            while (true)
            {
                while (!d.Connected)
                    Thread.Sleep(1000);
                if (IsDisposed)
                    return;
//... other graphs update here
                if (signalNewFFT && PanelFFT.Visible)
                {
                    signalNewFFT = false;
                    #region FFT
                    bool newRange = false;
                    if (graphFFT.MaxY != d.fftRangeYMax)
                    {
                        graphFFT.MaxY = d.fftRangeYMax;
                        newRange = true;
                    }
                    if (graphFFT.MinY != d.fftRangeYMin)
                    {
                        graphFFT.MinY = d.fftRangeYMin;
                        newRange = true;
                    }

                    List<PointF> points = new List<PointF>(2048);
                    int tempLength = 0;
                    short[] tempData = new short[2048];

                    int i = 0;

                    lock (d.fftDataLock)
                    {
                        tempLength = d.fftLength;
                        tempData = (short[])d.fftData.Clone();
                    }
                    foreach (short s in tempData)
                        points.Add(new PointF(i++, s));

                    graphFFT.SetLine("FFT", points);

                    if (newRange)
                        graphFFT.RefreshGraphComplete();
                    else if (PanelFFT.Visible)
                        graphFFT.RefreshGraph();

                    #endregion
                }
//... other graphs update here
                Thread.Sleep(5);
            }
        }

SetLine:

public void SetLine(String lineTitle, List<PointF> values)
    {
        IPointListEdit ip = zgcGraph.GraphPane.CurveList[lineTitle].Points as IPointListEdit;
        int tmp = Math.Min(ip.Count, values.Count);
        int i = 0;
        while(i < tmp)
        {
            if (values[i].X > peakX)
                peakX = values[i].X;
            if (values[i].Y > peakY)
                peakY = values[i].Y;
            ip[i].X = values[i].X;
            ip[i].Y = values[i].Y;
            i++;
        }
        while(ip.Count < values.Count)
        {
            if (values[i].X > peakX)
                peakX = values[i].X;
            if (values[i].Y > peakY)
                peakY = values[i].Y;
            ip.Add(values[i].X, values[i].Y);
            i++;
        }
        while(values.Count > ip.Count)
        {
            ip.RemoveAt(ip.Count - 1);
        }
    }

RefreshGraph:

public void RefreshGraph()
    {
        if (!explicidX && autoScrollFlag)
        {
            zgcGraph.GraphPane.XAxis.Scale.Max = Math.Max(peakX + grace.X, rangeX);
            zgcGraph.GraphPane.XAxis.Scale.Min = zgcGraph.GraphPane.XAxis.Scale.Max - rangeX;
        }
        if (!explicidY)
        {
            zgcGraph.GraphPane.YAxis.Scale.Max = Math.Max(peakY + grace.Y, maxY);
            zgcGraph.GraphPane.YAxis.Scale.Min = minY;
        }
        zgcGraph.Refresh();
    }

.

-------------------- Update7: --------------------

Просто запустил его через профилировщик ANTS. Он говорит мне, что обновление ZedGraph подсчитывается, когда программа выполняется быстро, в точности в два раза выше по сравнению с тем, когда она замедляется. Вот скриншоты: screenshot of ANTS when slowscreenshot of ANTS when fast

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

Кроме того, я обновил драйвер GPU, который не помог.

-------------------- Update8: --------------------

К сожалению, в течение нескольких дней я не могу воспроизвести проблему... Я получаю постоянную приемлемую скорость (которая по-прежнему выглядит немного медленнее, чем то, что у меня было в профилировщике две недели назад) t повлиял на любой из факторов, которые повлияли на него две недели назад - профайлер, видеозахват или окно драйвера графического процессора. У меня до сих пор нет объяснений, что вызывало это...

Ответ 1

Luaan разместил решение в комментариях выше, это системное разрешение по таймеру. Разрешение по умолчанию составляет 15,6 мс, профилировщик устанавливает разрешение 1 мс.

У меня была такая же проблема, очень медленное выполнение, которое ускорилось бы при запуске профилировщика. Проблема исчезла на моем ПК, но появилась на других компьютерах, казалось бы, наугад. Мы также заметили, что проблема исчезла при запуске окна Join Me в Chrome.

Мое приложение передает файл по CAN-шине. Приложение загружает CAN-сообщение с восемью байтами данных, передает его и ожидает подтверждения. С таймером, установленным на 15,6 мс, каждая поездка в оба конца составляла ровно 15,6 мс, и вся передача файлов заняла бы около 14 минут. С таймером, установленным на 1 мс время кругового перерыва, было изменено, но было бы столь же низким, как 4 мс, и все время передачи снизилось бы до менее двух минут.

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

powercfg -energy duration 5

Выходной файл в нем будет иметь следующее:

Разрешение таймера платформы: разрешение таймера платформы По умолчанию разрешение таймера платформы составляет 15,6 мс (15625000 нс) и должно использоваться всякий раз, когда система находится в режиме ожидания. Если разрешение таймера увеличено, технологии управления мощностью процессора могут оказаться неэффективными. Разрешение таймера может быть увеличено из-за воспроизведения мультимедиа или графической анимации. Разрешение текущего таймера (100 нс) 10000 Максимальный период таймера (100 нс) 156001

Мое текущее разрешение - 1 мс (10000 единиц 100 нс), за которым следует список программ, которые запрашивали увеличенное разрешение.

Эту информацию, а также более подробную информацию можно найти здесь: https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/

Вот некоторый код, чтобы увеличить разрешение таймера (изначально опубликовано как ответ на этот вопрос: как установить разрешение таймера с С# на 1 мс?):

public static class WinApi
{
    /// <summary>TimeBeginPeriod(). See the Windows API documentation for details.</summary>

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
    [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)]

    public static extern uint TimeBeginPeriod(uint uMilliseconds);

    /// <summary>TimeEndPeriod(). See the Windows API documentation for details.</summary>

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
    [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)]

    public static extern uint TimeEndPeriod(uint uMilliseconds);
}

Используйте его так, чтобы увеличить разрешение: WinApi.TimeBeginPeriod(1);

И как это, чтобы вернуться к умолчанию: WinApi.TimeEndPeriod(1);

Параметр, передаваемый TimeEndPeriod(), должен соответствовать параметру, который был передан TimeBeginPeriod().

Ответ 2

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

Например (это пример оконных форм), когда основной поток проверяет общий прогресс в узком цикле вместо использования таймера, например:

private void SomeWork() {
  // start the worker thread here
  while(!PollDone()) {
    progressBar1.Value = PollProgress();
    Application.DoEvents(); // keep the GUI responisive
  }
}

Замедление может повысить производительность:

private void SomeWork() {
  // start the worker thread here
  while(!PollDone()) {
    progressBar1.Value = PollProgress();
    System.Threading.Thread.Sleep(300); // give the polled thread some time to work instead of responding to your poll
    Application.DoEvents(); // keep the GUI responisive
  }
}

Выполняя это правильно, следует избегать использования вызова DoEvents alltogether:

private Timer tim = new Timer(){ Interval=300 };

private void SomeWork() {
  // start the worker thread here
  tim.Tick += tim_Tick;
  tim.Start();
}

private void  tim_Tick(object sender, EventArgs e){
  tim.Enabled = false; // prevent timer messages from piling up
  if(PollDone()){
    tim.Tick -= tim_Tick;
    return;
  }
  progressBar1.Value = PollProgress();
  tim.Enabled = true;
}

Вызов Application.DoEvents() может потенциально вызвать выделение головных болей, когда материал GUI не был отключен, и пользователь запускает другие события или одно и то же событие во второй раз одновременно, вызывая рост стека, который по своей природе создает первое действие за новым, но я ухожу от темы.

Вероятно, этот пример слишком специфичен для winforms, я попытаюсь сделать более общий пример. Если у вас есть поток, который заполняет буфер, который обрабатывается другими потоками, не забудьте оставить некоторый System.Threading.Thread.Sleep() slack в цикле, чтобы другие потоки могли выполнить некоторую обработку, прежде чем проверять, нужно ли снова заполнять буфер:/p >

public class WorkItem { 
  // populate with something usefull
}

public static object WorkItemsSyncRoot = new object();
public static Queue<WorkItem> workitems = new Queue<WorkItem>();

public void FillBuffer() {
  while(!done) {
    lock(WorkItemsSyncRoot) {
      if(workitems.Count < 30) {
        workitems.Enqueue(new WorkItem(/* load a file or something */ ));
      }
    }
  }
}

Рабочий поток будет иметь трудности с получением чего-либо из очереди, поскольку он постоянно блокируется заполняющей нитью. Добавление Sleep() (за пределами блокировки) может значительно ускорить другие потоки:

public void FillBuffer() {
  while(!done) {
    lock(WorkItemsSyncRoot) {
      if(workitems.Count < 30) {
        workitems.Enqueue(new WorkItem(/* load a file or something */ ));
      }
    }
    System.Threading.Thread.Sleep(50);
  }
}

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

Я не уверен, давал ли я представительные примеры (довольно сложно придумать что-то простое), но я думаю, что смысл в том, что сон() в правильном месте может помочь улучшить поток других потоков.

---------- Изменить после обновления7 -------------

Я удаляю эту цепочку LoopDataRefresh() вообще. Скорее поместите таймер в ваше окно с интервалом не менее 20 (это будет 50 кадров в секунду, если ни один не был пропущен):

private void tim_Tick(object sender, EventArgs e) {
  tim.Enabled = false; // skip frames that come while we're still drawing
  if(IsDisposed) {
    tim.Tick -= tim_Tick;
    return;
  }

  // Your code follows, I've tried to optimize it here and there, but no guarantee that it compiles or works, not tested at all

  if(signalNewFFT && PanelFFT.Visible) {
    signalNewFFT = false;

    #region FFT
    bool newRange = false;
    if(graphFFT.MaxY != d.fftRangeYMax) {
      graphFFT.MaxY = d.fftRangeYMax;
      newRange = true;
    }
    if(graphFFT.MinY != d.fftRangeYMin) {
      graphFFT.MinY = d.fftRangeYMin;
      newRange = true;
    }

    int tempLength = 0;
    short[] tempData;

    int i = 0;

    lock(d.fftDataLock) {
      tempLength = d.fftLength;
      tempData = (short[])d.fftData.Clone();
    }

    graphFFT.SetLine("FFT", tempData);

    if(newRange) graphFFT.RefreshGraphComplete();
    else if(PanelFFT.Visible) graphFFT.RefreshGraph();
    #endregion

    // End of your code

    tim.Enabled = true; // Drawing is done, allow new frames to come in.
  }
}

Здесь оптимизированный SetLine(), который больше не принимает список точек, но необработанных данных:

public class GraphFFT {
    public void SetLine(String lineTitle, short[] values) {
      IPointListEdit ip = zgcGraph.GraphPane.CurveList[lineTitle].Points as IPointListEdit;
      int tmp = Math.Min(ip.Count, values.Length);
      int i = 0;
      peakX = values.Length;

      while(i < tmp) {
        if(values[i] > peakY) peakY = values[i];
        ip[i].X = i;
        ip[i].Y = values[i];
        i++;
      }
      while(ip.Count < values.Count) {
        if(values[i] > peakY) peakY = values[i];
        ip.Add(i, values[i]);
        i++;
      }
      while(values.Count > ip.Count) {
        ip.RemoveAt(ip.Count - 1);
      }
    }
  }

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

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

Если вы действительно хотите, чтобы чертеж выполнялся в отдельном потоке, вам нужно будет нарисовать график в растровое изображение (вызов Draw() и передача контекста устройства растровых изображений). Затем передайте растровое изображение в основной поток и обновите его. Таким образом, вы теряете удобство конструктора и сетки свойств в своей среде разработки, но вы можете использовать в противном случае свободные ядра процессора.

---------- отредактируйте ответ на замечания --------

Да, есть способ рассказать, что называет. Посмотрите на свой первый экранный снимок, вы выбрали график "дерева вызовов". Каждая следующая строка немного перескакивает (это древовидный вид, а не только список!). В графе вызовов каждое дерево node представляет собой метод, который был вызван его родительским деревом - node (метод).

На первом изображении WndProc вызывается около 1800 раз, обрабатывается 872 сообщения, из которых 62 вызвано ZedGraphControl.OnPaint() (что в свою очередь составляет 53% от общего времени основного потока).

Причина, по которой вы не видите другой rootnode, заключается в том, что в третьем раскрывающемся списке выбрано "[604] Mian Thread", о котором я раньше не заметил.

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

Похоже, что потоки не синхронизированы и синхронизированы в разное время, когда сообщения об обновлениях появляются слишком поздно (когда WndProc был сделан и некоторое время спал), а затем внезапно вовремя какое-то время. Я не очень хорошо знаком с муравьями, но у него есть временная шкала бок о бок, включая время сна? Вы должны уметь видеть, что происходит в таком виде. Microsofts инструмент просмотра потоков пригодится для этого: enter image description here

Ответ 3

Когда я никогда не слышал или не видел нечто подобное; Идентификатор рекомендует подход здравого смысла комментирования разделов кода/инъекционных возвратов на вершинах функций до тех пор, пока вы не найдете логику, которая создает побочный эффект. Вы знаете свой код и, вероятно, имеете образованное предположение, где начать рубить. Else chop в основном все как тест на здравомыслие и начинают добавлять блоки обратно. Я часто удивлялся, как быстро можно найти такие, казалось бы, невозможные ошибки для отслеживания. Когда вы найдете соответствующий код, у вас будет больше подсказок, чтобы решить вашу проблему.

Ответ 4

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

  • Переменные среды: проблема таймера в другом ответе - только один пример. Могут быть изменения к Path и другим переменным, новые переменные могут быть установлены профилировщиком. Запишите текущие переменные среды в файл и сравните обе конфигурации. Попробуйте найти подозрительные записи, сбрасывать их одну за другой (или в комбинациях), пока не получите одинаковое поведение в обоих случаях.

  • Частота процессора. Это может легко произойти на ноутбуках. Потенциально, система энергосбережения устанавливает частоту процессора (ов) на более низкое значение для экономии энергии. Некоторые приложения могут "разбудить" систему, увеличив частоту. Проверьте это с помощью монитора производительности (permon).

  • Если приложения работают медленнее, чем возможно, должно быть неэффективное использование ресурсов. Используйте профилировщик, чтобы исследовать это! Вы можете прикрепить профилировщик к (медленному) запущенному процессу, чтобы увидеть, какие ресурсы under-/перегружены. В основном, есть две основные категории причин слишком медленного выполнения: ограничение памяти и выполнение вычисления. Оба могут дать больше понимания того, что вызывает замедление.

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

Ответ 5

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

Как подробно описано здесь, производительность отладки можно улучшить с помощью атрибута DebuggerNonUserCode. Например:

[DebuggerNonUserCode]
public static bool IsArchive(string filename)
{
    bool result = false;
    try
    {
        //this calls an external library, which throws an exception if the file is not an archive
        result = ExternalLibrary.IsArchive(filename);
    }
    catch
    {

    }
    return result;
}