硬汉嵌入式论坛

 找回密码
 立即注册
查看: 424|回复: 12
收起左侧

[Cache] STM32H7 Cache缓存命中率

[复制链接]

2

主题

8

回帖

14

积分

新手上路

积分
14
发表于 2025-6-24 10:00:28 | 显示全部楼层 |阅读模式
使用STM32H750VBT6,将其超频至600Mhz,项目开启了D-Cache和I-Cache,同时将D1域配置为WT,D2域配置为WBWA;
具体程序如下:



void MPU_Config(void)
{
  MPU_Region_InitTypeDef MPU_InitStruct = {0};

  /* Disables the MPU */
  HAL_MPU_Disable();

  /** Initializes and configures the Region and the memory to be protected
  */
    //TEX = 1, C = 1, B = 1,    //Write back, write and read allocate
    //TEX = 1, C = 0, B = x,    //no cacheable
    //TEX = 0, C = 1, B = 1,    //Write back, no write allocate
    //TEX = 0, C = 1, B = 0,    //Write through, no write allocate
    //最高性能
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;        //使能该保护区域
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;    //设置保护区域大小
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;   //设置访问权限,
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;        //是否缓冲? B = 0
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;     //是否cache?  C= 1
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;     //是否共用? S = 0
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;       //设置保护区域
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;           //设置类型扩展域为level0
    MPU_InitStruct.SubRegionDisable = 0x00;                 //禁止子区域
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;        //允许指令访问(允许读取指令)

    HAL_MPU_ConfigRegion(&MPU_InitStruct);           //配置MPU
   
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;        //使能该保护区域
    MPU_InitStruct.BaseAddress      = 0x30000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB; //设置保护区域大小
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;   //设置访问权限,
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;        //是否缓冲? B = 1
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;     //是否cache?  C=1
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;     //是否共用? S = 0
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;       //设置保护区域
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;           //设置类型扩展域为level1, TEX = 1
    MPU_InitStruct.SubRegionDisable = 0x00;                 //禁止子区域
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;        //允许指令访问(允许读取指令)
   
    HAL_MPU_ConfigRegion(&MPU_InitStruct);           //配置MPU


  /** Initializes and configures the Region and the memory to be protected
  */
  /* Enables the MPU */
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

}


#define     CH_NUM          6       //DMA采样的通道数
#define FIFO_DBS_0      1000        //每个DMA的传输长度

ALIGN_32BYTES(__attribute__((section (".RAM_D2"))) uint8_t src_fifo[CH_NUM][FIFO_DBS_0]);       //DMA传输的目标地址
ALIGN_32BYTES(__attribute__((section (".RAM_D1"))) uint8_t fifo_data[CH_NUM * FIFO_DBS_0]);     //用于传输完成后的数据整合

uint8_t tc_flag = 0;    //DMA传输完成标志
uint32_t ca_cycles = 0;     //算法效率

/*
*********************************************************************************************************
*   函 数 名: Copy_Fifo
*   功能说明: 将DMA 6个通道的数据排序
*   形    参: 无
*   返 回 值: 无
*********************************************************************************************************
*/
void    Copy_Fifo(void)
{
    unsigned int inc = 0;
   
    if(tc_flag == 1)        //传输结束,可以处理数据
    {
    //对DMA传输的内存数据做无效化处理
        SCB_InvalidateDCache_by_Addr((uint32_t *)src_fifo, FIFO_DBS_0 * CH_NUM);

        uint32_t start = DWT->CYCCNT;       //记录起始周期
      
        for(int i = 0; i < FIFO_DBS_0 ; i ++ )
        {
            for(uint16_t j =0; j<6; j++)
            {
                fifo_data[inc] = src_fifo[j];
                inc ++;
            }
        }
        tc_flag = 0;        //数据处理完成,开始下一次DMA传输
      
        uint32_t end = DWT->CYCCNT;     //记录结束周期
        ca_cycles = end - start;        //计算消耗周期数
        ca_cycles = ca_cycles / 600;    //换算成时间(单位:微秒),
      
    }
}


项目配置只勾选了IRAM1
其中,数组src_fifo位于D2域,用于存放读取到的GPIO口的数据,数组fifo_data位于D1域,用于6组DMA数据按照顺序存放在main函数中执行Copy_Fifo()。在执行Copy_Fifo的时候,DMA这些是已经关闭了的,只有一个计时用的定时器在运行,在数据处理完毕后会在这个定时器的UPDATE中断中打开DMA,开始下一次的采样。
理论上,ca_cycles的值最差的情况下应该是40~50us左右,但是实际测量这个时间是139us;我尝试过将一下关键的代码放在ITCM中,效果也一样。
当我把Cache关闭之后,ca_cycles的值为250us左右,也就是说目前这个139us的时间是cache基本没命中导致的,为什么会到这么高的miss情况呢,要如何提高命中率?
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
116739
QQ
发表于 2025-6-24 16:08:42 | 显示全部楼层
H750的DCache比较小,只有16KB.
回复

使用道具 举报

2

主题

8

回帖

14

积分

新手上路

积分
14
 楼主| 发表于 2025-6-25 11:49:33 | 显示全部楼层
eric2013 发表于 2025-6-24 16:08
H750的DCache比较小,只有16KB.

搬运的buffer是6K,所以没有超过DCache的大小吧
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
116739
QQ
发表于 2025-6-26 09:44:17 | 显示全部楼层
cline 发表于 2025-6-25 11:49
搬运的buffer是6K,所以没有超过DCache的大小吧

不仅这里,你的Cache应该还缓冲其他位置数据了。
回复

使用道具 举报

2

主题

8

回帖

14

积分

新手上路

积分
14
 楼主| 发表于 4 天前 | 显示全部楼层
eric2013 发表于 2025-6-26 09:44
不仅这里,你的Cache应该还缓冲其他位置数据了。

请问,如何查找其他位置是否被Cache缓冲了呢
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
116739
QQ
发表于 3 天前 | 显示全部楼层
cline 发表于 2025-6-30 16:32
请问,如何查找其他位置是否被Cache缓冲了呢

这个看不到,

可以尝试让你的总RAM使用小于16KB,效果就出来了
回复

使用道具 举报

5

主题

252

回帖

267

积分

高级会员

积分
267
发表于 3 天前 | 显示全部楼层
没开优化吧?两层for循环吃了太多时间了,改成while会好很多
回复

使用道具 举报

5

主题

252

回帖

267

积分

高级会员

积分
267
发表于 3 天前 | 显示全部楼层
这个问题看起来是cache命中率问题,实际上是代码效率问题,两层for循环里面就只有两行代码,就算100%cache命中率,也有大量内核周期耗在for循环指令里了
你这个完全可以改成memcpy,比两层for循环一个个byte赋值快多啦
回复

使用道具 举报

5

主题

252

回帖

267

积分

高级会员

积分
267
发表于 3 天前 | 显示全部楼层
DX3906 发表于 2025-7-1 11:36
这个问题看起来是cache命中率问题,实际上是代码效率问题,两层for循环里面就只有两行代码,就算100%ca ...

看错,这个不能用memcpy,不过也可以优化,下午我整理下
回复

使用道具 举报

5

主题

252

回帖

267

积分

高级会员

积分
267
发表于 3 天前 | 显示全部楼层
本帖最后由 DX3906 于 2025-7-1 14:41 编辑

在for循环前后反转io并用逻辑分析仪测量高电平时间可以知道代码耗时,下面是我测试的结果,不过我不是在stm32上跑的,不用管时间的绝对数值,只需要看相对比例就好

先看原代码,两层for循环一共耗时233.64u:
[C] 纯文本查看 复制代码
void Copy_Fifo(void)
{
    unsigned int inc = 0;
   
    if(tc_flag == 1)
    {
        RGPIO_WritePinOutput(EXT_GPIO01_GPIO, EXT_GPIO01_GPIO_PIN, 1);
        for(int i = 0; i < FIFO_DBS_0 ; i ++ )
        {
            for(uint16_t j =0; j<6; j++)
            {
                fifo_data[inc] = src_fifo[j][i];
                inc++;
            }
        }
        RGPIO_WritePinOutput(EXT_GPIO01_GPIO, EXT_GPIO01_GPIO_PIN, 0);
        tc_flag = 0;
    }
}

Snipaste_2025-07-01_14-13-38.png




然后是改进的代码,把里面的for循环展开了,耗时64.46us:
[C] 纯文本查看 复制代码
void Copy_Fifo(void)
{
    unsigned int inc = 0;
   
    if(tc_flag == 1)
    {
        RGPIO_WritePinOutput(EXT_GPIO01_GPIO, EXT_GPIO01_GPIO_PIN, 1);
        for(int i = 0; i < FIFO_DBS_0 ; i ++ )
        {
          fifo_data[inc++] = src_fifo[0][i];
          fifo_data[inc++] = src_fifo[1][i];
          fifo_data[inc++] = src_fifo[2][i];
          fifo_data[inc++] = src_fifo[3][i];
          fifo_data[inc++] = src_fifo[4][i];
          fifo_data[inc++] = src_fifo[5][i];
        }
        RGPIO_WritePinOutput(EXT_GPIO01_GPIO, EXT_GPIO01_GPIO_PIN, 0);
        tc_flag = 0;
    }
}

Snipaste_2025-07-01_14-24-27.png



以上测试均在关掉dcache的情况下跑的,编译器0级优化
可以看到,只需要把最里层的for循环展开,就能获得74%的性能提升,所以是双层for拖慢了整体效率


回复

使用道具 举报

2

主题

8

回帖

14

积分

新手上路

积分
14
 楼主| 发表于 昨天 10:33 | 显示全部楼层
DX3906 发表于 2025-7-1 14:31
在for循环前后反转io并用逻辑分析仪测量高电平时间可以知道代码耗时,下面是我测试的结果,不过我不是在stm ...

找到原因了,谢谢!谢谢
回复

使用道具 举报

3

主题

28

回帖

37

积分

新手上路

积分
37
发表于 昨天 15:17 来自手机 | 显示全部楼层
DX3906 发表于 2025-7-1 14:31
在for循环前后反转io并用逻辑分析仪测量高电平时间可以知道代码耗时,下面是我测试的结果,不过我不是在stm ...

请问为啥双层循环会慢这么多?按理来说不应该差这么多啊?
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
116739
QQ
发表于 2 小时前 | 显示全部楼层
spi-sd 发表于 2025-7-3 15:17
请问为啥双层循环会慢这么多?按理来说不应该差这么多啊?

一个for循环等于几条赋值指令。可以用DWT时钟周期单测一下时间差异。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-7-4 11:49 , Processed in 0.580919 second(s), 27 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表