樂鑫科技代理公司藍牙wifi二合一模塊ESP32內存分析案例研究,內存對硅片成本以及芯片尺寸具有重大影響,因此從硬件角度看,優化內存尺寸很重要,從軟件角度來看,能夠充分利用內存資源也至關重要。
在本文中,我們將討論 ESP-IDF 中的一些即將推出的功能和常用配置選項(可調項),以允許 終端應用程序 以佳方式利用各個內部存儲區域。
重要提示
這里我們將重點介紹樂鑫科技代理公司藍牙wifi二合一模塊ESP32 的單核模式,因為該模式下可以適用更多內存優化功能;
我們將在這里考慮典型的 IoT 用例,在該用例下犧牲性能獲得內存是可以接受的準則;
我們將以典型的云應用程序為研究用例,該應用需要具有相互認證支持的 TLS 連接;
這里使用的 ESP-IDF 功能分支可查閱 https://github.com/mahavirj/esp-idf/tree/feature/memory_optimizations 。
ESP32:內部存儲器分解
從存儲器布局可以看出,芯片內部存在各種具有不同時鐘速度的存儲器區域;
對于單核用例,我們獲得了額外的 32K 指令存儲空間(IRAM),否則(雙核模式)該區域會作為 APP CPU 內核的 cache;
對指令 RAM 的訪問,地址和空間大小應始終 32 位對齊;
對于終端應用程序的業務邏輯來說,總是希望有更多的 DRAM,它是訪問速度快的內存且沒有任何訪問限制。
案例研究— AWS IoT 示例應用程序
我們將使用來自 ESP-AWS-IoT 的 subscribe_publish 示例作為研究案例,來分析內存利用率;
樂鑫科技代理公司藍牙wifi二合一模塊ESP-IDF 提供了一個 API ,可以使用 heap_caps_get_minimum_free_size() 獲取小空閑堆或者說系統中可用的動態內存大小。我們的目標是大化這個數字(進行相對分析),從而增加終端應用程序特定業務邏輯的可用內存數量(特別是 DRAM 區域)。
默認內存利用率
我們將在 subscribe_publish 示例之上添加以下代碼補丁來記錄動態內存的統計信息。
開始,我們將分別記錄 DRAM 和 IRAM 區域如上所述的系統小空閑堆大??;
其次,我們將使用堆任務跟蹤功能,該功能提供了基于每個任務的動態內存使用信息。修改此功能后,還可以記錄每個任務 DRAM 和 IRAM 區域的峰值使用量;
我們將分別為 aws_iot_task ,tiT (tcpip) 和 wifi 任務記錄這些信息 (因為這些任務定義了從應用層到物理層的數據傳輸通道,反之亦然)。還應該注意的是,網絡任務的峰值內存使用量會受到環境因素(如 Wi-Fi 連接,網絡等待時間)的影響而變化。
注意 : 任務創建過程中對 core-id 的更改(代碼補丁如下)是針對單核的配置,這里我們僅用于此特殊示例。
diff --git examples/subscribe_publish/main/subscribe_publish_sample.c examples/subscribe_publish/main/subscribe_publish_sample.c
index c5b48ae..1982375 100644
--- examples/subscribe_publish/main/subscribe_publish_sample.c
+++ examples/subscribe_publish/main/subscribe_publish_sample.c
@@ -157,6 +157,28 @@ void disconnectCallbackHandler(AWS_IoT_Client *pClient, void *data) {
}
}
+#include "esp_heap_task_info.h"
+static void esp_dump_per_task_heap_info(void)
+{
+ heap_task_stat_t tstat = {};
+ bool begin = true;
+ printf("Task Heap Utilisation Stats:\n");
+ printf("||\tTask\t\t|\tPeak DRAM\t|\tPeak IRAM\t|| \n");
+ while (1) {
+ size_t ret = heap_caps_get_next_task_stat(&tstat, begin);
+ if (ret == 0) {
+ printf("\n");
+ break;
+ }
+ const char *task_name = tstat.task ? pcTaskGetTaskName(tstat.task) : "Pre-Scheduler allocs";
+ if (!strcmp(task_name, "wifi") || !strcmp(task_name, "tiT") || !strcmp(task_name, "aws_iot_task")) {
+ printf("||\t%-12s\t|\t%-5d\t\t|\t%-5d\t\t|| \n",
+ task_name, tstat.peak[0], tstat.peak[1]);
+ }
+ begin = false;
+ }
+}
+
void aws_iot_task(void *param) {
char cPayload[100];
@@ -278,6 +300,16 @@ void aws_iot_task(void *param) {
}
ESP_LOGI(TAG, "Stack remaining for task '%s' is %d bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL));
+
+ const int min_free_8bit_cap = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
+ const int min_free_32bit_cap = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL|MALLOC_CAP_32BIT);
+
+ esp_dump_per_task_heap_info();
+ printf("System Heap Utilisation Stats:\n");
+ printf("|| Miniumum Free DRAM\t| Minimum Free IRAM\t|| \n");
+ printf("||\t%-6d\t\t|\t%-6d\t\t||\n",
+ min_free_8bit_cap, (min_free_32bit_cap - min_free_8bit_cap));
+
vTaskDelay(1000 / portTICK_RATE_MS);
sprintf(cPayload, "%s : %d ", "hello from ESP32 (QOS0)", i++);
paramsQOS0.payloadLen = strlen(cPayload);
@@ -328,5 +360,5 @@ void app_main()
ESP_ERROR_CHECK( err );
initialise_wifi();
- xTaskCreatePinnedToCore(&aws_iot_task, "aws_iot_task", 9216, NULL, 5, NULL, 1);
+ xTaskCreatePinnedToCore(&aws_iot_task, "aws_iot_task", 9216, NULL, 5, NULL, 0);
}
在訂閱發布示例之上打補丁
使用默認配置(并啟用了堆任務跟蹤功能),我們得到以下堆利用率統計信息(所有值以字節為單位):
任務的內存使用率統計:
|| 任務名 | 峰值 DRAM |峰值 IRAM||
|| aws_iot_task | 63124 | 0 ||
|| tiT | 3840 | 0 ||
|| wifi | 31064 | 0 ||
系統的內存使用率統計:
|| 小可用 DRAM | 小可用 IRAM ||
|| 152976 | 40276 ||
單核配置
如前所述,我們將在所有實驗中使用單核配置。請注意,即使在單核模式下,樂鑫科技代理公司藍牙wifi二合一模塊ESP32 仍具有足夠的處理能力(接近 300 DMIPS),足以滿足典型的 IoT 用例的需求。
在應用程序中啟用相應的配置:
CONFIG_FREERTOS_UNICORE = y
重新運行應用程序之后更新的堆利用率統計信息如下所示:
任務的內存使用率統計:
|| 任務名 | 峰值 DRAM |峰值 IRAM||
|| aws_iot_task | 63124 | 0 ||
|| tiT | 3892 | 0 ||
|| wifi | 31192 | 0 ||
系統的內存使用率統計:
|| 小可用DRAM | 小可用IRAM ||
|| 162980 | 76136 ||
從上面可以看出,我們在 DRAM 中多獲得了約 10KB 的內存,這是由于不再需要第二個 CPU 內核的某些服務(例如,idle, esp_timer 任務等)。此外,也不再需要用于處理器間通信的 IPC 服務,因此我們可以從該服務的堆棧和動態內存中獲得額外內存。 IRAM 的增加是由于釋放了第二個 CPU 內核的 32KB cache,并且由于禁用了上述服務節省了一些代碼空間。
TLS 特定(優化)
非對稱 TLS 內容長度
此功能從 v4.0 開始已成為 ESP-IDF 的一部分。此功能允許為 TLS IN/OUT 緩沖區啟用非對稱的內容長度。因此,應用程序能夠將 TLS OUT 緩沖區從默認值 16KB (每個規范的大 TLS 片段長度) 減小到 2KB,從而可以節省 14KB 的動態內存空間。
請注意,不太可能將 TLS IN 的緩沖區長度從默認值 16KB 減小,除非您可以直接控制服務器配置項,或者確保服務器不會出現發送超過某個閾值的入站數據的行為(在握手或實際數據傳輸階段)。
在程序中啟用相應的配置:
#啟用 TLS 非對稱 IN/OUT 內容長度
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN = y
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN = 2048
重新運行應用程序以更新堆利用率統計信息,如下所示:
任務的內存使用率統計:
|| 任務 | 峰值 DRAM | 峰值 IRAM ||
|| aws_iot_task | 48784 | 0 ||
|| tiT | 3892 | 0 ||
|| wifi | 30724 | 0 ||
系統的內存使用率統計:
|| 小可用 DRAM | 小可用 IRAM ||
|| 177972 | 76136 ||
從上面可以看出,我們從 aws_iot_task 任務中獲得了約 14KB 的內存,因此小可用 DRAM 數量也相應的增加了。
動態緩沖區分配特性
在 TLS 連接期間,mbedTLS 堆棧從初始握手階段開始在整個會話期間保持動態分配功能的開啟。這些分配包括 TLS IN/OUT 緩沖區,對端證書,客戶端證書,私鑰等。在此特性中(即將成為樂鑫科技代理公司藍牙wifi二合一模塊ESP-IDF 的一部分),mbedTLS 內部 API 已被粘合(使用 SHIM 層),因此可以確保只要資源使用(包括數據緩沖區)完成,就會立即釋放相關的動態內存。
這大大有助于減少 TLS 連接時堆內存峰值利用率。由于頻繁進行動態內存操作(按需資源使用策略),因此對性能有微小的影響。此外,由于與身份驗證憑據(證書,密鑰等)有關的內存已被釋放,因此在 TLS 嘗試重新連接(如果需要)期間,應用程序需要確保再次填充 mbedTLS SSL 上下文。
應用程序中啟用相應的配置:
#允許對 mbedTLS 使用動態緩沖策略
CONFIG_MBEDTLS_DYNAMIC_BUFFER = y
CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT = y
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA = y
重新運行應用程序以更新堆利用率統計信息,如下所示:
任務的內存使用率統計:
|| 任務 | 峰值 DRAM | 峰值 IRAM||
|| aws_iot_task | 26268 | 0 ||
|| tiT | 3648 | 0 ||
|| wifi | 30724 | 0 ||
系統的內存使用率統計:
|| 少可用DRAM | 小可用IRAM ||
|| 203648 | 76136 ||
從上面可以看出,我們從 aws_iot_task 任務中獲得了約 22KB 的內存,因此小可用 DRAM 數量也相應增加。
網絡特定(優化)
Wi-Fi / LwIP 配置
我們可以進一步優化 Wi-Fi 和 LwIP 配置以減少內存使用,但以犧牲一些性能為代價。我們將減少 Wi-Fi TX 和 RX 緩沖區,并且通過將一些關鍵代碼路徑從網絡子系統遷移到指令存儲器(IRAM)來嘗試平衡二者。
?從性能方面考慮,在默認網絡配置下,平均 TCP 吞吐量接近 ?20Mbps,但在下面的配置下,它將接近 ?4.5Mbps,這仍然足以滿足典型的 IoT 用例。
應用程序中啟用相應的配置:
#小的 Wi-Fi / lwIP 的配置
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM = 4
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM = 16
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM = 8
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED =
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE = 16
CONFIG_LWIP_TCP_SND_BUF_DEFAULT = 6144
CONFIG_LWIP_TCP_WND_DEFAULT = 6144
CONFIG_LWIP_TCP_RECVMBOX_SIZE = 8
CONFIG_LWIP_UDP_RECVMBOX_SIZE = 8
CONFIG_ESP32_WIFI_IRAM_OPT = Y
CONFIG_ESP32_WIFI_RX_IRAM_OPT = Y
CONFIG_LWIP_IRAM_OPTIMIZATION = Y
重新運行應用程序以更新堆利用率統計信息,如下所示:
任務的內存使用率統計:
|| 任務 | 峰值 DRAM | 峰值 IRAM||
|| aws_iot_task | 26272 | 0 ||
|| tiT | 4108 | 0 ||
|| wifi | 19816 | 0 ||
系統的內存使用率統計:
|| 少可用DRAM | 小可用 IRAM ||
|| 213712 | 62920 ||
從上面的日志可以看出,我們獲得了大約 9KB 的額外 DRAM 供應用程序使用。對總 IRAM 的影響(減少)是因為我們將關鍵代碼路徑從網絡子系統移到了該區域。
系統特定(優化)
使用 RTC(快速)內存(僅單核)
從前述的內存分解圖可以看到,有一個有用的 8KB RTC 快速內存(相當快),它一直處于空閑狀態并且沒有被充分利用。ESP-IDF 很快將具有啟用 RTC 快速內存以進行動態分配的功能。該選項存在于單核配置中,因為 RTC 快速內存只能由 PRO CPU 訪問。
已經確定的是 RTC 快速內存將用作第一個動態存儲范圍,并且大多數啟動,預調度程序代碼/服務都將占據該范圍。這樣就不會因為內存的時鐘速度(稍微慢一點)而影響應用程序代碼的性能。
由于對該區域沒有訪問限制,因此從功能上我們以后將其稱為 DRAM 。
讓我們使用此功能重新運行我們的應用程序并收集內存數據。
應用程序中啟用相應的配置:
#將RTC內存添加到系統堆中
CONFIG_ESP32_ALLOW_RTC_FAST_MEM_AS_HEAP = y
重新運行應用程序以更新堆利用率統計信息,如下所示:
任務的內存使用率統計:
|| 任務 | 峰值 DRAM | 峰值 IRAM||
|| aws_iot_task | 26272 | 0 ||
|| tiT | 4096 | 0 ||
|| wifi | 19536 | 0 ||
系統的內存使用率統計:
|| 少可用 DRAM | 小可用 IRAM ||
|| 221792 | 62892 ||
從上面的日志可以看出,我們獲得了 8KB 的額外 DRAM 供應用程序使用。
使用指令存儲器(IRAM,僅單核)
?到目前為止,我們已經看到了允許終端應用程序從 DRAM(數據內存)區域獲得更多內存的不同配置選項。沿著類似的路線繼續,應該注意到的是 IRAM(指令內存)還剩余充足的空間,但是由于 32 位地址和大小對齊的限制,它不能被用作通用目的。
如果訪問(加載或存儲)來自 IRAM 區域且大小未按字對齊,則處理器將生成 LoadStoreError(3) exception 異常;
如果訪問(加載或存儲)來自 IRAM 區域且地址未字對齊,則處理器將生成 LoadStoreAlignmentError(9) exception 異常。
在 ESP-IDF 的此特殊特性中,上面提到的未對齊訪問異常已通過相應的異常處理程序進行了修復,因此程序能夠正確的執行。但是,對于每個(受限制的)加載或存儲操作,這些異常處理程序多可能消耗 167 個 CPU 周期。因此,使用此功能時可能會導致性能顯著下降(與 DRAM 訪問相比)。
可以按以下方式使用此內存區域:
先通過使用堆分配器中稱為MALLOC_CAP_IRAM_8BIT 的 API,以使用特殊功能區域;
其次使用提供的鏈接器屬性 IRAM_DATA_ATTR 和 IRAM_BSS_ATTR 將 DATA/BSS 重定向到此區域。
局限性:
該存儲區預不能用于 DMA 目的;
該內存區域不能用于分配任務堆棧。
?在討論該內存區域的使用以及理解性能損失的同時,發現 TLS IN/OUT(根據我們的配置值緩沖區可以是 16KB/2KB)緩沖區是從該區域分配的潛在候選對象之一。在其中一項實驗中,將 TLS IN/OUT 緩沖區移至 IRAM 后,通過 TLS 連接傳輸 1 MB 文件的時間從約 3 秒增加到 5.2 秒。
也可以將所有 TLS 分配重定向到 IRAM 區域,但這可能會對性能產生更大的影響,因此此功能僅重定向大小大于或等于 TLS IN 或 OUT 小緩沖區的緩沖區(在我們的示例中,閾值為 2 KB)。
讓我們使用此功能重新運行并收集內存數據
應用程序中啟用相應的配置:
#允許將 IRAM 用作 8 位可訪問區域
CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY = y
CONFIG_MBEDTLS_IRAM_8BIT_MEM_ALLOC = y
重新運行應用程序中更新的堆利用率統計信息,如下所示:
任務的內存使用率統計:
|| 任務 | 峰值 DRAM | 峰值 IRAM||
|| aws_iot_task | 17960 | 21216 ||
|| tiT | 3640 | 0 ||
|| wifi | 19536 | 0 ||
系統的內存使用率統計:
|| 少可用DRAM | 小可用IRAM ||
|| 228252 | 40432 ||
從上面的日志可以看出,我們為應用程序又增加了約 7KB 的額外 DRAM 。請注意,即使我們已將所有超過 2KB 閾值的分配重定向到 IRAM ,但 DRAM 區域(另一個局部大值)仍發生許多較小的(同時發生的)分配,因此有效增益低于實際值。如果附加的性能影響是可以接受的,則可以將所有 TLS 分配重定向到 IRAM ,并從 DRAM 區域獲得至少 10-12KB 的內存。
總結
通過配置選項對各種功能進行選擇從而實現對應用程序的完全控制,是 ESP-IDF 的重要功能之一;
通過上述實踐,我們系統地評估了樂鑫科技代理公司藍牙wifi二合一模塊ESP-IDF 中的各種特性和配置選項,從而將終端應用程序的 DRAM (快的內存)預算提高了63 KB (小空閑 DRAM 大小從 ~160KB 增加到 ~223KB);
其中一些配置選項僅適用于單核配置(在標題本身中已進行了標記),但即使在雙核配置中,其余選項也可用于節省內存;
對性能無嚴格要求的模塊(如終端應用程序的日志記錄和診斷),還可以將指令存儲器(IRAM)用作 8 位可訪問區域;
一旦實現了所需的系統特性后,建議禁用上面實踐中使用的某些調試功能,例如堆任務跟蹤,以減少元數據開銷(并進一步增加內存預算)。
引用
修改后的 subscription_publish 示例以及終 sdkconfig.defaults 文件可以在 這里 找到;
這個應用程序應該基于此處 ESP-IDF 副本和特性分支來構建。