Являются ли таймеры и петли в .Net точными?

При разработке программы для расчета частоты и ширины импульса импульса, генерируемого таймером 555 таймера, поступающего на ПК через параллельный порт ПК. Я заметил, что каждый раз, когда я запускаю код, он показывает разные значения, поэтому я начинаю тестировать циклы и таймеры на точность. Я запустил следующий код и пришел к выводу, что они неточны (возможно, я ошибаюсь, пожалуйста, поправьте меня, если я есть!):

Для таймеров:

    int sec = 0;
    private void button2_Click(object sender, EventArgs e)
    {
        sec = DateTime.Now.Second;
        i = 0;
        timer1.Enabled = true;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        if (sec == DateTime.Now.Second)
        {
            i++;
        }
        else
        {
            timer1.Enabled = false;
            MessageBox.Show(i.ToString(),"Timer Output");
        }
    }

ВЫВОД: Должно быть одинаковым, но:

enter image description hereenter image description hereenter image description here

Для LOOP:

    private void button1_Click(object sender, EventArgs e)
    {
        i = 0;
        CheckForIllegalCrossThreadCalls = false;
        Thread t1 = new Thread(LoopTest);
        t1.Start();
    }

    void LoopTest()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        this.Cursor = Cursors.WaitCursor;
        while (true)
        {
            if (sw.ElapsedMilliseconds != 1000)
            {
                i++;
            }
            else
            {
                break;
            }
        }
        sw.Stop();
        this.Cursor = Cursors.Default;
        MessageBox.Show(i.ToString(), "Loop Output");
    }

ВЫВОД: Должно быть одинаковым, но:

enter image description hereenter image description hereenter image description here

Что делать, чтобы сделать петли и таймер точным, есть ли способ сделать это? Или мне нужно перейти к сложному и сложному C-коду и DOS?

Я думаю, что это основная причина для получения неправильных значений в этом вопросе: Количество входных частот параллельного порта - С#

Ответ 1

1) Не используйте DateTime.Now для измерения производительности, используйте StopWatch.

2) "OUTPUT: Should be same, but .."

Почему они должны? Вы используете управляемый/JIT-код в не RTOS (операционная система реального времени). Вы можете получить код в любое время, если ОС почувствует это. Что побуждает вас полагать, что выполнение одного и того же кода N раз в этой среде должно всегда давать такие же результаты в такой небольшой степени?

3) Таймеры в Windows имеют разрешение ~ 15 мс. Лучшая ставка для очень точных таймингов - это API HighPerformanceTimer для систем (ЦП), которые его поддерживают. Вы даже не указали нам интервал таймера.

Вы не можете рассматривать множество переменных здесь, и вы основываете свои прогнозы на ошибочных предположениях. Сколько раз вы даже измеряете этот код? Учитываете ли вы время, необходимое для его компиляции в первый раз? Вы работаете в режиме выпуска? Через VS? Много ли работает в фоновом режиме? Я мог бы продолжить.

Ответ 2

Тщательная реализация позволяет измерять периоды времени на большинстве платформ Windows с точностью до нескольких микросекунд. Обратите внимание на следующие факты:

  • Windows не является оперативной ОС: здесь это неважно!

  • Использовать приоритеты процесса/потока: SetPriorityClass до REALTIME_PRIORITY_CLASS и SetThreadPriority до THREAD_PRIORITY_TIME_CRITICAL. Убедитесь, что у вас есть безопасный код, потому что эти приоритеты могут блокировать систему, пока вызывающий поток занят. (Process.PriorityClass и Thread.Priority не способны повысить приоритеты до требуемых уровней.)

  • Возможно, увеличьте интервал прерывания и интервал обновления системы с помощью Мультимедийный таймер. (Существуют различные .NET проекты, которые обертывают эти функции мультимедийного таймера.)

  • В многоядерных системах выбор ядра может также влиять на точность. Преимущественно заставить поток ждать ожидания таймера на процессоре0. Функция SetThreadAffinityMask позволяет привязать поток к определенному процессору. (Для приложений .Net см. Thread.ProcessorAffinity.)

  • Используйте QueryPerformanceCounter и QueryPerformanceFrequency как источники для высокочастотных временных измерений. (Для приложений .Net см.: Создание класса Weryper QueryPerfCounter)

  • Убедитесь, что значение частоты счетчиков производительности откалибровано. Значение, возвращаемое QueryPerformanceFrequency, отличается от наблюдаемого значения смещением и некоторым тепловым дрейфом. Это может/приведет к ошибкам многих пользователей. См. Проект Windows Timestamp, чтобы узнать, как такая калибровка может быть выполнена.

И: Да, вам, возможно, придется пойти на жесткую кодировку. Но можно наблюдать микросекундную синхронизацию очень надежна на платформах Windows.

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

Ответ 3

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

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

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

Возможно, вы можете подробно рассказать о том, что вы действительно пытаетесь сделать, и кто-то может предложить некоторые советы.

Ответ 4

Ответ на ответ правильный: таймеры в Windows не точны. Ну, конечно, недостаточно точно для измерения аппаратных сигналов.

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

private StopWatch _Sw = new StopWatch();
private List<TimeSpan> _Samples = new List<TimeSpan>();
private Timer _Timer = new Timer(TimerTick, TimeSpan.FromSeconds(1));
private const int RequiredSamples = 100;

private void StartSampling()
{
    // You can change this next line to PriorityClass.RealTime if you're careful.
    System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                              = PriorityClass.High;
    _Samples.Capacity = RequiredSamples;
    _Timer.Start();
    _Sw.Start();
    Hook555Timer(On555Pulse);
}

private void On555Pulse(object sender, EventArgs e)
{
    _Sample.Add(_Sw.Elapsed);
}

private void TimerTick(object sender, EventArgs e)
{
    if (_Samples.Count > RequiredSamples)
    {
        System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                                    = PriorityClass.Normal;
        _Timer.Stop();
        _Sw.Stop();
        UnHook555Timer(On555Pulse);

        // You can now use the time between each TimeSpan 
        // in _Samples to determine statistics about your timer.
        // Eg: Min / Max duration, average and median duration.
        var durations = _Samples
                .Zip(_Samples.Skip(1), 
                    (a,b) => new { First = a, Second = b } )
                .Select(pair => pair.Second.Subtract(pair.First));
        var minTime = durations.Min(ts => ts.TotalMilliseconds);
        var maxTime = durations.Max(ts => ts.TotalMilliseconds);
        var averageTime = durations.Average(ts => ts.TotalMilliseconds);
        // I don't think LINQ has a Median() aggregate out of the box.
        // Some comment about "an exercise for the reader" goes here.
        var medianTime = durations.Median(ts => ts.TotalMilliseconds);

        var frequency = _Samples.Last()
                                 .Subtract(_Samples.First())
                                      .TotalSeconds / _Samples.Count;
    }
}

(NB: код, написанный в блокноте, вряд ли будет работать без дополнительных изменений)

Моя точность теперь определяется StopWatch, а не Timer (обратите внимание, что StopWatch может быть не более точной, чем Timer в вашей системе, проверьте свойства Frequency и IsHighResolution)., И я увеличиваю приоритет процесса во время выборки, чтобы минимизировать другие процессы, предотвращающие мой процесс.

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