Как точно предсказать продолжительность выполнения цикла?

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

void DummyLoop(long y)
{
    long counter = 0;
    long DummyArray[3];
    char loopArray;
    
    while(counter < y)
    {
        for(loopArray = 0; loopArray < 3; loopArray++)
        {
            DummyArray[x] = 10000/50; // Some int division operation
        }
        counter++;
    }
}

Я измерил результаты с помощью внутреннего таймера MCU и вызвал цикл следующим образом:

T0CON0bits.EN = 1; // Start timer
DummyLoop(10000);
T0CON0bits.EN = 0; // Stop timer

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

Я добавил туда столбец, умножающий время, необходимое для одного цикла, на количество циклов для этого теста, просто чтобы показать, как меняются результаты. Примерно через 1000 циклов он сужается, но почему результат так сильно меняется? Я бы подумал, что время, необходимое для выполнения одного цикла, можно умножить на любое число? Для справки я использую 8-битный микроконтроллер PIC18F47Q43, скомпилированный с помощью XC8.

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

Some programmer dude 02.06.2024 15:12

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

tinman 02.06.2024 15:17

Для чего вы собираетесь использовать это средство?

Ted Lyngmo 02.06.2024 15:31
10000/50 — постоянное выражение. Он будет оценен во время компиляции и заменен на 200.
Clifford 02.06.2024 21:28

Если ваш компилятор посредственный или лучше (и вы, очевидно, включили оптимизацию), то время выполнения будет примерно 0 секунд, или, скорее, время переключения вашего контакта GPIO. Есть ли хорошие компиляторы для этой цели или вам придется использовать шутку Microchip о компиляторе? Рассмотрите возможность портирования на настоящий.

Lundin 03.06.2024 08:36
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
123
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это 8-битный MCU, поэтому, если у вас код без планировщика, то примерно без каких-либо прерываний вы можете получить почти одинаковое время выполнения для каждого цикла. Однако, если вмешивается планировщик и какой-либо источник прерываний, то выполнение одного цикла будет занимать разное время каждый раз, когда он выполняется. если t1 — это время, необходимое для выполнения 10 000 циклов, вы можете получить лучший результат со средним значением t1/10 000 для одного цикла. В соответствии с результатом, время одного цикла умножается на количество циклов. Если вы выполнили его, оно может быть неточным, или если вы рассчитывали с использованием таймингов инструкций, тогда источники планировщика/прерывания могут мешать выполнению цикла. Пожалуйста, укажите, откуда взята ссылка на синхронизацию одного цикла.

Ответ принят как подходящий

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

Вместо этого правильная формула: время(петли) = петли * К1 + К2.

У нас есть время для 1 и для 2 цикла. Это дает нам константу K1.

K1 = время(2) – время(1) = 0,017625 мс – 0,0100625 мс = 0,0075625 мс

Имея этот результат, мы можем вычислить константу K2.

K2 = время(1) - 1 * K1 = 0,0100625 мс - 1 * 0,0075625 мс = 0,0025 мс

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

Петли Измеренный Рассчитано Дельта 1 0,0100625 0,0100625 0 2 0,017625 0,017625 0 3 0,0251875 0,0251875 0 10 0,078125 0,078125 0 50 0,380625 0,380625 0 100 0,75875 0,75875 0 500 3,78375 3,78375 0 1000 7.565 7.565 0 5000 37,814 37,815 0,001 10000 75,626 75,6275 0,0015

Выглядит хорошо, не так ли? Различия в последних двух значениях, безусловно, являются ошибками округления используемой процедуры вывода. Вычисления с float обычно ограничиваются примерно 6 ненулевыми цифрами.


Примечание 1:

К1 – время одного цикла:

  • Проверка состояния внешнего цикла (положительно)
  • Внутренний цикл for внутри внешнего цикла
  • Фиктивный расчет и назначение

К2 — время единовременных накладных расходов:

  • Запуск/остановка таймера (детали зависят от точной работы таймера)
  • Вызов и возврат функции
  • Пролог и эпилог в функции
  • Выход из внешнего цикла
K1  K2
    x   T0CON0bits.EN = 1; // Start timer
    x   DummyLoop(10000);
    x   T0CON0bits.EN = 0; // Stop timer

    x   void DummyLoop(long y)
    x   {
    x       long counter = 0;
    x       long DummyArray[3];
    x       char loopArray;
    
x   x       while(counter < y)
x           {
x               for(loopArray = 0; loopArray < 3; loopArray++)
x               {
x                   DummyArray[x] = 10000/50; // Some int division operation
x               }
x               counter++;
x           }
    x   }

Заметка 2:

Вы можете суммировать эти времена из сгенерированного машинного кода.

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

Это именно то, что я искал, спасибо! Что именно означают K1 и K2?

ezra_vdj 03.06.2024 00:22

Другие вопросы по теме

Похожие вопросы

Программирование сокетов C, отправляющее файл изображения «изображение не может быть отображено, поскольку оно содержит ошибку»
Общий доступ к данным в pthreads
Строгое псевдонимирование первого члена структуры через непрозрачный указатель в C
Можете ли вы передать один и тот же указатель на SystemTimeToTzSpecificLocalTime как для ввода, так и для вывода?
Шифрование/дешифрование (чтение строки и изменение строки определенными символами)
Рефакторинг тестовых источников
Почему эта оптимизация компилятора происходит только вне основного?
Один и тот же код C с использованием GTK дает разные результаты в разных условиях компиляции (в Makefile и без него) без ошибок
Определение макросов с тем же именем, что и функции стандартной библиотеки
Экранированная обратная косая черта (двойная обратная косая черта) в C считается как два байта в строке