C#编译器优化循环?

时间:2014-11-21 22:33:55

标签: c# multithreading loops compiler-optimization

我正在尝试控制对对象的访问,以便在给定的时间跨度内只能访问该对象一定次数。在我所拥有的一个单元测试中,访问限制为每秒一次。所以5次访问应该只需4秒钟。但是,测试在我们的TFS服务器上失败,只用了2秒钟。我的代码的精简版本是这样的:

public class RateLimitedSessionStrippedDown<T>
{
    private readonly int _rateLimit;
    private readonly TimeSpan _rateLimitSpan;
    private readonly T _instance;
    private readonly object _lock;

    private DateTime _lastReset;
    private DateTime _lastUse;
    private int _retrievalsSinceLastReset;

    public RateLimitedSessionStrippedDown(int limitAmount, TimeSpan limitSpan, T instance )
    {
        _rateLimit = limitAmount;
        _rateLimitSpan = limitSpan;
        _lastUse = DateTime.UtcNow;
        _instance = instance;
        _lock = new object();
    }

    private void IncreaseRetrievalCount()
    {
        _retrievalsSinceLastReset++;
    }

    public T GetRateLimitedSession()
    {
        lock (_lock)
        {
            _lastUse = DateTime.UtcNow;

            Block();

            IncreaseRetrievalCount();

            return _instance;
        }
    }

    private void Block()
    {
        while (_retrievalsSinceLastReset >= _rateLimit &&
           _lastReset.Add(_rateLimitSpan) > DateTime.UtcNow)
        {
            Thread.Sleep(TimeSpan.FromMilliseconds(10));
        }

        if (DateTime.UtcNow > _lastReset.Add(_rateLimitSpan))
        {
            _lastReset = DateTime.UtcNow;
            _retrievalsSinceLastReset = 0;
        }
    }
}

在我的计算机上运行时,在Debug和Release中运行正常。但是,一旦我提交到TFS构建服务器,我的单元测试就失败了。这是测试:

    [Test]
    public void TestRateLimitOnePerSecond_AssertTakesAtLeastNMinusOneSeconds()
    {
        var rateLimiter = new RateLimitedSessionStrippedDown<object>(1, TimeSpan.FromSeconds(1), new object());

        DateTime start = DateTime.UtcNow;

        for (int i = 0; i < 5; i++)
        {
            rateLimiter.GetRateLimitedSession();
        }

        DateTime end = DateTime.UtcNow;

        Assert.GreaterOrEqual(end.Subtract(start), TimeSpan.FromSeconds(4));
    }

Test failure from the TFS CI Build Server

我想知道测试中的循环是否正在以一种单独的线程(或类似的东西)运行循环的每次迭代的方式进行优化,这意味着测试完成的速度比它应该更快,因为Thread.Sleep只有阻止调用它的线程?

1 个答案:

答案 0 :(得分:3)

你的问题在Block方法内部,现在我看一下评论,看来Henk Holterman已经提出了这个问题。

只有_lastReset.Add(_rateLimitSpan)DateTime.UtcNow相等时才会失败。这种情况不会经常发生,因此间歇性地失败的原因。修复方法是在此行中将>更改为>=

if (DateTime.UtcNow > _lastReset.Add(_rateLimitSpan))

这不是直观的原因,除非您了解每次调用DateTime.UtcNow时不一定每次调用都会返回一个新值。

即使DateTime.UtcNow精确到 100纳秒,其精度也与精度不同。它依赖于机器的定时器间隔,范围从1到15ms,但通常设置为15.25ms,除非您正在使用多媒体。

您可以使用此dotnetfiddle查看此操作。除非您打开的程序将计时器设置为不同的值,例如1ms,否则您会注意到刻度之间的差异大约是150000个刻度,大约15ms或正常的系统计时器间隔。

我们也可以通过将对DateTime.UtcNow的调用解除为临时变量并在方法结束时比较它们来验证这一点:

    private void Block()
    {
        var first = DateTime.UtcNow;
        while (_retrievalsSinceLastReset >= _rateLimit &&
           _lastReset.Add(_rateLimitSpan) > first)
        {
            Thread.Sleep(TimeSpan.FromMilliseconds(10));
            first = DateTime.UtcNow;
        }

        var second = DateTime.UtcNow;
        if (second > _lastReset.Add(_rateLimitSpan))
        {
            _lastReset = DateTime.UtcNow;
            _retrievalsSinceLastReset = 0;
        }

        if (first == second)
        {
            Console.WriteLine("DateTime.UtcNow returned same value");
        }
    }

在我的计算机上,对Block的所有五次调用打印出DateTime.UtcNow均等。