编辑
2025-08-08
记录知识
0
请注意,本文编写于 34 天前,最后修改于 34 天前,其中某些信息可能已经过时。

目录

栈预热
实施栈预热
测试程序
总结

在实时程序的应用场景中,为了代码运行的低延迟和确定性,针对栈区,也是有一定的优化小技巧的。

栈预热

程序可能因为分配栈区内存的响应延迟导致的性能颠簸,从而导致实时任务的延迟抖动。

针对此问题,在实时程序关键任务未运行之前,提前访问栈区地址空间,从而触发缺页异常,分配物理页面,并函数返回。然后当实时程序在运行关键任务的函数调用和栈区变量分配时,由于之前栈区已经被预热过了,再次访问时,无需触发缺页异常,也就不会出现因为分配内存导致的性能颠簸了。

实施栈预热

对于栈区内存,预热的办法可以提供一个栈预热函数,函数内完成如下三个步骤

  1. 分配一个很大的临时变量,占领栈区
  2. 访问这个临时变量,触发页面申请
  3. 函数返回

经过这三步,相当于强行访问了栈区的内存,提前进行了内存申请,此时如果后面的函数为实时程序关键任务,那么其再进行函数调用或者栈区变量访问时,不会触发缺页异常。从而不会产生性能颠簸。

测试程序

我们编写一个测试程序验证和说明这一点。

#include <stdlib.h> #include <stdio.h> #include <time.h> #include <string.h> #define ITERATIONS 10000 #define STACK_SIZE 1024 * 1024 static long long get_time() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ((long long)ts.tv_sec * 1000000000LL + (long long)(ts.tv_nsec)) / 1000; } void test_stack_prefetch() { char stack[STACK_SIZE]; memset(stack, 0x0, STACK_SIZE); return ; } void test_stack(int use_warmup) { double times[ITERATIONS]; double start, elapsed; char stack[STACK_SIZE]; if(use_warmup) test_stack_prefetch(); for (int i = 0; i < ITERATIONS; i++) { start = get_time(); test_stack_prefetch(); times[i] = get_time() - start; } double max, min; max = min = times[0]; for (int i = 1; i < ITERATIONS; i++) { if (times[i] < min) min = times[i]; // Min if (times[i] > max) max = times[i]; // Max } printf(" Min time: %.2f us\n", min); printf(" Max time: %.2f us\n\n", max); return ; } int main(int argc, char *argv[]) { int use_warmup = 0; if (argc == 2) { use_warmup = atoi(argv[1]);; } printf("Stack size: %d bytes, Iterations: %d Warmup: %d\n", STACK_SIZE, ITERATIONS, use_warmup); test_stack(use_warmup); return 0; }

这里函数test_stack_prefetch通过stack申请了STACK_SIZE大小的局部变量,这个变量存放在栈区,用来实施和模拟栈区的使用。

然后再通过memset将其全部设置为0,相当于全部访问了STACK_SIZE内的栈区内存,触发了缺页异常。

最后函数返回。

经过此函数的运行,从test_stack_prefetch函数的开始栈区sp 到 stack 变量地址 加上 STACK_SIZE 大小的栈区,都被访问过。

因为 test_stack_prefetch 函数会返回,栈指针会缩小,下次运行函数时,其访问的栈区地址 在上述 栈区地址范围内,因为上述 栈区地址已经提前访问过了,所以代码不会因为内存产生性能颠簸。

下面查看运行日志

# ./stack_prefetch 0 Stack size: 1048576 bytes, Iterations: 2 Warmup: 0 Min time: 79.00 us Max time: 692.00 us

可以看到,对于申请1M的栈空间,如果不经过预热,那么首次申请的时间是 692 us。 如果使用预热,那么根据上面的日志分析,应该是100us左右,下面运行验证

# ./stack_prefetch 1 Stack size: 1048576 bytes, Iterations: 2 Warmup: 1 Min time: 102.00 us Max time: 103.00 us

总结

至此,我们通过这么一个小技巧,可以保证在实时程序处理关键任务时,不会因为栈的内存分配问题导致实时任务抖动。此方法通常可以用于在实时任务运行之前进行预热,或者定期对整个栈区范围进行定期预热。