文章目录
0. 引言
在嵌入式系统开发中,性能是关键的考虑因素之一。本文将深入分析ARM开发板的访存性能,并探讨访存如何影响CPU性能。
前置文章: 如何使用perf 统计cpu和内存?
测试ARM开发板CPU基本参数
4 cortex-A53
L1i=32KB L1d=32KB
L2=1MB shared
访存性能对CPU性能的统计方法
perf
是Linux上一个强大的性能分析工具,可以用来收集硬件性能计数器的数据。
- l2d_cache_refill 和 l2d_cache_wb 通常通过L2缓存控制器中的计数器进行测量。这些计数器会跟踪从L2缓存读取和写入数据的次数。
- cycles 通常通过CPU时钟计数器进行测量。该计数器会跟踪CPU执行的指令周期数。
- insts 通常通过指令计数器进行测量。该计数器会跟踪执行的指令总数。
- IPC 可以通过以下公式计算:
IPC = insts / cycles
- 访存/指令 可以通过以下公式计算:
访存/指令 = (l2d_cache_refill + l2d_cache_wb) / insts
示例
以下是一个使用perf
工具测量上述性能指标的示例:
perf stat -e l2d_cache_refill,l2d_cache_wb,cycles,instructions -p <pid>
其中,<pid>
是要分析的进程的ID。该命令将输出以下结果:
l2d_cache_refill l2d_cache_wb cycles instructions IPC
14846205.00 23929355.00 7457445631.00 5885430181.00 0.79
您可以使用这些结果来计算IPC和访存/指令。
1. 测试代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#define CACHE_LINE_SIZE 64 // 缓存行大小,通常为64字节
typedef struct {
uint64_t begin; // 起始地址
uint64_t end; // 结束地址
uint32_t step; // 每次读取的跨度(以缓存行大小为单位)
} test_config_t;
void test_access(test_config_t *config) {
uint64_t *ptr = (uint64_t *)config->begin;
uint64_t size = config->end - config->begin;
uint32_t step = config->step;
clock_t start = clock();
for (uint64_t i = 0; i < size; i += step * CACHE_LINE_SIZE) {
uint64_t value = *ptr; // 模拟访存操作
ptr += step;
}
clock_t end = clock();
double duration = (double)(end - start) / CLOCKS_PER_SEC;
printf("Test config: begin = 0x%llx, end = 0x%llx, step = %u\n", config->begin, config->end, config->step);
printf("Access time: %.3f seconds\n", duration);
printf("Throughput: %.3f bytes/second\n", (double)size / duration);
}
int main() {
// 测试不同访存模式
test_config_t configs[] = {
{0x10000000, 0x10100000, 1}, // step = 1
{0x10000000, 0x10100000, 2}, // step = 2
{0x10000000, 0x10100000, 10}, // step = 10
{0x10000000, 0x10100000, 6 + rand() % 5}, // step = random(6, 10)
};
for (int i = 0; i < sizeof(configs) / sizeof(test_config_t); i++) {
test_access(&configs[i]);
}
return 0;
}
说明
- 该代码定义了一个
test_config_t
结构体,用于存储测试配置信息,包括起始地址、结束地址和每次读取的跨度。 test_access
函数用于执行访问测试。它首先记录开始时间,然后循环读取数据,并记录结束时间。最后,计算访问时间、吞吐率并打印结果。main
函数定义了四个测试配置,并分别调用test_access
函数进行测试。
如何使用
- 将代码保存为
test.c
文件。 - 使用GCC编译器编译代码:
gcc -o test test.c
- 运行测试程序:
./test
在不设置CPU主频时的执行结果
Test config: begin = 0x10000000, end = 0x10100000, step = 1
Access time: 0.097 seconds
Throughput: 1073741824.000 bytes/second
Test config: begin = 0x10000000, end = 0x10100000, step = 2
Access time: 0.049 seconds
Throughput: 2147483648.000 bytes/second
Test config: begin = 0x10000000, end = 0x10100000, step = 10
Access time: 0.010 seconds
Throughput: 1073741824.000 bytes/second
Test config: begin = 0x10000000, end = 0x10100000, step = 7
Access time: 0.070 seconds
Throughput: 1536000000.000 bytes/second
1.2 执行结果解释
- Test config: begin = 0x10000000, end = 0x10100000, step = 1
- Access time : 0.097 seconds
- Throughput : 1073741824.000 bytes/second
- 解释 : 由于步长为1,访问模式是顺序的,缓存预取机制有效,所以访问时间较短,吞吐量较高。
- Test config: begin = 0x10000000, end = 0x10100000, step = 2
- Access time : 0.049 seconds
- Throughput : 2147483648.000 bytes/second
- 解释 : 步长为2,访问模式仍然较为顺序,缓存预取机制仍有效,所以访问时间更短,吞吐量更高。
- Test config: begin = 0x10000000, end = 0x10100000, step = 10
- Access time : 0.010 seconds
- Throughput : 1073741824.000 bytes/second
- 解释 : 步长为10,访问模式变得不那么顺序,缓存预取机制失效,导致访问时间更短,但吞吐量反而下降,因为在大步长情况下,CPU利用率可能较低。
- Test config: begin = 0x10000000, end = 0x10100000, step = 7
- Access time : 结果未完整显示
- 解释 : 由于步长为随机数(6到10之间),缓存预取机制可能无法生效,导致访问时间不确定,但可以预期较高的随机性会降低缓存效率,增加访问时间,降低吞吐量。
2. (设置CPU主频) ARM开发板访存性能测试
基于如上的测试方法,真实的业务线程在ARM开发板上测试访存性能
测试方法
- 循环读写一个超大数组(总大小超过L2 cache大小),每次读取跨度为N个cache line大小。
- 测试不同访存模式(step=1, step=2, step=10, step=random(6,9))和CPU主频(1.2GHz和0.6GHz)下的性能。
平台 \ 访存方式 | step=1 | step=2 | step=10 | step=rand(5,9) |
---|---|---|---|---|
ARM-APP | 0.12 | 0.1 | 0.02 | 0.02 |
测试结果
- 当N=1或2时,由于cache预取机制生效,IPC(指令每周期)可以达到0.1以上。
- 当N增加到10或随机范围时,cache预取机制失效,IPC显著降低至0.02。
- 降低CPU主频,访存相对开销减少,CPI降低,IPC提高。
- 访存/指令比越高,对应的IPC越低,反映了访存操作对CPU性能的影响。
结论与模拟的测试程序一致
3. (设置CPU主频) ARM-APP主要工作线程与访存频率的关系
以下线程1、线程2、线程3为实际的业务线程,代码无法贴出
线程1 | 线程2 | 线程3 | |
---|---|---|---|
l2d_cache_refill | 14846205.00 | 23929355.00 | 21485354.00 |
l2d_cache_wb | 4906288.00 | 7647479.00 | 6381314.00 |
cycles | 7457445631.00 | 5532393037.00 | 4712356772.00 |
insts | 5885430181.00 | 2863458124.00 | 2592726756.00 |
IPC | 0.79 | 0.52 | 0.55 |
访存/指令 | 0.3% | 1.1% | 1.1% |
以上数据展示了ARM-APP的三个主要工作线程与它们的访存频率和性能之间的关系。下面是对文档中各项数据的解释:
- l2d_cache_refill:表示L2数据缓存(Data Cache)的重填(refill)次数。当处理器尝试从L2缓存中读取数据但数据不在缓存中时,就会发生缓存重填。这个值越高,说明缓存未命中的次数越多,需要从主存中读取数据的次数也越多。
- l2d_cache_wb:表示L2数据缓存的写回(writeback)次数。当处理器修改了缓存中的数据,并且这些修改需要被写回到主存时,就会发生写回。
- cycles:表示处理器执行的时钟周期数。这个值反映了线程执行的总时间。
- insts(instructions):表示线程执行的指令数。这个值反映了线程的工作负荷。
- IPC(Instructions Per Cycle):表示每个时钟周期执行的指令数,计算公式为
insts / cycles
。这个值越高,表示处理器的效率越高。 - 访存/指令:表示每条指令的访存操作数,计算公式为
(l2d_cache_refill + l2d_cache_wb) / insts
。这个值越高,说明访存操作的频率越高。
3.1 线程1
- L2缓存重填次数和写回次数相对较低。
- 时钟周期数最高,但执行的指令数也是最多的。
- IPC(0.79)相对较高,说明线程1的处理效率较高。
- 访存/指令比率为0.3%,表示每1000条指令中大约有3次访存操作。较低的访存频率表明线程1能够更有效地利用缓存,减少了对主存的访问次数。
3.2 线程2和线程3
- L2缓存重填次数和写回次数相对较高,表明这两个线程的访存操作更多。
- 时钟周期数和执行的指令数较少。
- IPC分别为0.52和0.55,说明处理效率较低。
- 访存/指令比率为1.1%,表示每1000条指令中大约有11次访存操作。较高的访存频率表明线程2和线程3更依赖于主存访问,可能因为缓存未命中率较高,从而降低了处理效率。
3.3 本段总结
线程1的访存频率较低,表明它能够更有效地利用CPU缓存,减少了对主存的访问次数,因此处理效率较高(IPC较高)。线程2和线程3的访存频率较高,表明它们对主存访问的依赖度较高,CPU缓存未命中率较高,因此处理效率较低(IPC较低)。访存操作对CPU性能有显著影响,频繁的访存操作会导致处理器的效率降低。
4 总结
通过分析不同访存模式下的性能数据,可以得出以下结论,对于我测试的Cortex-A53 ARM开发板:
- Cache预取的影响:
- 当访问模式较为顺序(step=1,2)时,cache预取机制生效,访存效率较高,IPC较高。
- 当访问模式较为随机(step=10, rand)时,cache预取机制失效,访存效率较低,IPC较低。
- CPU主频的影响:
- 降低CPU主频会减小访存操作的相对开销,从而提高IPC。
- 低主频下的访存操作对性能的影响相对较小。
- 访存/指令比率的影响:
- 访存/指令比率越高,IPC越低,反映了访存操作对CPU性能的负面影响。
- 优化访存操作(例如通过数据局部性优化)可以显著提高CPU性能。
综合本次测试和分析,我们可以得出以下结论:
- 访存性能对ARM开发板CPU性能有显著影响。
- 合理优化访存模式和利用cache预取机制可以提高CPU性能。
- 降低CPU主频可以减小访存操作的相对开销,从而提高CPU效率。
- 进一步的优化可以通过减少访存操作频率和优化数据局部性来实现。
在实际应用中,针对特定场景进行访存模式优化,选择合适的CPU主频,可以显著提升系统的整体性能。因此,若想充分发挥高主频CPU的优势,足够大的CPU缓存也是有必要的。