Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.Diagnostics.Stopwatch When measuring small time periods #90788

Closed
andreas-synnerdahl opened this issue Aug 18, 2023 · 3 comments
Closed

Comments

@andreas-synnerdahl
Copy link

Description

The GetRawElapsedTicks() method dose not handle possebility of negative values.

// Get the elapsed ticks.
private long GetRawElapsedTicks()
{
    long timeElapsed = _elapsed;

    if (_isRunning)
    {
        // If the Stopwatch is running, add elapsed time since
        // the Stopwatch is started last time.
        long currentTimeStamp = GetTimestamp();
        long elapsedUntilNow = currentTimeStamp - _startTimeStamp;
        timeElapsed += elapsedUntilNow;
    }
    return timeElapsed;
}

Simple fix:

private long GetRawElapsedTicks()
{
    long timeElapsed = _elapsed;

    if (_isRunning)
    {
        // If the Stopwatch is running, add elapsed time since
        // the Stopwatch is started last time.
        long currentTimeStamp = GetTimestamp();
        long elapsedUntilNow = currentTimeStamp - _startTimeStamp;

        if (elapsedUntilNow > 0)
        {
            timeElapsed += elapsedUntilNow;
        }
    }
    return timeElapsed;
}

I would like to suggest a little restructuring of the StopMethod on the System.Diagnostics.Stopwatch.

The current implementation allows negativ values in elapsedThisPeriod to be added to _elapsed and resets _elapsed if the total is a negativ value.

public void Stop()
{
    // Calling stop on a stopped Stopwatch is a no-op.
    if (_isRunning)
    {
        long endTimeStamp = GetTimestamp();
        long elapsedThisPeriod = endTimeStamp - _startTimeStamp;
        _elapsed += elapsedThisPeriod;
        _isRunning = false;

        if (_elapsed < 0)
        {
            // When measuring small time periods the Stopwatch.Elapsed*
            // properties can return negative values.  This is due to
            // bugs in the basic input/output system (BIOS) or the hardware
            // abstraction layer (HAL) on machines with variable-speed CPUs
            // (e.g. Intel SpeedStep).

            _elapsed = 0;
        }
    }
}

Would it not make more sens to skip adding negativ values to _elapsed?

        public void Stop()
        {
            // Calling stop on a stopped Stopwatch is a no-op.
            if (_isRunning)
            {
                long endTimeStamp = GetTimestamp();
                long elapsedThisPeriod = endTimeStamp - _startTimeStamp;
                _isRunning = false;

                if (elapsedThisPeriod > 0)
                {
                    // When measuring small time periods the Stopwatch.Elapsed*
                    // properties can return negative values.  This is due to
                    // bugs in the basic input/output system (BIOS) or the hardware
                    // abstraction layer (HAL) on machines with variable-speed CPUs
                    // (e.g. Intel SpeedStep).

                    _elapsed += elapsedThisPeriod;
                }
            }
        }

Or is the rationell due to some performance consideration in regards to the if-statement?

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 18, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 18, 2023
andreas-synnerdahl added a commit to andreas-synnerdahl/runtime that referenced this issue Aug 18, 2023
@vcsjones vcsjones added area-System.Diagnostics and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 18, 2023
@ghost
Copy link

ghost commented Aug 18, 2023

Tagging subscribers to this area: @tommcdon
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

The GetRawElapsedTicks() method dose not handle possebility of negative values.

// Get the elapsed ticks.
private long GetRawElapsedTicks()
{
    long timeElapsed = _elapsed;

    if (_isRunning)
    {
        // If the Stopwatch is running, add elapsed time since
        // the Stopwatch is started last time.
        long currentTimeStamp = GetTimestamp();
        long elapsedUntilNow = currentTimeStamp - _startTimeStamp;
        timeElapsed += elapsedUntilNow;
    }
    return timeElapsed;
}

Simple fix:

private long GetRawElapsedTicks()
{
    long timeElapsed = _elapsed;

    if (_isRunning)
    {
        // If the Stopwatch is running, add elapsed time since
        // the Stopwatch is started last time.
        long currentTimeStamp = GetTimestamp();
        long elapsedUntilNow = currentTimeStamp - _startTimeStamp;

        if (elapsedUntilNow > 0)
        {
            timeElapsed += elapsedUntilNow;
        }
    }
    return timeElapsed;
}

I would like to suggest a little restructuring of the StopMethod on the System.Diagnostics.Stopwatch.

The current implementation allows negativ values in elapsedThisPeriod to be added to _elapsed and resets _elapsed if the total is a negativ value.

public void Stop()
{
    // Calling stop on a stopped Stopwatch is a no-op.
    if (_isRunning)
    {
        long endTimeStamp = GetTimestamp();
        long elapsedThisPeriod = endTimeStamp - _startTimeStamp;
        _elapsed += elapsedThisPeriod;
        _isRunning = false;

        if (_elapsed < 0)
        {
            // When measuring small time periods the Stopwatch.Elapsed*
            // properties can return negative values.  This is due to
            // bugs in the basic input/output system (BIOS) or the hardware
            // abstraction layer (HAL) on machines with variable-speed CPUs
            // (e.g. Intel SpeedStep).

            _elapsed = 0;
        }
    }
}

Would it not make more sens to skip adding negativ values to _elapsed?

        public void Stop()
        {
            // Calling stop on a stopped Stopwatch is a no-op.
            if (_isRunning)
            {
                long endTimeStamp = GetTimestamp();
                long elapsedThisPeriod = endTimeStamp - _startTimeStamp;
                _isRunning = false;

                if (elapsedThisPeriod > 0)
                {
                    // When measuring small time periods the Stopwatch.Elapsed*
                    // properties can return negative values.  This is due to
                    // bugs in the basic input/output system (BIOS) or the hardware
                    // abstraction layer (HAL) on machines with variable-speed CPUs
                    // (e.g. Intel SpeedStep).

                    _elapsed += elapsedThisPeriod;
                }
            }
        }

Or is the rationell due to some performance consideration in regards to the if-statement?

Author: andreas-synnerdahl
Assignees: -
Labels:

area-System.Diagnostics, untriaged

Milestone: -

@tommcdon tommcdon added this to the 9.0.0 milestone Aug 18, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Aug 18, 2023
@IDisposable
Copy link
Contributor

Does this actually happen, I thought the underlying timestamp sources was monotonically increasing. See here

Is the performance counter monotonic (non-decreasing)?

Yes. QPC does not go backward.

and also won't roll over during a reasonable lifetime

How often does QPC roll over?

Not less than 100 years from the most recent system boot, and potentially longer based on the underlying hardware timer used. For most applications, rollover isn't a concern.

@tannergooding
Copy link
Member

Closing as a dupe of #66734.

As described in similar issues/related PRs:

We utilize the monotonic timers available from the underlying OS (QueryPerformanceCounter on Windows, CLOCK_UPTIME_RAW or CLOCK_MONOTONIC on Unix) which have strict contracts around not going backwards,

We can have up to a 1ns resolution, in which case overflow would take ~292 years in the worst possible scenario.

Given the support lifecycle of .NET and pieces of hardware, I'd say you have many other problems if you've not restarted your machine in 292 years ;)

@ghost ghost locked as resolved and limited conversation to collaborators Sep 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants