痞子衡嵌入式:鏈接函數到8位元組對齊地址或可進一步提升i.MXRT內核執行性能

来源:https://www.cnblogs.com/henjay724/archive/2020/06/10/13088601.html
-Advertisement-
Play Games

一個有趣的結論,Cortex-M7上將函數鏈接到8位元組對齊的地址有利於指令雙發射,這就是進一步提升代碼執行性能的秘密 ...



  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT上進一步提升代碼執行性能的經驗

  今天跟大家聊的這個話題還是跟痞子衡最近這段時間參與的一個基於i.MXRT1170的大項目有關,痞子衡在做其中的開機動畫功能,之前寫過一篇文章 《降低刷新率是定位LCD花屏顯示問題的第一大法》 介紹了開機動畫功能的實現以及LCD顯示註意事項,在此功能上,痞子衡想進一步測試從晶元上電到LCD屏顯示第一幅完整圖像的時間,這個時間我們暫且稱為1st UI時間,該時間的長短對項目有重要意義。

  痞子衡分別測試了代碼在XIP執行下和在TCM里執行下的1st UI時間,得到的結果竟然是XIP執行比TCM執行還要快50ms,這是怎麼回事?這完全顛覆了我們的理解,i.MXRT上TCM是與內核同頻的,Flash速度遠低於TCM。如果是XIP執行,即使有I-Cache加速,也最多與TCM執行一樣快,怎麼可能做到比TCM執行快這麼多。於是痞子衡便開始深挖這個奇怪的現象,然後發現了進一步提升代碼執行性能的秘密。

一、引出計時差異問題

  痞子衡的開機動畫程式是基於 \SDK_2.x.x_MIMXRT1170-EVK\boards\evkmimxrt1170\jpeg_examples\sd_jpeg 常式的,只是去了SD卡和libjpeg庫相關代碼。工程有兩個build,一個是TCM里執行(即debug),另一個是XIP執行(即flexspi_nor_debug)。

  項目板上的Flash型號是MX25UW51345G,痞子衡將其配成Octal mode, DDR, 166MHz用於啟動。項目板上還有兩個LED燈,痞子衡在LED燈上飛了兩根線,連同POR引腳一起連上示波器,用於精確測量1st UI各部分時間組成。

  示波器通道1連接POR引腳,表明1st UI時間起點;通道2連接LED1 GPIO,表明ROM啟動時間(進入用戶APP的時間點);通道3連接LED2 GPIO,做兩次電平變化,分別是1st圖像幀開始和結束的時間點。翻轉LED GPIO代碼位置如下:

void light_led(uint32_t ledIdx, uint8_t ledVal);

void SystemInit (void) {
    // 將LED1置1,標示ROM啟動時間
    light_led(1, 1);

    SCB->CPACR |= ((3UL << 10*2) | (3UL << 11*2));

    // ...
}

void APP_InitDisplay(void)
{
    // ...

    g_dc.ops->enableLayer(&g_dc, 0);

    // 將LED2置1,標示1st圖像幀開始時間點
    light_led(2, 1);
}

int main(void)
{
    BOARD_ConfigMPU();
    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    BOARD_ResetDisplayMix();

    APP_InitDisplay();

    while (1)
	{
	    // ...
	}
}

static void APP_BufferSwitchOffCallback(void *param, void *switchOffBuffer)
{
    s_newFrameShown = true;

    // 將LED2置0,標示1st圖像幀結束時間點
    light_led(2, 0);
}

  上圖是痞子衡抓到的波形(30Hz,XIP),痞子衡一共做了四次測試,分別是30Hz LCD刷新率下的XIP/TCM以及60Hz LCD刷新率下的XIP/TCM,結果如下表所示。表中的Init Time一欄表示的是開機動畫程式代碼執行時間(從SystemInit()函數開始執行到APP_InitDisplay()函數結束的時間),可以看到TCM執行比XIP執行慢近50ms,這便是奇怪問題所在。

代碼位置 LCD刷新率 POR Time Boot Time Init Time Launch Time
XIP 30Hz 3.414ms 10.082ms 34.167ms + 153ms 32.358ms
TCM 30Hz 3.414ms 10.854ms 33.852ms + 203ms 32.384ms
XIP 60Hz 3.414ms 9.972ms 18.142ms + 153ms 16.166ms
TCM 60Hz 3.414ms 10.92ms 17.92ms + 203ms 16.104ms

二、定位計時差異問題

  對於開機動畫代碼,XIP執行比TCM執行快這個結果,痞子衡是不相信的,於是痞子衡便用二分法逐步查找,發現時間差異是BOARD_InitLcdPanel()函數里的DelayMs()調用引起的,這些人為插入的延時是LCD屏控制器手冊里的要求,總延時時間應該是153ms,但是這個函數的執行在XIP下(153ms)和TCM里(203ms)時間不同。

static void BOARD_InitLcdPanel(void)
{
    // ...

#if (DEMO_PANEL ==  DEMO_PANEL_TM103XDKP13)
    // ...

    /* Power LCD on */    
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 1);
    DelayMs(2);
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 0);
    DelayMs(5);
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 1);
    DelayMs(6);
    GPIO_PinWrite(LCD_STBYB_GPIO, LCD_STBYB_GPIO_PIN, 1);
    DelayMs(140);
#endif
    // ...
}

  所以現在的問題就是為何在TCM里執行DelayMs(153)需要203ms,而XIP執行下是精確的。讓我們進一步查看DelayMs()函數的原型,這個函數其實調用的是SDK_DelayAtLeastUs()函數,SDK_DelayAtLeastUs()函數從命名上看就很有意思,AtLeast即保證軟延時一定能滿足用戶設置的時間,但也可能超過這個時間。為何是AtLeast設計,其實這裡就涉及到Cortex-M7內核一個很重要的特性 - 指令雙發射,軟體延時的本質是靠CPU執行指令來消耗時間,但是CPU拿指令到底是單發射還是雙發射有一定的不確定性,因此無法做到精確,如果以全雙發射來計算,就能得出最小延時時間。

#define DelayMs                  VIDEO_DelayMs

#if defined(__ICCARM__)
static void DelayLoop(uint32_t count)
{
    __ASM volatile("    MOV    R0, %0" : : "r"(count));
    __ASM volatile(
        "loop:                          \n"
        "    SUBS   R0, R0, #1          \n"
        "    CMP    R0, #0              \n"
        "    BNE    loop                \n");
}
#endif

void SDK_DelayAtLeastUs(uint32_t delay_us, uint32_t coreClock_Hz)
{
    assert(0U != delay_us);
    uint64_t count = USEC_TO_COUNT(delay_us, coreClock_Hz);
    assert(count <= UINT32_MAX);

#if (__CORTEX_M == 7)
    count = count / 3U * 2U;
#else
    count = count / 4;
#endif
    DelayLoop(count);
}

void VIDEO_DelayMs(uint32_t ms)
{
    SDK_DelayAtLeastUs(ms * 1000U, SystemCoreClock);
}

  分析到現在,問題已經轉化成為何XIP下執行指令雙發射概率比TCM里執行指令雙發射概率更大,關於這個現象並沒有在ARM官方文檔里查找到相關信息,DelayLoop()迴圈里只是3條指令,XIP下執行肯定是在Cache line里,這跟在TCM里執行並沒有什麼區別。讓我們再去看看兩個工程的map文件,找到DelayLoop()函數鏈接地址,這個函數在兩個測試工程下鏈接地址對齊不一樣,這意味著測試條件不完全相同,或許這是一個解決問題的線索。

  XIP執行工程(flexspi_nor_debug),DelayLoop()函數地址8位元組對齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop               0x3000'3169    0xa  Code  Lc  fsl_common.o [1]

  TCM執行工程(debug工程),DelayLoop()函數地址4位元組對齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop                    0x314d    0xa  Code  Lc  fsl_common.o [1]

三、找到計時差異本質

  前面找到DelayLoop()函數鏈接地址差異是一個線索,那我們就針對這個線索做測試,不再讓鏈接器自動分配DelayLoop()函數地址,改為在鏈接文件里指定地址去鏈接,下麵代碼是IAR環境下的示例,我們使用debug工程(即在TCM執行)來做測試。

  C源文件中在DelayLoop()函數定義前加#pragma location = ".myFunc",即將該函數定義為.myFunc的段,然後在鏈接文件icf中用place at語句指定.myFunc段到固定地址m_text_func_start處開始鏈接:

#if defined(__ICCARM__)
#pragma location = ".myFunc"
static void DelayLoop(uint32_t count)
{
    // ...
}
#endif
define symbol m_text_func_start        = 0x00004000;

place at address mem: m_text_func_start     { readonly section .myFunc };

define symbol m_text_start             = 0x00002400;
define symbol m_text_end               = 0x0003FFFF;

place in TEXT_region                        { readonly };

  根據鏈接起始地址m_text_func_start的不同,我們得到了不同的結果,如下表所示。至此真相大白,造成DelayMs()函數執行時間不同的根本原因不是XIP/TCM執行差異,而是鏈接地址對齊差異,8位元組對齊的函數更容易觸發CM7指令雙發射,相比4位元組對齊的函數在性能上能提升24.8% 。

m_text_func_start值 鏈接地址對齊 函數調用語句 實際執行時間
0x00004000 8n位元組 DelayMs(100) 100ms
0x00004002 2位元組,未能鏈接 N/A N/A
0x00004004 4位元組 DelayMs(100) 133ms
0x00004008 8位元組 DelayMs(100) 100ms

  現在我們得到了一個有趣的結論,Cortex-M7上將函數鏈接到8位元組對齊的地址有利於指令雙發射,這就是進一步提升代碼執行性能的秘密。

  至此,i.MXRT上進一步提升代碼執行性能的經驗痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時發佈到我的 博客園主頁CSDN主頁微信公眾號 平臺上。

微信搜索"痞子衡嵌入式"或者掃描下麵二維碼,就可以在手機上第一時間看了哦。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • LiveCharts 提示框(DataTooltip)百分比一直為0.00%解決辦法 問題描述:在使用LiveCharts 開源圖標庫的時候,使用CartesianChart類圖表,當Series為LineSeries(多個對象)類型時,DataTooltip數據提示框會提示每個點對應的百分比,但一 ...
  • 後臺修改前臺不刷新可能的原因: 1.前臺頁面沒有寫Binding 2.後臺數據定義的欄位沒有get和set 3.數據容器沒有使用ObservableCollection 4.欄位內容修改時沒有重置數據源 首先簡單舉例界面代碼如下: <DataGrid Name="DG" ItemsSource="{ ...
  • 隨著微服務的火熱,DDD(領域驅動設計模式)思想風起雲涌,衝擊著整個軟體生態系統。其中,事件匯流排那是必須知道的了,於是我便抱著一個學習DDD的心態搭建了一個博客網站,目前該網站正在建設階段,後續會不斷完善,這裡我只是講一下我裡面所用到的事件匯流排。 事件匯流排,我的理解就是發佈訂閱模式,這裡有一篇文章寫 ...
  • 使用請求頭認證來測試需要授權的 API 介面 Intro 有一些需要認證授權的介面在寫測試用例的時候一般會先獲取一個 token,然後再去調用介面,其實這樣做的話很不靈活,一方面是存在著一定的安全性問題,獲取 token 可能會有一些用戶名密碼之類的測試數據,還有就是獲取 token 的話如果全局使 ...
  • 系列文章 基於 abp vNext 和 .NET Core 開發博客項目 - 使用 abp cli 搭建項目 基於 abp vNext 和 .NET Core 開發博客項目 - 給項目瘦身,讓它跑起來 基於 abp vNext 和 .NET Core 開發博客項目 - 完善與美化,Swagger登場 ...
  • 《C# 敏捷開發實踐》 [作者] (英) Gary McLean Hall[譯者] (中) 許順強[出版] 人民郵電出版社[版次] 2016年07月 第1版[印次] 2016年07月 第1次 印刷[定價] 69.00元 【第一部分】 (P001) 編寫代碼是軟體開發的核心工作,而編寫好用的代碼有很多 ...
  • API是什麼,Linux系統中系統調用可以理解是操作系統為用戶提供的一系列操作的介面(API), 以C語言為例,我們使用fopen()函數可以打開一個文件,感覺非常簡單。文件保存在硬碟上,要經過複雜的處理才能顯示,這些細節對我們來說是透明的,由操作系統完成。也就是說,我們調用fopen()函數來通知 ...
  • Docker簡介;Docker與虛擬機區別;Docker的優點及內部組件說明;如何安裝Docker與鏡像加速配置。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...