Прошедшее время с Environment.TickCount() — избежание переноса

Защищает ли абсолютное значение следующий код от переноса Environment.TickCount?

If Math.Abs((Environment.TickCount And Int32.MaxValue) - StartTime) >= Interval Then
    StartTime = Environment.TickCount And Int32.MaxValue ' set up next interval
    ...
    ...
    ...
End If

Есть ли лучший способ защиты от Environment.TickCount?

(Это .NET 1.1.)

Редактировать – измененный код в соответствии с Microsoft Справка Environment.TickCount.


person user79755    schedule 16.04.2009    source источник


Ответы (8)


Избежать проблемы переноса легко, при условии, что период времени, который вы хотите измерить, не превышает 24,8 дня (все, что больше, не может быть представлено целым числом со знаком). В С#:

int start = Environment.TickCount;
DoLongRunningOperation();
int elapsedTime = Environment.TickCount - start;

Я не очень хорошо знаком с VB.NET, но пока вы используете непроверенную математику, это будет работать, и вам не придется беспокоиться о переносе.

Например, если Environment.TickCount переносится с 2147483600 на -2147483596, это не имеет значения. Вы все еще можете вычислить разницу между ними, которая составляет 100 миллисекунд.

person Qwertie    schedule 29.11.2009
comment
Все еще упаковка после 50 дней. - person usr; 10.05.2012
comment
Используя этот метод, вы можете отслеживать максимум 50 дней. - person usr; 06.06.2012
comment
Да, я знаю, что об этом спрашивали много лет назад, но если вам нужно отслеживать более 50 дней с помощью этого метода, почему бы не следить за тем, сколько раз счетчик сворачивался. Затем вы можете запустить его на 50 дней * Int32.MaxValue. Проведите опрос, чтобы узнать, часто ли он заворачивался, и отслеживайте. - person Patrick Allwood; 06.01.2014

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

При запуске кода игнорируйте первые 1000 результатов каждого теста (используется для прогрева). Также игнорируйте число, напечатанное после «ignore:». Это просто для того, чтобы гарантировать, что оптимизация компилятора не повлияет на результат.

Результаты на моем компьютере:

Test1 1000000: 7ms    - Environment.TickCount
Test2 1000000: 1392ms - DateTime.Now.Ticks
Test3 1000000: 933ms  - Stopwatch.ElapsedMilliseconds

Оба DateTime.Now.Ticks (тест 2) и Stopwatch.ElapsedMilliseconds (тест 3) значительно медленнее, чем Environment.TickCount (тест 1), но недостаточно, чтобы быть заметным, если вы не выполняете много вычислений. Например, я исследовал это, потому что мне нужен дешевый способ получить время в напряженных игровых циклах.

Я думаю, что мое решение должно быть примерно таким (непроверенным):

var elapsed = Environment.TickCount - start;
if (elapsed < 0)
   elapsed = Int32.MaxValue - start + Environment.TickCount;

Код эталона:

void Main()
{
    Test1(1000);
    Test1(1000000);
    Test2(1000);
    Test2(1000000);
    Test3(1000);
    Test3(1000000);
}

void Test1(int c) {
    var sw = new Stopwatch();
    sw.Start();
    long sum = 0;
    for (var i = 0; i < c; i++)
    {
        sum += Environment.TickCount;
    }
    sw.Stop();
    Console.WriteLine("Test1 " + c + ": " + sw.ElapsedMilliseconds + "ms   (ignore: " + sum + ")");}

void Test2(int c) {
    var sw = new Stopwatch();
    sw.Start();
    long sum = 0;
    for (var i = 0; i < c; i++)
    {
        sum += DateTime.Now.Ticks;
    }
    sw.Stop();
    Console.WriteLine("Test2 " + c + ": " + sw.ElapsedMilliseconds + "ms   (ignore: " + sum + ")");
}
void Test3(int c) {
    var sw = new Stopwatch();
    sw.Start();
    long sum = 0;
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (var i = 0; i < c; i++)
    {
        sum += stopwatch.ElapsedMilliseconds;
    }
    sw.Stop();
    Console.WriteLine("Test3 " + c + ": " + sw.ElapsedMilliseconds + "ms   (ignore: " + sum + ")");
}
person Tedd Hansen    schedule 17.08.2015

Просто используйте секундомер:

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Thread.Sleep(10000);
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatch.Elapsed;

Он вернет int64, а не int32. Плюс легче понять.

person Carra    schedule 29.11.2009
comment
Я знаю, что этому 3 года, но Stopwatch/QueryPerformanceCounter мертв IMO, потому что почти все ПК теперь имеют несколько процессоров, и в этом случае это ненадежно, если вы не заставите свой поток работать в одном процессе... но тогда это, вероятно, делает недействительным то, что вы хотели проверить в любом случае, поскольку ваша версия выпуска, вероятно, не будет иметь этого ограничения. - person eselk; 19.06.2012
comment
@eselk это неверно для всех версий Windows выше Windows XP. См. раздел здесь. - person Karsten; 02.05.2017
comment
Я неправильно понял комментарий от Karsten: Более ясно: вы можете безопасно использовать и рекомендуется использовать Stopwatch() для Windows XP И выше !! Согласно этой связанной статье MSDN Карстена, есть только несколько процессоров, на которых счетчики производительности (и секундомер) занимают много времени: на относительно небольшом количестве платформ, которые не могут использовать регистр TSC в качестве основы QPC, [..]., получение меток времени с высоким разрешением может быть значительно дороже, чем получение меток времени с более низким разрешением. Если достаточно разрешения от 10 до 16 миллисекунд, вы можете использовать GetTickCount64.... - person Philm; 03.05.2017

Я не уверен, чего вы пытаетесь достичь. Похоже, вы пытаетесь определить, произошел ли определенный интервал, и если да, то выполните определенную логику.

Это, вероятно, причинит вам бесконечную боль, если вы используете Environment.TickCount для этой цели. Как вы указали, это значение может и будет переноситься примерно каждые 25 дней. Невозможно предотвратить это, и если вы попытаетесь, вы получите ошибки в своем коде.

Вместо этого, почему бы не сделать следующее:

  • Используйте реальный таймер для внутреннего и выполнения события
  • Сохраните StartTime как значение DateTime (или просто Date в VB). Это значение имеет гораздо более широкий диапазон. Крайне маловероятно, что ваше приложение будет работать достаточно долго, чтобы это значение было перенесено (задолго до этого ОС потребуется перезагрузка :)).
person JaredPar    schedule 16.04.2009

В зависимости от вашей точности, почему бы вам просто не использовать:

DateTime.Now.Ticks

Для этого Ticks нет упаковки.

person Keltex    schedule 16.04.2009

Ваш код скорее всего не сработает. Если приложение должно было работать в Vista или более новой версии Windows, можно было бы использовать GetTickCount64., но я предполагаю, что это не так, учитывая, что вы используете .NET 1.1.

Ваш код не должен проверять равенство с интервалом, так как это, скорее всего, пойдет не так. Используйте Int64 для хранения тиков, сохраните последний обработанный тик в переменной, и если эта переменная больше, чем новое значение тика, у вас есть обертка, и вам нужно увеличить общую базу тиков (Int64) - при условии, что вы проверяйте галочку чаще, чем 1 раз в 12 дней... ;)

person Lucero    schedule 16.04.2009

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

  1. Время, необходимое для самого измерения времени, критично по времени, и вам нужна максимальная производительность, потому что вы должны измерять несколько миллионов или миллиардов раз. Здесь у Tickcount есть преимущества, потому что он может составлять ок. В 20-25 раз быстрее, чем Stopwatch() (измерено на моей машине i7).

  2. Но вам не нужно измерять точнее, чем 1 миллисекунда

  3. Вы уверены, что не должны считать больше 24 дней, потому что это макс. (ролловер) время 32-битного целого числа для TickCount.

Обычно это было бы не так, и по крайней мере пункт 1. не имеет значения. Секундомер (который обычно измеряет как минимум в 1000 раз точнее (микросекундный диапазон и лучше)) обычно является наиболее подходящим выбором.

Помимо .ElapsedMilliseconds вы также можете использовать статический метод
Stopwatch.GetTimestamp();

Вот почему: для вызова секундомера 10 миллионов раз в цикле на моем старшем i7 требуется 1,3 секунды. GetTickCount64 немного быстрее, чуть более 1 секунды для того же. Поэтому выберите один из них, и вам не придется мучиться с 32-разрядными приложениями.

Конечно, приведенные характеристики являются приблизительными на одном (моем) оборудовании и могут отличаться.

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

https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#Does_QPC_reliably_work_on_multi-processor_systems__multi-core_system__and_________systems_with_hyper-threading_

Цитаты:

«Влияют ли на точность QPC изменения частоты процессора, вызванные управлением питанием или технологией Turbo Boost? Нет. Если процессор имеет инвариантный TSC, такие изменения не влияют на QPC. [...]»

«Надежно ли работает QPC в многопроцессорных системах, многоядерных системах и системах с гиперпоточностью? Да

Как определить и проверить, работает ли QPC на моей машине? Вам не нужно выполнять такие проверки. [...] "

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

person Philm    schedule 03.05.2017

Я предполагаю, что StartTime является положительным целым числом со знаком; Я предполагаю, что вы ранее сделали StartTime = (Environment.TickCount And Int32.MaxValue).

Abs() не устраняет проблему. Если Interval ‹ 12,43 дня, код будет правильно определять время, пока StartTime мало. Но в некоторых случаях, всякий раз, когда StartTime > (24,86 дня — Интервал), тест будет проходить раньше, чем должен, в тот момент, когда (TickCount And Int32.MaxValue) сбрасывается до 0. Кроме того, если Interval > 12,43 дня И (TickCount А Int32.MaxValue) близко к 12,43 дня, тест никогда не пройдет.

Выполнение Environment.TickCount And Int32.MaxValue не является необходимым и бесполезным. Он создает положительное целое число со знаком32, представляющее время в мс, которое обнуляется каждые 24,86 дня. Альтернативы сложнее понять, но они без труда позволяют вам использовать временные интервалы до 49,71 дня. Необработанный Environment.TickCount преобразуется в Int32.MinValue через 24,86 дня и каждые 49,71 дня после этого. Приведение unchecked((Uint32)Environment.TickCount) создает [положительное] целое число без знака 32, представляющее время в мс, которое возвращается к 0 каждые 49,71 дня. (Поскольку Environment.TickCount вызывает функцию Windows GetTickCount(), которая возвращает DWORD (UInt32) и без проверки приводит его к Int32.)

Зацикливание Environment.TickCount не влияет на измерение прошедшего времени. Непроверенное вычитание всегда дает правильный результат.

Вы можете вычислить прошедшее время как int32 со знаком (от -24,86 до +24,86 дней), что более полезно, если вы сравниваете независимое время или потенциально смешиваете будущее и прошлое; или unsigned int32 (от 0 до +49,71 дня). Вы можете захватить T0 как int32 со знаком или int32 без знака; это не имеет значения для результатов. Вам нужно [unchecked] приведение только в том случае, если T0 или прошедшее время не имеют знака; с подписанным T0 и подписанным прошедшим временем вам не нужны приведения.

Захват подписанного T0: (C#)

...
int iT0 = Environment.TickCount;  // T0 is NOW.
...
iElapsed = unchecked(Environment.TickCount - iT0)  // -24.81 to +24.81 days
uiElapsed = unchecked((uint)Environment.TickCount - (uint)iT0)  // 0 to +49.71 days
if (uiElapsed >= uiTimeLimit)  // IF time expired,
{
    iT0 = Environment.TickCount;  // For the next interval, new T0 is NOW.
    ...
}

Захват T0 без знака (мое предпочтение; Environment.TickCount никогда не должно быть подписано):

...
uint uiT0 = unchecked((uint)Environment.TickCount);  // T0 is NOW.
...
iElapsed = unchecked(Environment.TickCount - (int)uiT0)  // -24.81 to +24.81 days
uiElapsed = unchecked((uint)Environment.TickCount - uiT0)  // 0 to +49.71 days
if (uiElapsed >= uiTimeLimit)  // IF time expired,
{
    uiT0 = unchecked((uint)Environment.TickCount);  // For the next interval, new T0 is NOW.
    ...
}

Если ваш TimeLimit близок к пределу цикла (24,81 или 49,71 дня), время может истечь без прохождения теста. Вы должны протестировать хотя бы один раз, пока Elapsed >= TimeLimit. (Если вы не уверены, что тестируете достаточно часто, вы можете добавить резервный тест в Elapsed. Если Elapsed когда-либо уменьшается, он завершится, поэтому время должно быть истекло.)

=====

Для временных интервалов более 49,71 дня вы можете подсчитать, сколько раз uiElapsed повторяется, или вы можете подсчитать, сколько раз Environment.TickCount повторяется. Вы можете объединить счетчики в 64-битное значение, эмулируя GetTickCount64() (доступно только в Windows Vista и новее). 64-битное значение имеет полный диапазон (292 миллиона лет) и полное разрешение (1 мс). Или вы можете сделать 32-битное значение с уменьшенным диапазоном и/или разрешением. Код, проверяющий перенос, должен выполняться не реже одного раза в 49,71 дня, чтобы убедиться, что перенос не обнаружен.

uint uiTickCountPrev = 0;
uint uiTickWrap = 0;
Int64 TickCount64;
...
uiTickCount = unchecked((uint)Environment.TickCount)  // 0 to +49.71 days
if (uiTickCount < uiTickCountPrev)  // IF uiElapsed decreased,
    uiWrapcount++;  count that uiElapsed wrapped.
uiElapsedPrev = uiElapsed;  // Save the previous value.
TickCount64 = (Int64)uiTickWrap << 32 + (Int64)uiTickCount;

Заметки:

Environment.TickCount указывает время с момента загрузки в мс с разрешением 10-16 мс.

Непроверенная разница Int32 дает разницу во времени от -24,81 до +24,81 дня с циклическим циклом. Непроверенное вычитание 32-битных целых чисел приводит к переполнению и циклическому переходу к правильному значению. Знак Environment.TickCount никогда не имеет значения. Пример, перенос на единицу: iBase = Environment.TickCount получает Int32.MaxValue; через один тик Environment.TickCount заменяется на Int32.MinValue; и unchecked(Environment.TickCount - iBase) дает 1.

Непроверенная разница UInt32 дает разницу во времени от 0 до +49,71 дня с циклическим циклом. (Прошедшее время никогда не может быть отрицательным, так что это лучший выбор.) Неконтролируемое вычитание 32-битных целых чисел без знака переполняется и возвращается к правильному значению. Пример, перенос на единицу: iBase = Environment.TickCount получает UInt32.MaxValue; через один тик Environment.TickCount заменяется на UInt32.MinValue; и unchecked(Environment.TickCount - iBase) дает 1.

person A876    schedule 24.10.2016
comment
unchecked не кажется необходимым, чтобы приведение работало должным образом. Он заворачивается автоматически. - person ygoe; 20.03.2019