STM32運行程序到底ROM快還是RAM快?

目錄

一、前言

二、ROM和RAM程序運行速度實驗

三、預取指令技術

四、實驗分析

五、結論

六、參考資料


一、前言

最近在網上查找資料學習STM32如何將代碼搬到RAM中去運行,於是查看了一些前人的博客,介紹了KEIL的分散加載文件(sct文件)和將特定的函數定義到RAM地址的方法,然後又順便提了下在RAM中運行程序速度會有所提升,當然我一開始也是滿滿的贊同,學習嘛,總是要跟着前人的資料去做對應的實驗,才能更有助於自己的學習和消化,於是就跟着實驗一步一步來做,在實驗中發現了一些問題,於是引出了本文的一些疑問和思考,有不對的地方還望批評指正。

 

二、ROM和RAM程序運行速度實驗

總體思路是,設置一個在ROM或RAM中的while死循環程序,在這個循環中不斷進行特定的計算,每隔100毫秒通過串口打印一次循環運行的次數。

1、STM32F103ZET6平臺,內核頻率72MHz。

(1)ROM中運行

初始化一個100毫秒中斷一次的定時器

unsigned int dwSpeedTestLastCnt = 0;
unsigned int dwSpeedTestCnt = 0;

void time_init()
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;	

	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	TIM_TimeBaseInitStructure.TIM_Period = 200;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 35999;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; 
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);	
	TIM_Cmd(TIM3,ENABLE); 

	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);	

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;	
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}

void SpeedTest(void)
{
	while(1)
	{
		dwSpeedTestCnt++;
	}
}

void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3, TIM_IT_Update))
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
		
		printf("\r\nCycle = %d", dwSpeedTestCnt - dwSpeedTestLastCnt);
		dwSpeedTestLastCnt = 	dwSpeedTestCnt;
			
		GPIOC->ODR ^= 0x01;  //閃個LED
		
		TIM3->CNT = 0;  
	}
}

int main(void)
{
    USART1_Config();
    LED_Config();
    time_init();
    SpeedTest();

    return 0;
}

工程編譯成功後從map文件中看到SpeedTest函數地址如下:

    SpeedTest                                0x08003805   Thumb Code    14  main.o(.text)

取打印的10個典型值如下:即每100ms循環了31萬多次。

Cycle = 313572
Cycle = 313768
Cycle = 313638
Cycle = 313833
Cycle = 313702
Cycle = 313572
Cycle = 313768
Cycle = 313638
Cycle = 313833
Cycle = 313702

(2)RAM中運行

在RAM中運行程序需要修改sct文件(在工程的Obj目錄裏),在RAM地址區域添加一個名爲“RAMCODE ”的Section段,如下:

 剛剛的程序只需要改掉一個地方就可以了,就是把測試函數用一對標籤包住,修改如下:

#pragma arm section code = "RAMCODE"
void SpeedTest(void)
{
	while(1)
	{
		dwSpeedTestCnt++;
	}
}
#pragma arm section

工程編譯成功後從map文件中看到SpeedTest函數地址如下:

    SpeedTest                                0x20000001   Thumb Code    14  main.o(RAMCODE)

取打印的10個典型值如下:即每100ms循環了55萬多次。

Cycle = 555242
Cycle = 555011
Cycle = 554780
Cycle = 555125
Cycle = 554897
Cycle = 555242
Cycle = 555011
Cycle = 554780
Cycle = 555125
Cycle = 554897

(3)總結

對於這個簡單的測試來說,在STM32F103ZET6這個平臺下,內核頻率72MHz,RAM中程序跑的比ROM要快,快了大概

77%((555011 - 313572) / 313572 * 100% = 77%)。

 

2、STM32F429BIT6平臺,內核頻率168MHz。

由於好奇心,又在這個平臺進行了一下測試,還是一樣的套路,100ms下看能循環多少次。

(1)ROM中運行

工程編譯成功後從map文件中看到SpeedTest函數地址如下:

SpeedTest                                0x080011c5   Thumb Code    14  main.o(i.SpeedTest)

 取打印的10個典型值如下:即每100ms循環了167萬多次。

Cycle = 1675472
Cycle = 1675468
Cycle = 1675472
Cycle = 1675468
Cycle = 1675472
Cycle = 1675468
Cycle = 1675472
Cycle = 1675468
Cycle = 1675472
Cycle = 1675468

(2)RAM中運行

工程編譯成功後從map文件中看到SpeedTest函數地址如下:

    SpeedTest                                0x20000001   Thumb Code    14  main.o(RAMCODE)

 取打印的10個典型值如下:即每100ms循環了128萬多次。

Cycle = 1288825
Cycle = 1288824
Cycle = 1288825
Cycle = 1288825
Cycle = 1288825
Cycle = 1288824
Cycle = 1288825
Cycle = 1288824
Cycle = 1288825
Cycle = 1288824

(3)總結

對於這個簡單的測試來說,在STM32F429BIT6這個平臺下,內核頻率168MHz,ROM中程序跑的比RAM要快,快了大概

30%((1675472 - 1288825) / 1288825 * 100% = 30%)。

那麼問題來了,跑的測試程序都是一樣的爲什麼得出的結論不一樣?按理來說RAM的讀寫速度肯定比內嵌的Flash快的多啊,爲什麼在F429這裏,程序在RAM裏跑不過ROM了?

帶着這些疑問,於是又在網上各種查找資料。

 

三、預取指令技術

STM32F103xx系列的CPU時鐘頻率可以達到72MHz,但是內部Flash的時鐘頻率跑不了太高只有24MHz,所以當CPU直接訪問Flash存儲器時必須插入等待週期以得到正確的結果。但是還有個要注意的問題,Flash可以每次讀出64位的數據,而CPU每次取指令最多爲32位的字,如果是每次取指令都從Flash直接讀取的話,勢必造成至少32位從Flash裏面讀出來的數據的讀取浪費(讀出來了卻沒用上),所以STM32F103xx專門有兩個64位“預取緩衝區”來保存每次讀出的指令,CPU到這個緩衝區裏去拿指令,當“預取緩衝區”爲空時再次從Flash讀出指令。於是Flash的數據位寬得到了完全的使用,CPU在執行指令同時“預取緩衝區”也在讀取Flash數據,減少了CPU的等待週期從而提高了CPU運行效率。下面從幾個方面來了解。

1、STM32F1的內部Flash和系統框架

下面是從《STM32F10xxx中文參考手冊》中摘抄來的關於內部Flash的介紹:

2.3.3 嵌入式閃存
高性能的閃存模塊有以下的主要特性:
● 高達512K字節閃存存儲器結構:閃存存儲器有主存儲塊和信息塊組成:
─ 主存儲塊容量:
小容量產品主存儲塊最大爲4K× 64位,每個存儲塊劃分爲32個1K字節的頁(見表2)。
中容量產品主存儲塊最大爲16K× 64位,每個存儲塊劃分爲128個1K字節的頁(見表3)。
大容量產品主存儲塊最大爲64K× 64位,每個存儲塊劃分爲256個2K字節的頁(見表4)。
互聯型產品主存儲塊最大爲32K× 64位,每個存儲塊劃分爲128個2K字節的頁(見表5)。
─ 信息塊容量:
互聯型產品有2360× 64位(見表5)。
其它產品有258× 64位(見表2、 表3、 表4)。
閃存存儲器接口的特性爲:
● 帶預取緩衝器的讀接口(每字爲2× 64位)
● 選擇字節加載器
● 閃存編程/擦除操作
● 訪問/寫保護

……………………………………
閃存讀取
閃存的指令和數據訪問是通過AHB總線完成的。預取模塊是用於通過ICode總線讀取指令的。仲
裁是作用在閃存接口,並且DCode總線上的數據訪問優先。
讀訪問可以有以下配置選項:
● 等待時間:可以隨時更改的用於讀取操作的等待狀態的數量。
● 預取緩衝區(2個64位):在每一次復位以後被自動打開,由於每個緩衝區的大小(64位)與閃
存的帶寬相同,因此只通過需一次讀閃存的操作即可更新整個緩衝區的內容。由於預取緩
衝區的存在, CPU可以工作在更高的主頻。 CPU每次取指最多爲32位的字,取一條指令
時,下一條指令已經在緩衝區中等待。
● 半週期:用於功耗優化。
注: 1. 這些選項應與閃存存儲器的訪問時間一起使用。等待週期體現了系統時鐘(SYSCLK)頻率與閃
存訪問時間的關係:
0等待週期,當 0 < SYSCLK < 24MHz
1等待週期,當 24MHz < SYSCLK ≤ 48MHz
2等待週期,當 48MHz < SYSCLK ≤ 72MHz
2 . 半週期配置不能與使用了預分頻器的AHB一起使用,時鐘系統應該等於HCLK時鐘。該特性
只能用在時鐘頻率爲8MHz或低於8MHz時,可以直接使用的內部RC振盪器(HSI),或者是主振
蕩器(HSE),但不能用PLL。
3. 當AHB預分頻係數不爲1時,必須置預取緩衝區處於開啓狀態。
4. 只有在系統時鐘(SYSCLK)小於24MHz並且沒有打開AHB的預分頻器(即HCLK必須等於
SYSHCLK)時,才能執行預取緩衝器的打開和關閉操作。一般而言,在初始化過程中執行預取
緩衝器的打開和關閉操作,這時微控制器的時鐘由8MHz的內部RC振盪器(HSI)提供。
5. 使用DMA: DMA在DCode總線上訪問閃存存儲器,它的優先級比ICode上的取指高。 DMA在
每次傳送完成後具有一個空餘的週期。有些指令可以和DMA傳輸一起執行。

下面是F1系列(非互聯網產品)的系統結構圖:

ICode總線:該總線將Cortex™-M3內核的指令總線與閃存指令接口相連接。指令預取在此總線上完成。

DCode總線:該總線將Cortex™-M3內核的DCode總線與閃存存儲器的數據接口相連接(常量加載和調試訪問)。

系統總線:此總線連接Cortex™-M3內核的系統總線(外設總線)到總線矩陣,總線矩陣協調着內核和DMA間的訪問。

DMA總線:此總線將DMA的AHB主控接口與總線矩陣相聯,總線矩陣協調着CPU的DCode和DMA到SRAM、閃存和外設的訪問。

在STM32庫函數“system_stm32f10x.c”文件中有個叫SetSysClockTo72的函數,這個函數將系統時鐘配置到72MHz,如下:

/**
  * @brief  Sets System clock frequency to 72MHz and configure HCLK, PCLK2 
  *         and PCLK1 prescalers. 
  * @note   This function should be used only after reset.
  * @param  None
  * @retval None
  */
static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
  }
}

其中這兩句使能“預取緩衝區”,然後設置等待週期爲2個等待週期,如下:

    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;   

爲什麼設置2個等待週期呢?因爲72MHz/24MHz = 3。等2個週期後,下一個週期可以讀走數據。

 

2、“預取緩衝區”工作方式

這個沒看到手冊上面有很詳細的說具體怎麼工作的,倒是在網上搜到了一篇文章感覺講的挺好的,如下引用一段(因爲是個文檔,不知道出處是哪裏,就貼不上原文鏈接了):


首先,STM32的內部Flash是組織成64位寬度,即每次可以讀出64位;在FlashCPU的取指隊列之間有兩個緩衝器,用於暫存Flash中取出的指令,見下圖。

其次,STM32的指令有16位的也有32位的,指令是從圖中綠色的緩衝器取出;當綠色緩衝器變空時,黃色緩衝器中的內容會被複制到綠色緩衝器中;這樣取指與讀取Flash互不干擾。

正因爲STM32的指令有不同長度,所以程序執行的等待週期與程序的內容有關。圖一是假定所有指令都是16位的指令:

(1)時刻t0時黃色緩衝器和綠色緩衝器都爲空,此時CPU等待3個週期後,到時刻t1時才能讀到指令;

(2)時刻t1時綠色緩衝器被填滿,黃色緩衝器仍爲空,Flash控制器繼續讀取後續指令;

(3)時刻t2時綠色緩衝器還有兩個字節,黃色緩衝器被填滿;此時因爲兩個緩衝器都有數據,讀取Flash的操作暫停(圖一中的綠色虛線框所示)

(4)當綠色緩衝器變空時,黃色緩衝器被複制到綠色緩衝器,同時恢復讀取Flash的操作;

(5)時刻t3時緩衝器的狀態又變爲上述第(3)步的狀態。

從以上分析可以看出,CPU的指令執行是沒有等待週期的。但當執行跳轉指令時,Flash緩衝器中的內容作廢,系統回到了上述第(1)步的狀態。

圖二是假定每三條指令中有兩條16位的指令和一條32位的指令。這種情況下,如圖所示,CPU的指令執行也是沒有等待週期的。

圖三是假定所有指令都是32位的指令,從圖中可看出,CPU每執行兩條指令,要插入一個等待週期。

上面的分析只是針對每個CPU週期都有取指操作的情況,而實際的操作中情況並沒有這麼簡單,因爲Cortex-M3的指令不都是單週期指令。實際的程序執行情況是受很多因素影響的,單純靜態的分析也是不現實的。


從以上可以瞭解到雖然Flash速度跟不上CPU,但是在理想情況下,CPU讀取指令達到了“0”等待的效果。

 

3、STM32F4的內部Flash和系統框架

說完了F1的咱再來說說F4的,F4是M4的內核,CPU時鐘可以達到180MHz,既然比M3快了那麼多,那麼肯定在內部Flash和指令讀取機制上會有一定的提升,下面摘抄《STM32F429中文參考手冊》裏介紹Flash的一段:

3.3 嵌入式 Flash
Flash 具有以下主要特性:
● 對於 STM32F40x 和 STM32F41x,容量高達 1 MB;對於 STM32F42x 和 STM32F43x,
容量高達 2 MB
● 128 位寬數據讀取
● 字節、半字、字和雙字數據寫入
● 扇區擦除與全部擦除
● 存儲器組織結構
Flash 結構如下:
— 主存儲器塊,分爲 4 個 16 KB 扇區、 1 個 64 KB 扇區和 7 個 128 KB 扇區
— 系統存儲器,器件在系統存儲器自舉模式下從該存儲器啓動
— 512 字節 OTP(一次性可編程),用於存儲用戶數據
OTP 區域還有 16 個額外字節,用於鎖定對應的 OTP 數據塊。
— 選項字節,用於配置讀寫保護、 BOR 級別、軟件/硬件看門狗以及器件處於待機或
停止模式下的復位。
● 低功耗模式(有關詳細信息,請參見參考手冊的“電源控制 (PWR)”部分)

3.4 讀接口
3.4.1 CPU 時鐘頻率與 Flash 讀取時間之間的關係
爲了準確讀取 Flash 數據,必須根據 CPU 時鐘 (HCLK) 頻率和器件電源電壓在 Flash 存取控
制寄存器 (FLASH_ACR) 中正確地編程等待週期數 (LATENCY)。
當電源電壓低於 2.1 V 時,必須關閉預取緩衝器。 表 7 所示爲 Flash 等待週期與 CPU 時鐘
頻率之間的對應關係。

注意: STM32F405xx/07xx 和 STM32F415xx/17xx 器件:
- 當 VOS =“0”時, fHCLK 最大值 = 144 MHz。
- 當 VOS =“1”時, fHCLK 最大值 = 168 MHz。
STM32F42xxx 和 STM32F43xxx 器件:
- 當 VOS[1:0] =“0x01”時, fHCLK 最大值爲 120 MHz。
- 當 VOS[1:0] =“0x10”時, fHCLK 最大值爲 144 MHz。
- 當 VOS[1:0] =“0x11”時, fHCLK 最大值爲 168 MHz。

從以上信息知道F4的Flash數據位寬增加到了128位,然後電壓的不同也會相應的影響等待週期。

接下來還專門介紹了“ART加速器”,如下:

3.4.2 自適應實時存儲器加速器 (ART Accelerator™)
專有的自適應實時 (ART) 存儲器加速器面向 STM32 工業標準 ARM® Cortex™-M4F 處理器
進行了優化。該加速器很好地體現了 ARM Cortex M4F 的固有性能優勢,克服了通常條件
下,高速的處理器在運行中需要經常等待 FLASH 讀取的情況。
爲了發揮處理器的全部性能,該加速器將實施指令預取隊列和分支緩存,從而提高了 128 位
Flash 的程序執行速度。根據 CoreMark 基準測試,憑藉 ART 加速器所獲得的性能相當於
Flash 在 CPU 頻率高達 168 MHz 時以 0 個等待週期執行程序。
指令預取
每個 Flash 讀操作可讀取 128 位,可以是 4 行 32 位指令,也可以是 8 行 16 位指令,具體
取決於燒寫在 Flash 中的程序。因此對於順序執行的代碼,至少需要 4 個 CPU 週期來執行
前一次讀取的 128 位指令行。在 CPU 請求當前指令行時,可使用 I-Code 總線的預取操作讀
取 Flash 中的下一個連續存放的 128 位指令行。可將 FLASH_ACR 寄存器中的 PRFTEN 位
置 1,來使能預取功能。當訪問 Flash 至少需要一個等待週期時,此功能非常有用。
圖 4 所示爲需要 3 WS( 3 個等待週期)訪問 Flash 時連續 32 位指令的執行過程,圖中分別
介紹了使用和不使用預取操作兩種情況。
不使用預取操作

 

使用預取操作

處理非順序執行的代碼(有分支)時,指令可能並不存在於當前使用的或預取的指令行中。這種情況下, CPU 等待時間至少等於等待週期數。 

指令緩存存儲器
爲了減少因指令跳轉而產生的時間損耗,可將 64 行 128 位的指令保存到指令緩存存儲器
中。可將 FLASH_ACR 寄存器中的指令緩存使能 (ICEN) 位置 1,來使能這一特性。每當出
現指令缺失(即請求的指令未存在於當前使用的指令行、預取指令行或指令緩存存儲器中)
時,系統會將新讀取的行復制到指令緩存存儲器中。如果 CPU 請求的指令已存在於指令緩
存區中,則無需任何延時即可立即獲取。指令緩存存儲器存滿後,可採用 LRU(最近最少使
用)策略確定指令緩存存儲器中待替換的指令行。此特性非常適用於包含循環的代碼。
數據管理
在 CPU 流水線執行階段,將通過 D-Code 總線訪問 Flash 中的數據緩衝池。因此,直到提
供了請求的數據後, CPU 流水線纔會繼續執行。爲了減少因此而產生的時間損耗,通過
AHB 數據總線 D-Code 進行的訪問優先於通過 AHB 指令總線 I-Code 進行的訪問。
如果頻繁使用某些數據,可將 FLASH_ACR 寄存器中的數據緩存使能 (DCEN) 位置 1,來使
能數據緩存存儲器。此特性的工作原理與指令緩存存儲器類似,但保留的數據大小限制在 8
行 128 位/行以內。
注意: 用戶配置扇區中的數據無法緩存。

“ART 加速器”這個東西在F1的手冊裏面沒看到說過,F4裏介紹說實現了“指令預取隊列”和“分支緩存”,這一部分介紹看起來比F1的更牛逼,在F4的系統框圖裏也專門畫了這麼一個東西。

“分支緩存”是什麼意思?我們先來想個問題,在程序裏有個循環體,那麼到達循環體尾部的時候有條跳轉指令指示程序地址跳回到之前的某個地方,因爲預取緩存裏還有很多個字節是存了這條跳轉語句之後的一些東西,這些都用不着了,要重新到跳轉的地址去讀指令了,導致了CPU需要等待這一部分的讀取時間,循環次數越多,等待的時間越多。於是引入了“分支緩存”,它可以記錄下最近的跳轉情況,當再次遇到相同的跳轉語句情景時,機智地去將要跳轉的地址去預取指令,這樣在指令跳轉時CPU就不需要等待了。這裏講的比較粗略,想更詳細瞭解的可以參考文末貼出的一篇文章《CPU分支指令預測技術》。

 

四、實驗分析

1、分析

經過以上資料的學習,其實可以大致明白是什麼原因了。在沒有對程序優化的情況下,因爲SpeedTest這個測試函數裏的while循環只有一條變量自加語句,導致運行時程序瘋狂跳轉,而F1只有預取緩存,F4既有預取緩存又有分支緩存,所以F1在處理循環跳轉時會有CPU的等待週期,而F4在多次循環時可以說基本沒有。SpeedTest裏循環的彙編代碼如下,這是從調試窗口上拷貝下來的,程序一直在這6條指令裏循環跑。

0x08003806 486A      LDR      r0,[pc,#424]  ; @0x080039B0
0x08003808 6800      LDR      r0,[r0,#0x00]
0x0800380A 1C40      ADDS     r0,r0,#1
0x0800380C 4968      LDR      r1,[pc,#416]  ; @0x080039B0
0x0800380E 6008      STR      r0,[r1,#0x00]
0x08003810 E7F9      B        0x08003806

對於F1來說,在這種情況下RAM跑程序的速度優勢強於ROM跑程序的跳轉等待週期缺陷,於是看到的現象是RAM跑程序更快。對於F4來說,已經處理了ROM跑程序的跳轉等待週期缺陷,所以RAM已經不具優勢了,於是看到的現象是ROM跑程序更快。

 

2、再次實驗驗證

爲了驗證上面說的這些,下面再對F1做個實驗,把循環體裏的語句複製成很多的單條語句,讓循環體成爲一大段連續的程序區,這樣的話對F1做出來的實驗現象會是ROM比RAM更快嗎?

更改測試函數如下:

void SpeedTest(void)
{
	while(1)
	{
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
		dwSpeedTestCnt++;
	}
}

(1)ROM中運行

取打印的10個典型值如下:即每100ms循環了113萬多次。

Cycle = 1138258
Cycle = 1138491
Cycle = 1138730
Cycle = 1138966
Cycle = 1139205
Cycle = 1138255
Cycle = 1138491
Cycle = 1138733
Cycle = 1138966
Cycle = 1139206

(2)RAM中運行

取打印的10個典型值如下:即每100ms循環了101萬多次。

Cycle = 1012125
Cycle = 1012333
Cycle = 1012542
Cycle = 1012755
Cycle = 1012964
Cycle = 1012125
Cycle = 1012333
Cycle = 1012541
Cycle = 1012756
Cycle = 1012963

(3)結論

從實驗數據可以看出,經過改動測試函數,循環的跳轉頻繁程度明顯降低,ROM上跑程序的速度實現了反超。

 

五、結論

由於STM32與其他單片機一樣,採用哈佛總線結構,即取指總線和數據總線是分開的,所以取指令和取數據是可以同時進行的,用ROM跑程序的話正好可以利用這一優良特性。但是用RAM跑程序的話由於取指令和取數據都需要佔用數據總線,雖然RAM是極快的,對單片機CPU來說數據讀寫永遠是0等待週期,就算這樣也需要多花時鐘週期去讀數據,因爲讀指令和讀數據不可以同時進行了,這是一種性能的損失。剛好“預取緩存”和“分支緩存”技術又將RAM的速度優勢給平衡掉了。所以我個人認爲,在F4平臺下,通常情況下將程序代碼放在RAM中跑並不會具有速度優勢。對於F1平臺而言,還要看具體的程序結構才能確定。

 

六、參考資料

文章:

《STM32 進階教程 11 - RAM中運行程序》

https://blog.csdn.net/zhanglifu3601881/article/details/95040782

《CPU分支指令預測技術》

http://blog.sina.com.cn/s/blog_58942aff01009w0z.html

資料:

《STM32F10xxx中文參考手冊_V10》

《Cortex-M3權威指南》

《STM32F407, 429參考手冊(中文)》

《STM32F429_數據手冊(中文)》

鏈接:https://pan.baidu.com/s/12ZU83ET5z7JPxkU9BAYurQ  提取碼:cbom

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章