Environment.TickCount() 的运行时间 - 避免换行

发布于 2024-07-17 08:31:23 字数 563 浏览 6 评论 0 原文

绝对值是否可以保护以下代码免受 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 帮助。

Does the absolute value protect the following code from the Environment.TickCount wrap?

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

Is there a better method of guarding against the Environment.TickCount wrap-around?

(This is .NET 1.1.)

Edit - Modified code according to Microsoft Environment.TickCount help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

揪着可爱 2024-07-24 08:31:23

避免环绕问题很容易,只要您要测量的时间跨度不超过 24.8 天(任何更长的时间都不能用有符号整数表示)。 在 C# 中:

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

我对 VB.NET 不是很熟悉,但是只要您使用未经检查的数学,这就会起作用,并且您不必担心换行。

例如,如果Environment.TickCount从2147483600换行到-2147483596,则没关系。 您仍然可以计算它们之间的差异,即 100 毫秒。

Avoiding the wrapping problem is easy, provided that the time span you want to measure is no more than 24.8 days (anything longer can't be represented in a signed integer). In C#:

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

I am not very familiar with VB.NET, but as long as you use unchecked math, this will work and you don't have to worry about the wrap.

For example, if Environment.TickCount wraps from 2147483600 to -2147483596, it doesn't matter. You can still compute the difference between them, which is 100 milliseconds.

生生漫 2024-07-24 08:31:23

因为我正在研究这个并偶然发现这个页面,所以我将分享我所做的一个小基准。

运行代码时,忽略每个测试的前 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 + ")");
}

Since I was looking into this and stumbled onto this page I'll share a small benchmark I made.

When running the code ignore the first 1000-result from each test (used for warmup). Also ignore the number printed after "ignore:". Its just there just to ensure no compiler optimization is done that would affect the result.

Results on my computer:

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

Both DateTime.Now.Ticks (test 2) and Stopwatch.ElapsedMilliseconds (test 3) are considerably slower than Environment.TickCount (test 1), but not enough to be noticeable unless you are performing a lot of calculations. For example I was investigating this because I need a cheap way of getting time in tight game loops.

I think my solution will have to be something like (untested):

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

Benchmark code:

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 + ")");
}
橙幽之幻 2024-07-24 08:31:23

只需使用 秒表

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。 而且它更容易理解。

Just use the stopwatch:

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

It will return an int64 and not an int32. Plus it's easier to understand.

椒妓 2024-07-24 08:31:23

我不确定你想实现什么目标。 看起来您正在尝试检测是否发生了特定间隔,如果发生了则执行某些特定逻辑。

如果您为此目的使用Environment.TickCount,这可能会给您带来无尽的痛苦。 正如您所指出的,该值可以并且将大约每 25 天循环一次。 没有办法阻止这种情况的发生,如果你尝试这样做,你最终会在代码中出现错误。

相反,为什么不执行以下操作:

  • 使用实际的计时器来内部执行事件
  • 将 StartTime 存储为 DateTime(或 VB 中的 Date)值。 该值的范围要长得多。 您的应用程序不太可能运行足够长的时间以使该值回绕(操作系统将需要在此之前很久重新启动:))。

I'm not sure what you're trying to accomplish. It looks like you are trying to detect if a particular interval has occured and if so execute some specific logic.

This is likely to cause you no end of pain if you use Environment.TickCount for this purpose. As you pointed out this value can and will wrap roughly every 25 days. There is no way to prevent this from happening and if you try to you'll end up with bugs in your code.

Instead why not do the following:

  • Use an actual Timer for the internal and execution of the event
  • Store the StartTime as a DateTime (or just Date in VB) value. This value has a much longer range. It's highly unlikely that your app will run long enough for this value to wrap around(OS will need a reboot long before then :) ).
苍白女子 2024-07-24 08:31:23

根据您的准确性,为什么不直接使用:

DateTime.Now.Ticks

此 Ticks 没有包装。

Depending on your accuracy, why don't you just use:

DateTime.Now.Ticks

There's no wrapping for this Ticks.

独守阴晴ぅ圆缺 2024-07-24 08:31:23

您的代码很可能无法工作。 如果应用程序要在 Vista 或更新的 Windows 版本上运行,则可以使用 GetTickCount64 ,但我假设情况并非如此,因为您使用的是 .NET 1.1。

您的代码不应检查间隔是否相等,因为这可能会出错。 使用 Int64 存储刻度,将最后处理的刻度存储在变量中,如果该变量大于新的刻度值,则您将获得换行并需要增加总刻度基数(Int64) - 假设您每 12 天检查一次蜱虫超过 1 次...;)

Your code will most likely not work. If the app was to run on Vista or a newer Windows version, one could use GetTickCount64, but I assume that this is not the case, given that you use .NET 1.1.

Your code should not check for equality with the interval, as this will likely go wrong. Use a Int64 to store the ticks, store the last processed tick in a variable, and if this variable is greater than the new tick value you've got a wrap and need to increment your total tick base (the Int64) - assuming that you check the tick more often than 1 every 12 days... ;)

邮友 2024-07-24 08:31:23

对于大多数用途来说,秒表是最合适的选择。 Environment.TickCount 仅在以下情况下才有意义:

  1. 时间测量本身所需的时间对时间至关重要,并且您需要最佳性能,因为您必须测量数百万或数十亿次。 这里 Tickcount 有优势,因为它可以是大约。 比 Stopwatch() 快 20-25 倍(在我的 i7 机器上测量)。

  2. 但是你不需要测量比1毫秒更精确的

  3. 你确定你不必计算超过24天,因为这是最大值。 TickCount 的 32 位整数(翻转)时间。

通常情况并非如此,至少第 1 点并不重要。 秒表(其测量精度通常至少高出约 1000 倍(微秒范围或更佳))通常是最合适的选择。

除了 .ElapsedMilliseconds 之外,您还可以使用静态方法
Stopwatch.GetTimestamp();

原因如下:在我的旧 i7 上循环调用 Stopwatch 1000 万次需要 1.3 秒。 GetTickCount64 稍微快一些,只需要 1 秒多一点。 因此,选择一个,不必为 32 位的东西而烦恼。

当然,给定的性能只是一个(我的)硬件上的近似值,可能会有所不同。

但微软表示将尽一切努力使秒表(性能计数器)在所有系统上表现最佳。 似乎不太可能有人轻易击败这一点:

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

引文:

“电源管理或 Turbo Boost 技术导致的处理器频率变化是否会影响 QPC 精度?
不会。如果处理器具有不变的 TSC,则 QPC 不会受到此类更改的影响。 [...]”

“QPC 能否可靠地工作在多处理器系统、多核系统和超线程系统上?

我如何确定和验证 QPC 在我的机器上运行?
您不需要执行此类检查。
[...]
尽管如此

,阅读该书仍能获得许多更详细的信息,尤其是硬件和 Windows 版本之间的性能差异。但这可能是最重要的,如果您需要实时性能,而 Windows 不是专门设计的,而且肯定不是最适合的。

Stopwatch is the most appropriate choice for most purposes. Environment.TickCount makes only sense for rare purposes where:

  1. The needed time for time measurement itself is time critical and you need best performance because you have to measure multiple million or billion times. Here Tickcount has advantages, because it can be approx. 20-25 times quicker than Stopwatch() (measured on my i7 machine).

  2. But you don't need to measure more accurate than 1 milliscecond

  3. You are sure that you have not to count more than 24 days, because this is the max. (rollover) time of 32-bit int for TickCount.

Normally this would be not the case, and at least point 1. doesn't matter. Stopwatch (which measures normally at least about 1000 times more precisely (microsecond range and better)) is generally the most appropriate choice.

Besides .ElapsedMilliseconds you can also use the static method
Stopwatch.GetTimestamp();

Here is why: For calling Stopwatch 10 million times in a loop on my older i7 1.3 seconds are needed. GetTickCount64 is slightly quicker with little more than 1 second for the same. So choose one and don't have to struggle with the 32-bit stuff.

Of course the given performances are just approximations on one (my) hardware and can differ.

But Microsoft states to do all to make Stopwatch (performance counters) best on all systems. It seems unlikely that someone easily beats that:

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_

Quotations:

"Is QPC accuracy affected by processor frequency changes caused by power management or Turbo Boost technology?
No. If the processor has an invariant TSC, the QPC is not affected by these sort of changes. [...]"

"Does QPC reliably work on multi-processor systems, multi-core system, and systems with hyper-threading?
Yes

How do I determine and validate that QPC works on my machine?
You don't need to perform such checks.
[...]
"

Nevertheless reading that gives a lot of more detailed information, especially to performance differences in hardware and Windows versions. But this may be primarily important, if you need real-time performance where Windows is not made-for and surely not best suitable.

转角预定愛 2024-07-24 08:31:23

我假设 StartTime 是一个正符号整数; 我假设您之前做了 StartTime = (Environment.TickCount And Int32.MaxValue)。

Abs() 不能解决问题。 如果间隔< 12.43 天,当 StartTime 很小时,代码将正确计时。 但有时,每当 StartTime > 时, (24.86 天 - 间隔),当 (TickCount And Int32.MaxValue) 翻转到 0 时,测试将比应有的时间更早通过。 12.43 天 AND (TickCount And Int32.MaxValue) 接近 12.43 天,测试永远不会通过。

执行Environment.TickCount And Int32.MaxValue 是不必要的,也没有帮助。 它生成一个正符号整数32,表示以毫秒为单位的时间,每 24.86 天环绕到 0。 替代方案更难理解,但它们可以毫无困难地让您的时间间隔长达 49.71 天。 原始 Environment.TickCount 在 24.86 天后以及此后每 49.71 天环绕到 Int32.MinValue。 转换 unchecked((Uint32)Environment.TickCount) 会生成一个表示时间(以毫秒为单位)的[正]无符号整数32,每 49.71 天回绕到 0。 (因为 Environment.TickCount 调用 Windows 函数 GetTickCount(),该函数返回一个 DWORD (UInt32),并将其未经检查地转换为 Int32。)

Environment.TickCount 的环绕不会影响经过时间的测量。 未经检查的减法总是给出正确的结果。

您可以将经过的时间计算为有符号 int32(-24.86 到 +24.86 天),如果您要比较独立时间或可能混合未来和过去的时间,则更有用; 或 unsigned int32(0 到 +49.71 天)。 您可以将 T0 捕获为有符号 int32 或无符号 int32; 这对结果没有影响。 如果 T0 或经过的时间未签名,则仅需要 [unchecked] 转换; 有了签名的 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 位值具有全范围(2.92 亿年)和全分辨率(1ms)。 或者您可以创建一个降低范围和/或分辨率的 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 的符号并不重要。 例如,按 1 进行环绕:iBase = Environment.TickCount gets Int32.MaxValue; 一刻后,Environment.TickCount 包装为 Int32.MinValue; 和 unchecked(Environment.TickCount - iBase) 产生 1。

未检查的 UInt32 差异给出了时间差,从 0 到 +49.71 天,并带有环绕。 (经过的时间永远不可能为负,因此这是更好的选择。)无符号 32 位整数的未经检查的减法会溢出并回绕到正确的值。 例如,按 1 进行环绕:iBase = Environment.TickCount gets UInt32.MaxValue; 一刻后,Environment.TickCount 包装为 UInt32.MinValue; 和 unchecked(Environment.TickCount - iBase) 产生 1。

I assume StartTime is a positive signed integer; I assume you earlier did StartTime = (Environment.TickCount And Int32.MaxValue).

Abs() does not fix the problem. If Interval < 12.43 days, the code will time correctly while StartTime is small. But some of the time, whenever StartTime > (24.86 days - Interval), the test will pass earlier than it should, the moment that (TickCount And Int32.MaxValue) rolls over to 0. Additionally, if Interval > 12.43 days AND (TickCount And Int32.MaxValue) is close to 12.43 days, the test will never pass.

Doing Environment.TickCount And Int32.MaxValue is not necessary and not helpful. It makes a positive signed integer32 representing time in ms that wraps around to 0 every 24.86 days. The alternatives are harder to understand, but they let you time intervals up to 49.71 days with no difficulty. The raw Environment.TickCount wraps around to Int32.MinValue after 24.86 days and every 49.71 days after that. Casting unchecked((Uint32)Environment.TickCount) makes a [positive] unsigned integer32 representing time in ms that wraps around to 0 every 49.71 days. (Because Environment.TickCount calls the Windows function GetTickCount(), which returns a DWORD (UInt32), and unchecked-casts it to Int32.)

The wraparound of Environment.TickCount does not impair measurement of elapsed time. Unchecked subtraction always gives the correct result.

You can compute elapsed time as signed int32 (-24.86 to +24.86 days), more useful if you are comparing independent times or potentially mixing future and past; or unsigned int32 (0 to +49.71 days). You can capture T0 as signed int32 or unsigned int32; it makes no difference to the results. You only need [unchecked] casting if T0 or elapsed time are unsigned; with signed T0 and signed elapsed time, you need no casts.

Capturing T0 signed: (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.
    ...
}

Capturing T0 unsigned (my preference; Environment.TickCount should never have been signed):

...
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.
    ...
}

If your TimeLimit is close to the wraparound limit (24.81 or 49.71 days), it is possible for time to expire without the test ever passing. You have to test at least once while Elapsed >= TimeLimit. (If you are not sure of testing often enough, you can add a backup test on Elapsed. If Elapsed ever decreases, it has wrapped, so the time must be up.)

=====

To time intervals longer than 49.71 days, you can count how many times uiElapsed wraps around, or you can count how many times Environment.TickCount wraps around. You can compose the counters into a 64-bit value, emulating GetTickCount64() (only available in on Windows Vista and newer). A 64-bit value has full range (292 million years) and full resolution (1ms). Or you can make a 32-bit value that has reduced range and/or resolution. The code that checks for wrapping must execute at least once every 49.71 days, to ensure that no wrap goes detected.

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;

Notes:

Environment.TickCount gives time since boot, in ms, with 10-16ms resolution.

The unchecked Int32 difference gives the time difference, -24.81 to +24.81 days, with wrap-around. Unchecked subtraction of 32-bit integers overflows and wraps around to the correct value. The sign of Environment.TickCount never matters. Example, wraparound by one: iBase = Environment.TickCount gets Int32.MaxValue; one tick later, Environment.TickCount wraps to Int32.MinValue; and unchecked(Environment.TickCount - iBase) yields 1.

The unchecked UInt32 difference gives the time difference, 0 to +49.71 days, with wrap-around. (Elapsed time can never be negative, so this is the better choice.) Unchecked subtraction of unsigned 32-bit integers overflows and wraps around to the correct value. Example, wraparound by one: iBase = Environment.TickCount gets UInt32.MaxValue; one tick later, Environment.TickCount wraps to UInt32.MinValue; and unchecked(Environment.TickCount - iBase) yields 1.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文