c中的忙碌等待时间不一致

wnavrhmk  于 2023-10-16  发布在  其他
关注(0)|答案(4)|浏览(120)

我有一个角色,应该“吃”200微秒,“睡”200微秒,重复,直到他们死,如果他们没有吃time_to_die微秒。
在下面的函数main的代码片段中,结构time_to_die有一个配置为1000微秒的成员tv_usec,我希望它永远循环。
一段时间后,执行一次函数busy_wait所需的时间大约是预期的5倍(足以杀死角色),角色死亡。我想知道为什么以及如何解决它。

  1. #include <sys/time.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. struct timeval time_add_microseconds(struct timeval time, long long microseconds)
  6. {
  7. time.tv_usec += microseconds;
  8. while (time.tv_usec >= 1000000)
  9. {
  10. time.tv_sec += 1;
  11. time.tv_usec -= 1000000;
  12. }
  13. return (time);
  14. }
  15. short time_compare(struct timeval time_one, struct timeval time_two)
  16. {
  17. if (time_one.tv_sec != time_two.tv_sec)
  18. {
  19. if (time_one.tv_sec > time_two.tv_sec)
  20. return (1);
  21. else
  22. return (-1);
  23. }
  24. else
  25. {
  26. if (time_one.tv_usec > time_two.tv_usec)
  27. return (1);
  28. else if (time_one.tv_usec == time_two.tv_usec)
  29. return (0);
  30. else
  31. return (-1);
  32. }
  33. }
  34. // Wait until interval in microseconds has passed or until death_time is reached.
  35. void busy_wait(int interval, struct timeval last_eaten_time, struct timeval time_to_die)
  36. {
  37. struct timeval time;
  38. struct timeval end_time;
  39. struct timeval death_time;
  40. gettimeofday(&time, NULL);
  41. end_time = time_add_microseconds(time, interval);
  42. death_time = time_add_microseconds(last_eaten_time, time_to_die.tv_sec * 1000000ULL + time_to_die.tv_usec);
  43. while (time_compare(time, end_time) == -1)
  44. {
  45. gettimeofday(&time, NULL);
  46. if (time_compare(time, death_time) >= 0)
  47. {
  48. printf("%llu died\n", time.tv_sec * 1000000ULL + time.tv_usec);
  49. exit(1);
  50. }
  51. }
  52. }
  53. int main(void)
  54. {
  55. struct timeval time;
  56. struct timeval time_to_die = { .tv_sec = 0, .tv_usec = 1000};
  57. struct timeval last_eaten_time = { .tv_sec = 0, .tv_usec = 0 };
  58. while (true)
  59. {
  60. gettimeofday(&time, NULL);
  61. printf("%llu eating\n", time.tv_sec * 1000000ULL + time.tv_usec);
  62. last_eaten_time = time;
  63. busy_wait(200, last_eaten_time, time_to_die);
  64. gettimeofday(&time, NULL);
  65. printf("%llu sleeping\n", time.tv_sec * 1000000ULL + time.tv_usec);
  66. busy_wait(200, last_eaten_time, time_to_die);
  67. }
  68. }

注意:除了我已经在代码中使用的系统函数之外,我只允许使用uscript、write、malloc和free。
感谢您抽出宝贵的时间。
注意:我把毫秒和微秒弄混了,这就是沿着的问题...

cmssoen2

cmssoen21#

一段时间后,执行一次函数忙碌_wait所需的时间大约是预期的5倍(足以杀死角色),角色死亡。我想知道为什么以及如何解决它。
有多种可能性。他们中的许多人都围绕着这样一个事实,即在程序运行时,您的计算机中有更多的事情正在进行,而不仅仅是程序正在运行。除非你运行在实时操作系统上,否则底线是你不能修复一些可能导致这种行为的东西。
例如,您的程序与系统本身以及在其上运行的所有其他进程共享CPU。这可能比你想象的要多:现在,在我的6核工作站上有400多个活动进程。当需要CPU时间的进程多于运行它们的CPU时,系统将在竞争的进程之间分配可用时间,当它们的回合到期时抢先挂起进程。
如果您的程序在忙碌等待期间碰巧被抢占,那么在CPU上的下一次调度之前,很可能会经过200 μs以上的墙时间。时间片的大小通常以毫秒为单位,在通用操作系统上,从一个程序结束到下一个程序开始之间的时间没有上限(或下限)。
正如我在评论中所做的那样,我观察到您正在使用gettimeofday来测量运行时间,但这并不在允许的系统函数列表中。这种不一致性的一个可能解决方案是,您不应该对运行时间进行 * 测量 *,而是假设/模拟。例如,usleep() * 在列表中,所以也许您应该使用usleep()而不是忙碌wait,并假设睡眠时间正好是请求的时间。或者,您可能只需要调整一个内部时间计数器,而不是实际暂停执行。

64jmpszr

64jmpszr2#

为什么
最终:因为中断或陷阱被传递到执行程序的CPU核心,这将控制权转移到操作系统。
一些常见的原因:
1.操作系统使用定期触发的硬件定时器运行其进程调度。也就是说,操作系统正在运行某种公平的调度程序,它必须检查你的进程的时间是否到了。
1.您系统中的某些设备需要维修。例如,一个数据包通过网络到达,您的声卡的输出缓冲区正在运行低,必须重新填充,等等。
1.你的程序主动向操作系统发出请求,将控制权转移给它。基本上:每当你进行系统调用时,内核可能必须等待I/O,或者它可能决定是时候调度一个不同的进程了,或者两者兼而有之。在您的例子中,对printf的调用将在某个时候导致write(2)系统调用,该系统调用将最终执行一些I/O。
怎么办
原因3可以通过确保不进行系统调用来避免,即永远不会陷入操作系统。
原因1和2是 * 非常 * 难以完全摆脱。你基本上是在寻找一个实时操作系统(RTOS)。像Linux这样的操作系统可以通过将进程放置在不同的调度域(SCHED_FIFO/SCHED_RR)中来近似于此。如果您愿意切换到针对实时应用程序定制的内核,您可以走得更远。您还可以查看“CPU隔离”之类的主题。

nnsrf1az

nnsrf1az3#

为了说明printf,同时也为了说明注解中提到的gettimeofday计时,我尝试了两件事

  1. #include <sys/time.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. int main(void)
  6. {
  7. struct timeval time;
  8. long long histo[5000];
  9. for(int i=0; i<5000; i++){
  10. gettimeofday(&time, NULL);
  11. histo[i]=time.tv_sec * 1000000ULL + time.tv_usec;
  12. }
  13. long long min=1000000000;
  14. long long max=0;
  15. for(int i=1; i<5000; i++){
  16. long long dt=histo[i]-histo[i-1];
  17. if(dt<min) min=dt;
  18. if(dt>max) max=dt;
  19. if(dt>800) printf("%d %lld\n", i, dt);
  20. }
  21. printf("avg: %f min=%lld max=%lld\n", (histo[4999]-histo[0])/5000.0, min, max);
  22. }

所以它在这里所做的只是循环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的循环上进行测量。

  1. #include <sys/time.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. int main(void)
  6. {
  7. struct timeval time;
  8. long long old=0;
  9. long long ntot=0;
  10. long long nov10=0;
  11. long long nov100=0;
  12. long long nov1000=0;
  13. for(int i=0;;i++){
  14. gettimeofday(&time, NULL);
  15. long long t=time.tv_sec * 1000000ULL + time.tv_usec;
  16. if(old){
  17. long long dt=t-old;
  18. ntot++;
  19. if(dt>10){
  20. nov10++;
  21. if(dt>100){
  22. nov100++;
  23. if(dt>1000) nov1000++;
  24. }
  25. }
  26. }
  27. if(i%10000==0){
  28. printf("tot=%lld >10=%lld >100=%lld >1000=%lld\n", ntot, nov10, nov100, nov1000);
  29. old=0;
  30. }else{
  31. old=t;
  32. }
  33. }
  34. }

所以,它测量的东西,我可以傲慢地称之为“对数直方图”的时间。这一次,独立于终端(我把旧回到0每次我打印的东西,使这些时间不计)
结果

  1. tot=650054988 >10=130125 >100=2109 >1000=2

所以,可以肯定的是,99.98%的情况下,gettimeofday所需的时间不到10μs。但是,每百万次调用3次(这意味着,在你的代码中,只有几秒钟),它需要超过100μs。在我的实验中,有两次花费了超过1000 μs。只是获取timeofday,而不是printf。
显然,gettimeofday并没有花费1 ms。但简单地说,我的系统上发生了一些更重要的事情,该进程必须等待1 ms才能从调度程序获得一些CPU时间。
别忘了这是在我的电脑上。在我的计算机上,你的代码运行良好(好吧,这些测量表明,如果我让它运行,只要我让这些测量运行,它最终会失败)。
在您的计算机上,这些数字(2 >1000)肯定要多得多,所以它很快就会失败。
抢占式多任务操作系统根本不能保证以微秒为单位的执行时间。你必须使用一个真实的时间操作系统(例如RT-linux)。不管怎么说,如果它存在的话--我从2002年起就没有用过它了)。

展开查看全部
wn9m85ua

wn9m85ua4#

正如在其他答案中指出的那样,在我的限制范围内,如果不对代码的设计进行重大更改,就无法使代码按我预期的那样工作。因此,我修改了代码,不再依赖gettimeofday来确定哲学家是否死亡,或者确定要打印的时间值。相反,我只是在每次我的角色吃饭/睡觉时向time添加200 μs。这感觉就像一个廉价的把戏。因为虽然在开始时我显示正确的系统壁时间,但随着程序的运行,我的时间变量将越来越不同于系统时间,但我想这就是我想要的。

  1. #include <sys/time.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. struct timeval time_add_microseconds(struct timeval time, long long microseconds)
  6. {
  7. time.tv_usec += microseconds;
  8. while (time.tv_usec >= 1000000)
  9. {
  10. time.tv_sec += 1;
  11. time.tv_usec -= 1000000;
  12. }
  13. return (time);
  14. }
  15. short time_compare(struct timeval time_one, struct timeval time_two)
  16. {
  17. if (time_one.tv_sec != time_two.tv_sec)
  18. {
  19. if (time_one.tv_sec > time_two.tv_sec)
  20. return (1);
  21. else
  22. return (-1);
  23. }
  24. else
  25. {
  26. if (time_one.tv_usec > time_two.tv_usec)
  27. return (1);
  28. else if (time_one.tv_usec == time_two.tv_usec)
  29. return (0);
  30. else
  31. return (-1);
  32. }
  33. }
  34. bool is_destined_to_die(int interval, struct timeval current_time, struct timeval last_eaten_time, struct timeval time_to_die)
  35. {
  36. current_time = time_add_microseconds(current_time, interval);
  37. if ((current_time.tv_sec * 1000000ULL + current_time.tv_usec) - (last_eaten_time.tv_sec * 1000000ULL + last_eaten_time.tv_usec) >= time_to_die.tv_sec * 1000000ULL + time_to_die.tv_usec)
  38. return (true);
  39. else
  40. return (false);
  41. }
  42. // Wait until interval in microseconds has passed or until death_time is reached.
  43. void busy_wait(int interval, struct timeval current_time, struct timeval last_eaten_time, struct timeval time_to_die)
  44. {
  45. struct timeval time;
  46. struct timeval end_time;
  47. struct timeval death_time;
  48. gettimeofday(&time, NULL);
  49. if (is_destined_to_die(interval, current_time, last_eaten_time, time_to_die))
  50. {
  51. death_time = time_add_microseconds(last_eaten_time, time_to_die.tv_sec * 1000000 + time_to_die.tv_usec);
  52. while (time_compare(time, death_time) == -1)
  53. gettimeofday(&time, NULL);
  54. printf("%llu died\n", time.tv_sec * 1000000ULL + time.tv_usec);
  55. exit(1);
  56. }
  57. end_time = time_add_microseconds(time, interval);
  58. while (time_compare(time, end_time) == -1)
  59. gettimeofday(&time, NULL);
  60. }
  61. int main(void)
  62. {
  63. struct timeval time;
  64. struct timeval time_to_die = { .tv_sec = 0, .tv_usec = 1000};
  65. struct timeval last_eaten_time = { .tv_sec = 0, .tv_usec = 0 };
  66. gettimeofday(&time, NULL);
  67. while (true)
  68. {
  69. printf("%llu eating\n", time.tv_sec * 1000000ULL + time.tv_usec);
  70. last_eaten_time = time;
  71. busy_wait(200, time, last_eaten_time, time_to_die);
  72. time = time_add_microseconds(time, 200);
  73. printf("%llu sleeping\n", time.tv_sec * 1000000ULL + time.tv_usec);
  74. busy_wait(200, time, last_eaten_time, time_to_die);
  75. time = time_add_microseconds(time, 200);
  76. }
  77. }
展开查看全部

相关问题