Точное измерение времени выполнения кода в потоке (C#)

Я пытаюсь как можно точнее измерить время выполнения некоторых фрагментов кода в нескольких потоках, принимая во внимание переключение контекста и время простоя потока. Приложение реализовано на C# (VS 2008). Пример:

public void ThreadFunc ()
{
    // Some code here

    // Critical block #1 begins here
    long lTimestamp1 = Stopwatch.GetTimestamp ();

    CallComplex3rdPartyFunc (); // A

    long lTimestamp2 = Stopwatch.GetTimestamp ();
    // Critical block #1 ends here

    // Some code here

    // Critical block #2 begins here
    long lTimestamp3 = Stopwatch.GetTimestamp ();

    CallOtherComplex3rdPartyFunc (); // B

    long lTimestamp4 = Stopwatch.GetTimestamp ();
    // Critical block #2 ends here

    // Save timestamps for future analysis.
}

public int Main ( string[] sArgs )
{
    // Some code here

    int nCount = SomeFunc ();

    for ( int i = 0; i < nCount; i++ )
    {
        Thread oThread = new Thread ( ThreadFunc );
        oThread.Start ();
    }

    // Some code here

    return ( 0 );
}

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

Я запускаю приведенный выше код в нескольких потоках - где-то от 1 до 200 потоков, в зависимости от пользовательского ввода. Компьютеры, на которых работает этот код, имеют от 2 до 16 ядер — пользователи используют меньшее количество потоков на более слабых машинах.

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

Это означает, что продолжительность между lTimestamp1 и lTimestamp2 включает время, когда поток фактически не выполнялся — он ожидал повторного планирования, пока выполнялись другие потоки. Однако количество тактов в любом случае увеличивается, так что продолжительность теперь действительно

Время блока кода = A + B + некоторое время, проведенное в других потоках

пока я хочу, чтобы это было только

Время блока кода = A + B

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

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

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

Будем очень признательны за любые советы - код сборки C++ или x86 также будет работать.

Редактировать: кажется, что это невозможно реализовать. Представленная ниже идея Скотта (с использованием GetThreadTimes) хороша, но, к сожалению, GetThreadTimes() — это несовершенный API, и он почти никогда не возвращает правильные данные. Спасибо за все отклики!


person xxbbcc    schedule 29.09.2011    source источник
comment
вы не можете этого сделать, кроме как изменить среду выполнения .NET и собрать там время (даже тогда это будет неточно!)   -  person Yahia    schedule 29.09.2011
comment
Связано: stackoverflow. ком/вопросы/3853993/   -  person Henk Holterman    schedule 29.09.2011
comment
Я не знаю, комментарий это или решение, но почему вы просто не профилировали код и не измерили его, как следует, я имею в виду в одиночку? Всегда будет переключатель потоков (даже с ОС!)..   -  person gbianchi    schedule 29.09.2011
comment
и вы можете создать прокси (тот же интерфейс) для сторонних функций и измерить время входа и выхода из этих функций... в зависимости от ситуации - например, если у вас нет доступа к источнику вызывающего приложения вы можете сделать это, подключив или подобное   -  person Yahia    schedule 29.09.2011
comment
Я не думаю, что есть способ сделать это без профайлера. Также стоит упомянуть, что для задач, связанных с процессором, создание 200 потоков на 16-ядерной машине контрпродуктивно.   -  person Yaur    schedule 29.09.2011
comment
@Yahia, он хочет измерять время только во время выполнения, то есть не время, когда его поток ожидает планирования.   -  person Yaur    schedule 29.09.2011
comment
да - и это на самом деле невозможно, то, что я описываю, - это способы приблизительного определения времени...   -  person Yahia    schedule 29.09.2011
comment
Поток будет тратить время на ожидание планирования только в том случае, если есть другие потоки, которые можно запланировать. Почему бы просто не понизить приоритет каждого другого потока в системе настолько низко, чтобы он никогда не запускался? Тогда вы измеряете только время, проведенное в вашем потоке, потому что это единственный поток в системе, который фактически работает.   -  person Eric Lippert    schedule 29.09.2011
comment
gbianchi: приложение многопоточное; нет возможности делать измерения только на одном потоке. Кроме того, вы, похоже, не полностью прочитали мой вопрос: я не могу использовать профилировщик, потому что эти временные метки должны собираться во время выполнения, все время для последующего анализа. Я не пытаюсь сделать одноразовое измерение. Yahia: Я не могу использовать прокси. Если бы я хотел только измерить время между двумя отметками времени, текущий код уже делает это. Как сказал Яур, мне нужно вычесть время, когда поток не работает. Эрик: Я не могу этого сделать; в системе будут другие потоки, которые необходимо запустить.   -  person xxbbcc    schedule 29.09.2011


Ответы (2)


Это можно сделать с помощью вызова Native API GetThreadTimes. Вот статья о CodeProject, в которой он используется.

Второй вариант — использовать QueryThreadCycleTime. Это не даст вам время, но даст вам количество циклов, выполняемых текущим потоком.

Имейте в виду, что вы не можете просто преобразовать cycles->seconds напрямую из-за того, что многие процессоры (особенно мобильные процессоры) не работают с фиксированной скоростью, поэтому нет постоянного числа, на которое можно было бы умножить, чтобы получить прошедшее время в секундах. Но если вы используете процессор, который не меняет свою скорость, тогда будет простой математической задачей получить время настенных часов из циклов.

person Scott Chamberlain    schedule 29.09.2011
comment
Он должен использовать это в сочетании с Thread.BeginThreadAffinity, чтобы код оставался в том же физическом потоке во время выполнения. - person user7116; 29.09.2011
comment
Скотт и sixlettervariables - спасибо, это выглядит многообещающе. Мне придется прочитать всю статью CodeProject, но, похоже, это хорошая зацепка. - person xxbbcc; 29.09.2011
comment
Извините, я вынужден убрать отметку «Правильный ответ», хотя ваш ответ самый лучший и близкий. К сожалению, GetThreadTimes() — бесполезный API — он имеет разрешение 15 мс и часто сообщает 0 как время ядра и/или пользовательское время, когда потоки уступают или переходят в состояние ожидания. Похоже, то измерение, которое я пытаюсь провести, невозможно, по крайней мере, в Windows. - person xxbbcc; 30.09.2011
comment
@xxbbcc Я знаю, что это старый вопрос, и вам, вероятно, это больше не нужно, но взгляните на опубликованное мной обновление. - person Scott Chamberlain; 14.06.2013
comment
@ScottChamberlain Спасибо за обновление - я знал о процессорах с переменной скоростью (это проблема и для виртуальных машин). Я искал способ собрать информацию о частоте вместе с таймингами, но даже если информация доступна (редко), она в основном ненадежна. Я отказался от попыток сделать это, так как большинство аппаратных средств просто не сообщают о полезных показателях производительности. - person xxbbcc; 14.06.2013

Вы можете использовать Stopwatch.Start() и Методы Stopwatch.Stop() для приостановки/ продолжить измерение времени, оно не сбрасывает прошедшее /ElapsedMilliseconds, поэтому, возможно, вы можете использовать это .

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

ИЗМЕНИТЬ:

Интересная статья с тестами: Как много времени уходит на переключение контекста?

person sll    schedule 29.09.2011
comment
@Yaur: я понимаю ключевой момент вопроса, я считаю, что у нас нет технических инструментов для обработки переключений контекста. - person sll; 29.09.2011
comment
sll: к сожалению, я не могу сделать это только с секундомером, так как я не знаю, когда ОС останавливает текущий поток, чтобы переключиться на другой. Итак, моя проблема заключается именно в том, что базовые тики ЦП все еще истекают, даже когда мой текущий поток не запущен, но ожидает повторного планирования. Это, очевидно, не проблема только для текущего потока моего приложения — это относится ко всем потокам, которые я создаю, поскольку все эти потоки пытаются выполнить одно и то же измерение. - person xxbbcc; 29.09.2011