具有灵活位大小的定时器环绕
Timer wraparound with flexible bit sizes
给定一个 counter/timer 增加并简单地以给定的位宽度回绕,一个 well-known 解决找到计数器的两个捕获值之间的差异的问题的解决方案(计数器可能有包裹在两点之间)只是在计数器上执行无符号减法(如果不知道哪个更大,则可能将结果解释为有符号)。
例如给定一个 32 位定时器,像这样的代码可以用来确定一些代码花费的时间长度 运行:
uint32_t start = GetSomePlatformSpecificTimer();
RunSomeOtherCode();
uint32_t end = GetSomePlatformSpecificTimer();
uint32_t platformTicksTakenByCode = end - start;
或者检查是否已达到某个时间限制:
uint32_t limit = GetSomePlatformSpecificTimer() + timeLimitInTicks;
while (true)
{
bool finished = DoSomethingSmall();
if (finished)
break;
if ((int32_t)(GetSomePlatformSpecificTimer() - limit) >= 0)
return ERROR_TIMEOUT;
}
如果已知计时器为 32 位宽,则此方法效果很好。也可以通过改变使用的类型来针对 16 位或 8 位定时器进行调整。
是否有类似简单的方法可以在计时器大小与类型大小不匹配的情况下执行相同的操作?比如24位定时器,或者18位定时器。
假设位大小 <= 32 并且由某些外部 header 中的 #define COUNTER_WIDTH
指定(并且可能会更改)。
最好的解决办法是sign-extend把两个计数器的值从COUNTER_WIDTH
改成32位再用上面的代码?我可以看到这可能适用于 FF -> 00 翻转,但我认为它会破坏 7F -> 80 翻转,所以大概必须对此进行某种检查(如果值可能 sign-extending接近零, zero-extending 如果值接近中点)。我认为这也意味着两个值之间的差异不应超过计数器范围的四分之一,否则可能会导致问题。
或者有更好的方法吗?
您可以不进行符号扩展,而是相乘,使整个范围与您的算术类型大小相同。换句话说,使用定点运算来填充整数。在你的例子中,uint32_t
看起来像
uint32_t start = GetSomePlatformSpecificTimer();
RunSomeOtherCode();
uint32_t end = GetSomePlatformSpecificTimer();
start <<= 32-COUNTER_WIDTH;
end <<= 32-COUNTER_WIDTH;
uint32_t platformTicksTakenByCode = end - start;
platformTicksTakenByCode >>= 32-COUNTER_WIDTH;
显然您想要封装该算法:
const uint32_t start = GetScaledTimer();
RunSomeOtherCode();
const uint32_t end = GetScaledTimer();
const uint32_t platformTicksTakenByCode = RescaleDuration(end - start);
和
uint32_t GetScaledTimer()
{
return GetSomePlatformSpecificTimer() << 32-COUNTER_WIDTH;
}
uint32_t RescaleDuration(uint32_t d)
{
return d >> 32-COUNTER_WIDTH;
}
然后,您将获得与全角计时器大致相同的行为,并在必要时使用相同的选项来使用签名类型。
给定一个 counter/timer 增加并简单地以给定的位宽度回绕,一个 well-known 解决找到计数器的两个捕获值之间的差异的问题的解决方案(计数器可能有包裹在两点之间)只是在计数器上执行无符号减法(如果不知道哪个更大,则可能将结果解释为有符号)。
例如给定一个 32 位定时器,像这样的代码可以用来确定一些代码花费的时间长度 运行:
uint32_t start = GetSomePlatformSpecificTimer();
RunSomeOtherCode();
uint32_t end = GetSomePlatformSpecificTimer();
uint32_t platformTicksTakenByCode = end - start;
或者检查是否已达到某个时间限制:
uint32_t limit = GetSomePlatformSpecificTimer() + timeLimitInTicks;
while (true)
{
bool finished = DoSomethingSmall();
if (finished)
break;
if ((int32_t)(GetSomePlatformSpecificTimer() - limit) >= 0)
return ERROR_TIMEOUT;
}
如果已知计时器为 32 位宽,则此方法效果很好。也可以通过改变使用的类型来针对 16 位或 8 位定时器进行调整。
是否有类似简单的方法可以在计时器大小与类型大小不匹配的情况下执行相同的操作?比如24位定时器,或者18位定时器。
假设位大小 <= 32 并且由某些外部 header 中的 #define COUNTER_WIDTH
指定(并且可能会更改)。
最好的解决办法是sign-extend把两个计数器的值从COUNTER_WIDTH
改成32位再用上面的代码?我可以看到这可能适用于 FF -> 00 翻转,但我认为它会破坏 7F -> 80 翻转,所以大概必须对此进行某种检查(如果值可能 sign-extending接近零, zero-extending 如果值接近中点)。我认为这也意味着两个值之间的差异不应超过计数器范围的四分之一,否则可能会导致问题。
或者有更好的方法吗?
您可以不进行符号扩展,而是相乘,使整个范围与您的算术类型大小相同。换句话说,使用定点运算来填充整数。在你的例子中,uint32_t
看起来像
uint32_t start = GetSomePlatformSpecificTimer();
RunSomeOtherCode();
uint32_t end = GetSomePlatformSpecificTimer();
start <<= 32-COUNTER_WIDTH;
end <<= 32-COUNTER_WIDTH;
uint32_t platformTicksTakenByCode = end - start;
platformTicksTakenByCode >>= 32-COUNTER_WIDTH;
显然您想要封装该算法:
const uint32_t start = GetScaledTimer();
RunSomeOtherCode();
const uint32_t end = GetScaledTimer();
const uint32_t platformTicksTakenByCode = RescaleDuration(end - start);
和
uint32_t GetScaledTimer()
{
return GetSomePlatformSpecificTimer() << 32-COUNTER_WIDTH;
}
uint32_t RescaleDuration(uint32_t d)
{
return d >> 32-COUNTER_WIDTH;
}
然后,您将获得与全角计时器大致相同的行为,并在必要时使用相同的选项来使用签名类型。