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

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

  • когда я запускаю программу из VS, она работает медленно
  • когда я запускаю программу из встроенного EXE, она работает медленно
  • когда я запускаю программу через Performance Wizard / CPU Profiler, она запускается Blazing Fast.
  • когда я запускаю программу из встроенного EXE, а затем запускаю VS и присоединяю профилировщик к ЛЮБОМУ ДРУГОМ ПРОЦЕССУ, моя программа ускоряется!

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

Каждый проект в решении настроен на RELEASE, Отладка неуправляемого кода ОТКЛЮЧЕНА, Определить константы DEBUG и TRACE ОТКЛЮЧЕНА, Оптимизировать код - я пробовал либо, Уровень предупреждения - я пробовал либо, Подавить JIT - я пробовал, короче, я пробовал все решения, уже предложенные на StackOverflow - ни одно не сработало. Программа работает медленно вне профилировщика, быстро в профилировщике. Я не думаю, что проблема в моем коде, потому что он становится быстрее, если я присоединяю профилировщик к другому, несвязанному процессу!

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

ОБНОВЛЕНИЯ 1–8 подписаны

-------------------- Обновление1: --------------------

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

-------------------- Обновление 2: --------------------

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

-------------------- Обновление 3: --------------------

Запуск захвата экрана в видео также ускоряет работу программы!

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

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

-------------------- Обновление 5: --------------------

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

-На моем 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 is:

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 is:

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, когда программа работает быстро, точно в два раза выше, чем при медленной. Вот скриншоты: снимок экрана ANTS при медленном снимок экрана ANTS, когда он быстро

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

Также я обновил драйвер графического процессора, но это не помогло.

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

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


person Daniel    schedule 17.05.2013    source источник
comment
Это действительно работает? Мое первое предположение заключалось в том, что некоторые аспекты функциональности вообще не выполняются, когда профилировщик подключен, поэтому повышенная скорость не имеет значения, поскольку она не работает. Кроме того, может помочь приблизительное время. Это 10-кратное ускорение, 5% -ное ускорение, это 5 секунд или 10 мс, разница между быстрым и медленным, в чем? Кроме того, каков приемлемый уровень производительности для приложения?   -  person Servy    schedule 17.05.2013
comment
В дополнение ко всем пунктам, уже упомянутым @Servy: мне интересно, что на самом деле означает fast и slow. Обновление UI? Какой-то алгоритм? Какая-то пропускная способность данных?   -  person Daniel Hilgarth    schedule 17.05.2013
comment
Профилировщик находится в режиме выборки или измерения?   -  person xwlan    schedule 17.05.2013
comment
@Servy - Да, все аспекты программы работают. Разница в производительности примерно в строках в 10-50 раз быстрее. Очень быстро под профилировщиком. Необходимый уровень производительности - в реальном времени или максимально приближенный к этому. Задержки до 50 мс между получением и просмотром данных приемлемы, но чем меньше, тем лучше.   -  person Daniel    schedule 17.05.2013
comment
@Daniel - обновление пользовательского интерфейса заметно медленнее. Связь TCP заметно медленнее. Выполнение декодирования и других алгоритмических задач практически не отличается (не медленнее).   -  person Daniel    schedule 17.05.2013
comment
@xwlan - режим выборки.   -  person Daniel    schedule 17.05.2013
comment
Я не понимаю, почему это происходит, за исключением того, что профилировщик поддерживает кеш-память процессора в горячем состоянии, страницу с доступом к контактам в рабочем наборе путем периодического прерывания и доступа к стеку пользовательского режима и кодовым страницам. Что касается других процессов, которые также ускоряются, я предполагаю, что это связано с тем, что большинство приложений совместно используют большую часть системных dll, поэтому профилирование других процессов приводит к тому, что эти dll хранятся в ОЗУ, что косвенно снижает сбой страницы вашего процесса.   -  person xwlan    schedule 17.05.2013
comment
Я знаю, что сложное приложение сложно разложить, но посмотрите, сможете ли вы его разветвить и начать удалять функции, пока оно не будет вести себя одинаково в обоих режимах. Посмотрите, сможете ли вы таким образом изолировать причину.   -  person Bobson    schedule 17.05.2013
comment
@Bobson - Я пробовал. Из 10 диаграмм данных, которые я показываю, я отключил 9 (не получал их данные через TCP, не запускал никаких функций декодирования, не пытался обновить элементы управления графиками). Один из оставшихся действительно ускорился, но при обоих сценариях он ускорился, так что разница осталась. Даже если я отключу все графики (чтобы экран оставался белым), низкая производительность останется. Я запускал таймеры для большинства функций, все дают максимальную задержку 0-50 мс. Тем не менее, программа работает медленно, а интерфейс обновляется раз в секунду.   -  person Daniel    schedule 18.05.2013
comment
Вы пробовали воспроизвести это на другом компьютере, а не только на виртуальной машине? Возможно, это связано с вашей видеокартой.   -  person Bobson    schedule 20.05.2013
comment
@Bobson - на моем Core i5-2400S с Intel HD2000 пользовательский интерфейс работает медленно, а загрузка процессора составляет ~ 15%. На Core 2 Duo коллеги с Intel G41 Express пользовательский интерфейс работает быстро, но загрузка ЦП составляет ~ 90% (что тоже ненормально).   -  person Daniel    schedule 20.05.2013
comment
@Bobson - на Core i5-2400S с выделенным Radeon X1650 пользовательский интерфейс работает молниеносно, загрузка процессора составляет ~ 50%.   -  person Daniel    schedule 20.05.2013
comment
В вашем Update2 вы можете запускать профилировщик, даже если он работает медленно. Вы смотрели на номера профилировщика, чтобы найти отсутствие производительности?   -  person Oliver    schedule 22.05.2013
comment
@Oliver - в Update2 программа работает на виртуальной машине, а профилировщик работает на хост-машине, привязанной не к моей программе, а к другому случайному процессу, например StickyNotes или Explorer. Так что я получаю не фактические цифры по моей программе, а по StickyNotes. Цель этого обновления - показать, что странная связь, когда профилировщик ускоряет мою программу, даже если она подключена к несвязанному процессу, не выполняется, когда мое приложение находится на виртуальной машине (но все еще сохраняется, когда оба работают на хосте)   -  person Daniel    schedule 22.05.2013
comment
@ Даниэль: Хорошо, я недостаточно внимательно прочитал. Следующая попытка: вы используете lock (d.fftDataLock). Итак, каково вмешательство между другими ресурсами, использующими ту же блокировку? Что будет, если убрать строчки о вызове метода graphFFT.Refresh / RefreshComplete?   -  person Oliver    schedule 22.05.2013
comment
@Daniel: Незначительная проблема (возможно, только в сокращенном коде): вы создаете пустой short[] tempData, который будет заменен в незаполненной блокировке, поэтому вы можете удалить = new short[2048]. Или лучше один раз создать буфер вне while(true) и вместо .Clone() использовать .CopyTo().   -  person Oliver    schedule 22.05.2013
comment
@Oliver - Clone намного быстрее, чем CopyTo, я сначала попробовал CopyTo, но поскольку он копирует каждый элемент в новую ячейку памяти, а есть 2k элементов, он не может выполняться один раз каждые 1/10 мс. Но я думаю, что вы правы насчет нового короткометражного фильма [2048], исправьте его сейчас ...   -  person Daniel    schedule 22.05.2013
comment
@Daniel: Никогда не проводил тест производительности на .CopyTo(), но я, хотя внутри он также сделал бы memcpy и не повторял бы каждый элемент.   -  person Oliver    schedule 22.05.2013
comment
@Oliver - Если я удалю graphFFT.Refresh (), у меня не будет графиков на дисплее и я не смогу сказать, хорошая производительность или нет - мой взгляд может заметить разницу только в том случае, если графики работают. Единственный другой поток, который использует блокировку, - это тот, который получает данные через TCP и устанавливает их в буфер d.fftData. Без блокировки я получаю смешанные показания - половина графика - из одного обновления, следующая половина - из другого.   -  person Daniel    schedule 22.05.2013
comment
@Oliver - Clone () не выполняет memcpy, он возвращает указатели. edit: моя ошибка, извините, CopyTo также выполняет неглубокое копирование в соответствии с MSDN. Моя разница в производительности между ними, должно быть, возникла из-за чего-то другого ...   -  person Daniel    schedule 22.05.2013
comment
@Daniel: замените отображение графика простым текстовым сообщением, записанным в метку (но не используйте label.Text + = myText, только label.Text = text), возможно, с отметкой времени и т. Д., Чтобы увидеть мерцание. Я готов поспорить, что проблема в графическом драйвере и в том, как библиотека графов вызывает его для рисования чего-либо (GDI, openGL, DirectX и т. Д.), И насколько хорош драйвер графической карты для выполнения этих команд.   -  person Oliver    schedule 22.05.2013
comment
Сколько графиков в приложении?   -  person It'sNotALie.    schedule 24.05.2013
comment
@newStackExchangeInstance - есть 10 сопоставлений данных, 6 из которых являются графиками, а остальные также являются своего рода визуальными представлениями. Обычно одновременно видны только 4 из них. Те, которые не видны, вообще не обрабатываются (данные не декодируются). Я попробовал показать все 10 одновременно, а также показал только 1. Разница была минимальной.   -  person Daniel    schedule 24.05.2013
comment
Возможно, какая-то часть вашего кода зависит от общесистемного разрешения таймера? Профилировщик, вероятно, увеличивает частоту таймера, поэтому все, что использует это (например, ожидание дескрипторов ожидания, Thread.Sleep, Timer и т. Д.), Будет намного более точным. Это объяснило бы оба случая ускорения - например, приложения WPF увеличивают разрешение таймера при необходимости (например, при выполнении плавной анимации), а затем возвращают его обратно (когда анимация останавливается). Профилировщикам, очевидно, также нужны более точные таймеры.   -  person Luaan    schedule 22.04.2015
comment
Вы учли настройки энергосбережения в своей ОС? Иногда процессор будет увеличивать / уменьшать тактовую частоту в зависимости от использования ... установите в окнах режим электропитания «Высокая производительность» и проверьте любые настройки BIOS, относящиеся к этому?   -  person Milney    schedule 12.01.2017
comment
О, господин, @Milney, это было почти 2 года назад! И да, я все это проверял, никакого эффекта.   -  person Daniel    schedule 12.01.2017


Ответы (5)


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

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

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

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

powercfg -energy duration 5

Где-то в выходном файле будет следующее:

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

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

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

Вот код для увеличения разрешения таймера (изначально опубликованный как ответ на этот вопрос: как установить разрешение таймера с C # на 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 ().

person DonT    schedule 15.07.2016

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

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

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:

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() может потенциально вызвать выделение головной боли, когда элементы графического интерфейса не были отключены, и пользователь запускает другие события или одно и то же событие во второй раз одновременно, вызывая подъем стека, который по своей природе ставит в очередь первое действие после нового, но я ухожу не по теме.

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

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);
  }
}

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

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

---------- Редактировать после Update7 -------------

Я бы вообще удалил эту 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 () и передача контекста устройства растровых изображений). Затем передайте растровое изображение в основной поток и обновите его. Таким образом, вы потеряете удобство конструктора и сетки свойств в своей среде IDE, но сможете использовать свободные ядра процессора.

---------- редактировать ответ на примечания --------

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

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

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

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

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

person Louis Somers    schedule 19.05.2013
comment
Да, я пробовал это - предоставление Thread.Sleep (500) потоку, который обновляет мои ZedGraphs, дает положительный эффект на отзывчивость пользовательского интерфейса, но делает графики намного медленнее, и мне нужно, чтобы они были близки к реальному времени. Все, что ниже 500 мс, имеет убывающую отдачу. - person Daniel; 20.05.2013
comment
Обычно Sleep (200) (5 обновлений в секунду) достаточно, чтобы восприниматься как режим реального времени. Sleep (33) даст вам 30 кадров в секунду, что отлично подходит для видео (но обычно слишком много для приложения). Проверьте, как вы обновляете ZedGraphs. Вероятно, ваши рабочие потоки ждут GDI + для рендеринга растровых изображений, в которых нет необходимости. Вы должны собирать данные быстро, не блокируя рабочие потоки больше, чем необходимо. Когда у вас есть необработанные данные, позвольте рабочим потокам продолжать работу, пока ваш основной поток обновляет графики и отображает их (без блокировки общих ресурсов в процессе). - person Louis Somers; 20.05.2013
comment
Я просто прочитал ваши обновления, попробуйте перетащить значок с рабочего стола над приложением, если он ускоряется, пока ваша мышь движется, то это потому, что WndProc запускается для более частой обработки сообщений (проверка, должен ли курсор мыши меняться, когда элемент может становятся доступными для некоторых элементов управления). В этом случае ваши рабочие зависят от обрабатываемых сообщений WndProc (и, вероятно, работают в основном потоке?). Вы используете таймер для запуска работы? - person Louis Somers; 20.05.2013
comment
Если перетащить значок, ничего существенного не произойдет. Нет, я использую поток с постоянным циклом, который проверяет наличие новых данных. Затем он считывает его из буфера и устанавливает в ZedGraph. Но я также пробовал напрямую вызывать эту функцию при поступлении данных (без цикла), и результат тот же. - person Daniel; 21.05.2013
comment
Извините за двойной комментарий - разница между обновлениями в 5–200 мс очень заметна. 10 мс - это самое долгое время, которое все еще ощущается в реальном времени. Имеет смысл то, что вы говорите, что потоки ждут GDI +. Я обновляю ZedGraphs, вызывая .Refresh () в объекте zedGraphControl в вышеупомянутом циклическом потоке (а не в основном потоке). Это неправильно? Как мне избежать ожидания в GDI +? - person Daniel; 21.05.2013
comment
Вы не должны вызывать Refresh () в каком-либо другом потоке, кроме вашего основного потока. В любом случае он будет работать в вашем основном потоке. Тот факт, что он не генерирует исключения между потоками (чего я действительно ожидал), означает, что он, скорее всего, выполняет if (InvokeRequired) проверку и делегирует действие вашему основному потоку через насос сообщений (WndProc), вызывая Invoke (). Плохо то, что он блокируется, пока ваш основной поток не обработает сообщение и не выполнит функцию Refresh (). Попытайтесь провести рефакторинг, чтобы все, что связано с графическим интерфейсом, оставалось в вашем основном потоке, сохраняя при этом рабочие места. - person Louis Somers; 21.05.2013
comment
Хорошо, а если у меня открыто 5 окон устройств? MainThread не может справиться с такой нагрузкой. + Это не просто обновление, здесь происходит много декодирования и буферизации, непосредственно перед тем, как я вызываю Refresh () в ZedGraph. Я обновил вопрос с помощью фрагмента кода. Кроме того, я попытался быстро отредактировать, где LoopDataRefresh выполняется в основном потоке и регулярно вызывает Application.DoEvents (), но программа зависает. - person Daniel; 22.05.2013
comment
Я быстро просмотрел ваш опубликованный код, но не заметил ничего серьезного. Попробуйте заменить zgcGraph.Refresh(); на zgcGraph.Invalidate(); и пусть таймер в основном потоке сделает zgcGraph.Update(). Я посмотрю, смогу ли я более тщательно просканировать ваш код в эти выходные. - person Louis Somers; 23.05.2013
comment
Я пробовал это изначально, но это стало еще медленнее. Основной поток кажется перегруженным всеми сообщениями, которые перекачивает ZedGraph (см. Ответ IdeaHat здесь: social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/) - person Daniel; 23.05.2013
comment
Ваш update7 доказывает, что метод рисования выполняется в вашем основном потоке (см. Program.Main() в верхней части стека). В быстрой части это скорее всего пропуск кадров. Это просто зависит от частоты срабатывания WndProc, связанной с частотой, с которой ваш поток вызывает update () (который, похоже, не блокирует). Мне сказали, что частота WndProc более или менее связана с частотой обновления экрана (но я в этом сомневаюсь). В любом случае, я считаю, что пропуск кадров не должен ухудшать внешний вид в реальном времени. Ознакомьтесь с этими графиками. - person Louis Somers; 27.05.2013
comment
Нет, это не так, Program.Main () находится на вершине `` стека '', потому что этот список отсортирован по времени выполнения функции включительно, а Program.Main () выполняется от начала программы до конца, что делает его самая продолжительная функция. В любом случае, он не пропускает кадры, потому что график становится более плавным. Он работает так же, как и ссылка, которую вы разместили, когда работает быстро. - person Daniel; 27.05.2013
comment
Дэниел, эта иерархия на самом деле является трассировкой стека. Методы на том же уровне (братья и сестры в TreeView) сортируются по времени, включая дочерние (дочерние элементы (вызовы выше в стеке) помечаются). Ваш вызов Update () отсутствует. Он начинается от WndProc () до Paint () до Draw (). Если вы прокрутите вниз до другого узла корневого уровня, вы должны найти другой поток, начинающийся с LoopDataRefresh(), и вызов RefreshGraph() на один уровень ниже, Thread.Sleep(), вероятно, будет первым элементом в LoopDataRefresh(). Будьте уверены, весь рисунок выполняется в основном потоке. «Быстро» - это пропуск кадров! - person Louis Somers; 27.05.2013
comment
Насколько я помню, другого узла корневого уровня нет (сейчас я дома, завтра отправлю обратно форму). Из того, что я собрал, ничто в этом списке не отсортировано или не сгруппировано по потокам, и нет способа определить, какой поток выполняет какую функцию - только то, сколько времени это заняло. Но даже если главный поток рисует (что я тоже верю и никогда не спорил), как это означает, что он пропускает кадры? Могу ли я буквально видеть больше кадров (более плавное движение)? - person Daniel; 28.05.2013
comment
Ах, да, я понимаю :) Также да, я знаю о представлении потоков Concurrency Profiler и использовал его, к сожалению, это дает мне только одну сторону медали, потому что программа всегда работает быстро, если запущен какой-либо профилировщик VisualStudio. Так что мне не с чем сравнивать данные. Насколько я могу судить, явных блоков потоков нет (нет каскадных исполнительных блоков или повторяющихся шаблонов блокировок) - person Daniel; 28.05.2013
comment
Попробуйте запустить Ants в режиме настенных часов вместо времени процессора и посмотрите на другие потоки, используют ли они столько процессора, сколько могут, или они тоже спят? - person Louis Somers; 28.05.2013
comment
К сожалению, в течение нескольких дней я не могу воспроизвести проблему ... Я получаю постоянную приемлемую скорость (которая все еще кажется немного медленнее, чем та, что была у меня в профилировщике две недели назад), на которую не влияет любой из факторов, которые влияли на это две недели назад - профилировщик, захват видео или окно драйвера графического процессора. У меня до сих пор нет объяснения причин этого ... - person Daniel; 01.06.2013

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

person Robert    schedule 20.05.2013
comment
Я пробовал это, но не смог выделить проблемные участки. У меня такое чувство, что какая бы ни была причина этого, это не что-то в моем коде. Возможно, что-то про ZedGraph. Поскольку это не имеет абсолютно никакого смысла, если я где-то допустил ошибку, эти действия, совершенно не относящиеся к моему работающему приложению, исправят ее (например, прикрепление профилировщика к notepad.exe) - person Daniel; 20.05.2013
comment
Я бы попытался прокомментировать гораздо больше, даже если приложение запускается с пустым окном. Это идеальный метод проб и ошибок, гарантирующий стабильные результаты, но вы должны взять на себя обязательство все комментировать, если это необходимо. - person Robert; 21.05.2013

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

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

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

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

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

person Haymo Kutschbach    schedule 06.10.2019

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

Подробно здесь, производительность отладки можно повысить с помощью атрибута 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;
}
person Fidel    schedule 19.05.2019