我有一个角色,应该“吃”200微秒,“睡”200微秒,重复,直到他们死,如果他们没有吃time_to_die
微秒。
在下面的函数main
的代码片段中,结构time_to_die
有一个配置为1000微秒的成员tv_usec
,我希望它永远循环。
一段时间后,执行一次函数busy_wait
所需的时间大约是预期的5倍(足以杀死角色),角色死亡。我想知道为什么以及如何解决它。
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct timeval time_add_microseconds(struct timeval time, long long microseconds)
{
time.tv_usec += microseconds;
while (time.tv_usec >= 1000000)
{
time.tv_sec += 1;
time.tv_usec -= 1000000;
}
return (time);
}
short time_compare(struct timeval time_one, struct timeval time_two)
{
if (time_one.tv_sec != time_two.tv_sec)
{
if (time_one.tv_sec > time_two.tv_sec)
return (1);
else
return (-1);
}
else
{
if (time_one.tv_usec > time_two.tv_usec)
return (1);
else if (time_one.tv_usec == time_two.tv_usec)
return (0);
else
return (-1);
}
}
// Wait until interval in microseconds has passed or until death_time is reached.
void busy_wait(int interval, struct timeval last_eaten_time, struct timeval time_to_die)
{
struct timeval time;
struct timeval end_time;
struct timeval death_time;
gettimeofday(&time, NULL);
end_time = time_add_microseconds(time, interval);
death_time = time_add_microseconds(last_eaten_time, time_to_die.tv_sec * 1000000ULL + time_to_die.tv_usec);
while (time_compare(time, end_time) == -1)
{
gettimeofday(&time, NULL);
if (time_compare(time, death_time) >= 0)
{
printf("%llu died\n", time.tv_sec * 1000000ULL + time.tv_usec);
exit(1);
}
}
}
int main(void)
{
struct timeval time;
struct timeval time_to_die = { .tv_sec = 0, .tv_usec = 1000};
struct timeval last_eaten_time = { .tv_sec = 0, .tv_usec = 0 };
while (true)
{
gettimeofday(&time, NULL);
printf("%llu eating\n", time.tv_sec * 1000000ULL + time.tv_usec);
last_eaten_time = time;
busy_wait(200, last_eaten_time, time_to_die);
gettimeofday(&time, NULL);
printf("%llu sleeping\n", time.tv_sec * 1000000ULL + time.tv_usec);
busy_wait(200, last_eaten_time, time_to_die);
}
}
注意:除了我已经在代码中使用的系统函数之外,我只允许使用uscript、write、malloc和free。
感谢您抽出宝贵的时间。
注意:我把毫秒和微秒弄混了,这就是沿着的问题...
4条答案
按热度按时间cmssoen21#
一段时间后,执行一次函数忙碌_wait所需的时间大约是预期的5倍(足以杀死角色),角色死亡。我想知道为什么以及如何解决它。
有多种可能性。他们中的许多人都围绕着这样一个事实,即在程序运行时,您的计算机中有更多的事情正在进行,而不仅仅是程序正在运行。除非你运行在实时操作系统上,否则底线是你不能修复一些可能导致这种行为的东西。
例如,您的程序与系统本身以及在其上运行的所有其他进程共享CPU。这可能比你想象的要多:现在,在我的6核工作站上有400多个活动进程。当需要CPU时间的进程多于运行它们的CPU时,系统将在竞争的进程之间分配可用时间,当它们的回合到期时抢先挂起进程。
如果您的程序在忙碌等待期间碰巧被抢占,那么在CPU上的下一次调度之前,很可能会经过200 μs以上的墙时间。时间片的大小通常以毫秒为单位,在通用操作系统上,从一个程序结束到下一个程序开始之间的时间没有上限(或下限)。
正如我在评论中所做的那样,我观察到您正在使用
gettimeofday
来测量运行时间,但这并不在允许的系统函数列表中。这种不一致性的一个可能解决方案是,您不应该对运行时间进行 * 测量 *,而是假设/模拟。例如,usleep()
* 在列表中,所以也许您应该使用usleep()
而不是忙碌wait,并假设睡眠时间正好是请求的时间。或者,您可能只需要调整一个内部时间计数器,而不是实际暂停执行。64jmpszr2#
为什么
最终:因为中断或陷阱被传递到执行程序的CPU核心,这将控制权转移到操作系统。
一些常见的原因:
1.操作系统使用定期触发的硬件定时器运行其进程调度。也就是说,操作系统正在运行某种公平的调度程序,它必须检查你的进程的时间是否到了。
1.您系统中的某些设备需要维修。例如,一个数据包通过网络到达,您的声卡的输出缓冲区正在运行低,必须重新填充,等等。
1.你的程序主动向操作系统发出请求,将控制权转移给它。基本上:每当你进行系统调用时,内核可能必须等待I/O,或者它可能决定是时候调度一个不同的进程了,或者两者兼而有之。在您的例子中,对
printf
的调用将在某个时候导致write(2)系统调用,该系统调用将最终执行一些I/O。怎么办
原因3可以通过确保不进行系统调用来避免,即永远不会陷入操作系统。
原因1和2是 * 非常 * 难以完全摆脱。你基本上是在寻找一个实时操作系统(RTOS)。像Linux这样的操作系统可以通过将进程放置在不同的调度域(
SCHED_FIFO
/SCHED_RR
)中来近似于此。如果您愿意切换到针对实时应用程序定制的内核,您可以走得更远。您还可以查看“CPU隔离”之类的主题。nnsrf1az3#
为了说明printf,同时也为了说明注解中提到的gettimeofday计时,我尝试了两件事
所以它在这里所做的只是循环5000次printf/gettimeofday迭代。然后测量(循环后)平均值,最小值和最大值。
在我的X11终端(Sakura)上,平均值为8 μs/循环,最小值为1 μs,最大值为3790 μs!(我做的其他测量表明,这3000 μs左右也是唯一一个超过200 μs的。换句话说,它永远不会超过200 μs。除了“大”的时候。平均来说,一切都很顺利。但是偶尔,一个printf需要将近4毫秒(这是不够的,它不会连续发生几次,人类用户甚至注意到它。但是这远远超过了使你的代码失败的需要)。
在我的控制台(没有X11)终端(一个80 x25终端,可能会,也可能不会使用我的图形卡的文本模式,我从来没有确定),平均值是272 μs,最小值193 μs,最大值是1100 μs。这一点(追溯)并不奇怪。这个终端是缓慢的,但更简单,所以不太容易“惊喜”。但是,它失败得更快,因为超过200 μs的概率非常高,即使不是很长时间,也有一半以上的环路需要超过200 μs。
我还尝试了在没有printf的循环上进行测量。
所以,它测量的东西,我可以傲慢地称之为“对数直方图”的时间。这一次,独立于终端(我把旧回到0每次我打印的东西,使这些时间不计)
结果
所以,可以肯定的是,99.98%的情况下,gettimeofday所需的时间不到10μs。但是,每百万次调用3次(这意味着,在你的代码中,只有几秒钟),它需要超过100μs。在我的实验中,有两次花费了超过1000 μs。只是获取timeofday,而不是printf。
显然,gettimeofday并没有花费1 ms。但简单地说,我的系统上发生了一些更重要的事情,该进程必须等待1 ms才能从调度程序获得一些CPU时间。
别忘了这是在我的电脑上。在我的计算机上,你的代码运行良好(好吧,这些测量表明,如果我让它运行,只要我让这些测量运行,它最终会失败)。
在您的计算机上,这些数字(2 >1000)肯定要多得多,所以它很快就会失败。
抢占式多任务操作系统根本不能保证以微秒为单位的执行时间。你必须使用一个真实的时间操作系统(例如RT-linux)。不管怎么说,如果它存在的话--我从2002年起就没有用过它了)。
wn9m85ua4#
正如在其他答案中指出的那样,在我的限制范围内,如果不对代码的设计进行重大更改,就无法使代码按我预期的那样工作。因此,我修改了代码,不再依赖
gettimeofday
来确定哲学家是否死亡,或者确定要打印的时间值。相反,我只是在每次我的角色吃饭/睡觉时向time
添加200 μs。这感觉就像一个廉价的把戏。因为虽然在开始时我显示正确的系统壁时间,但随着程序的运行,我的时间变量将越来越不同于系统时间,但我想这就是我想要的。