可攜式 C++ 程式設計¶
注意:此文件涵蓋需要在目標硬體環境中建置和執行的程式碼。這適用於核心執行階段,以及此儲存庫中的核心和後端實作。這些規則不一定適用於僅在開發主機上執行的程式碼,例如編寫或建置工具。
ExecuTorch 執行階段程式碼旨在可攜,並且應該可以為各種系統建置,從伺服器到手機到 DSP,從 POSIX 到 Windows 到裸機環境。
這表示它不能假設存在
檔案
執行緒
例外
stdout
,stderr
printf()
,fprintf()
一般的 POSIX API 和概念
同時,它也不能假設
64 位元指標
給定整數類型的大小
char
的正負號
為了將二進位檔案大小保持在最小,並嚴格控制記憶體分配,程式碼可能無法使用
malloc()
、free()
new
、delete
大多數
stdlibc++
類型;特別是那些管理自身記憶體的容器類型,例如string
和vector
,或者記憶體管理包裝類型,例如unique_ptr
和shared_ptr
。
為了幫助降低複雜度,程式碼不得依賴任何外部依賴項,除了
flatbuffers
(用於.pte
檔案的反序列化)flatcc
(用於事件追蹤序列化)Core PyTorch (僅適用於 ATen 模式)
平台抽象層 (PAL)¶
為了避免假設目標系統的功能,ExecuTorch 執行期允許客戶端覆寫其平台抽象層 (PAL) 中的底層函數,PAL 在 //executorch/runtime/platform/platform.h
中定義,以執行以下操作:
獲取目前時間戳記
列印日誌訊息
使系統 panic
記憶體分配¶
執行期程式碼不應使用 malloc()
或 new
,而應使用客戶端提供的 MemoryManager
(//executorch/runtime/executor/memory_manager.h
) 來分配記憶體。
檔案載入¶
客戶端不應直接載入檔案,而應提供已載入資料的緩衝區,或包裝在類似 DataLoader
的類型中。
整數類型¶
ExecuTorch 執行期程式碼不應對原始類型(例如 int
、short
或 char
)的大小做任何假設。 例如,C++ 標準僅保證 int
至少為 16 位寬。 並且 ARM 工具鏈將 char
視為無符號,而其他工具鏈通常將其視為有符號。
相反,執行期 API 使用一組更可預測但仍是標準的整數類型
類似於
uint64_t
、int32_t
的<cstdint>
類型; 這些類型保證位元寬度和正負號,與架構無關。 當您需要非常特定的整數寬度時,請使用這些類型。size_t
用於事物計數或記憶體偏移量。 保證size_t
足夠大,可以表示任何記憶體位元組偏移量; 也就是說,它將與目標系統的本機指標類型一樣寬。 優先使用它而不是uint64_t
來進行計數/偏移量,以便 32 位元系統無需為 64 位元值的額外開銷付費。ssize_t
用於某些 ATen 相容性情況,其中Tensor
會傳回帶正負號的計數。 盡可能優先使用size_t
。
浮點運算¶
並非每個系統都支援浮點運算:有些系統甚至未在其工具鏈中啟用浮點模擬。 因此,核心執行期程式碼不得在執行期執行任何浮點運算,儘管可以簡單地建立或管理 float
或 double
值(例如,在 EValue
中)。
核心執行期之外的 Kernel 允許執行浮點運算。 儘管有些 Kernel 可能選擇不執行,以便它們可以在沒有浮點支援的系統上執行。
日誌記錄¶
ExecuTorch 執行期不使用 printf()
、fprintf()
、cout
、cerr
或類似 folly::logging
或 glog
的程式庫,而是提供 ET_LOG
介面(在 //executorch/runtime/platform/log.h
中)和 ET_CHECK
介面(在 //executorch/runtime/platform/assert.h
中)。 這些訊息是使用 PAL 中的 hook 列印的,這表示客戶端可以將它們重新導向到任何底層日誌記錄系統,或者如果可用,只需將它們列印到 stderr
即可。
日誌記錄格式可移植性¶
固定寬度整數¶
當您有類似這樣的日誌語句時
int64_t value;
ET_LOG(Error, "Value %??? is bad", value);
您應該在 %???
部分放置什麼來匹配 int64_t
? 在不同的系統上,int64_t
typdef 可能是 int
、long int
或 long long int
。 選擇像 %d
、%ld
或 %lld
這樣的格式可能在一個目標上有效,但在另一個目標上會中斷。
為了具有可移植性,執行期程式碼使用 <cinttypes>
中的標準(但不得不承認很尷尬)輔助巨集。 每個可移植整數類型都有一個對應的 PRIn##
巨集,例如
int32_t
->PRId32
uint32_t
->PRIu32
int64_t
->PRId64
uint64_t
->PRIu64
有關更多訊息,請參閱 https://en.cppreference.com/w/cpp/header/cinttypes
這些巨集是文字字串,可以與格式字串的其他部分串連,例如
int64_t value;
ET_LOG(Error, "Value %" PRId64 " is bad", value);
請注意,這需要分割文字格式字串(額外的雙引號)。 它還需要在巨集之前加上 %
。
但是,透過使用這些巨集,您可以確保工具鏈會針對該類型使用適當的格式模式。
類型轉換¶
有時候,尤其是在跨越 ATen 和 lean 模式的程式碼中,值的類型本身在不同的建置模式下可能會有所不同。在這些情況下,將值轉換為 lean 模式類型,例如
ET_CHECK_MSG(
input.dim() == output.dim(),
"input.dim() %zd not equal to output.dim() %zd",
(ssize_t)input.dim(),
(ssize_t)output.dim());
在這種情況下,Tensor::dim()
在 lean 模式下返回 ssize_t
,而 at::Tensor::dim()
在 ATen 模式下返回 int64_t
。由於它們在概念上都返回(有符號)計數,因此 ssize_t
是最合適的整數類型。int64_t
也能夠工作,但它會不必要地要求 32 位元系統在 lean 模式下處理 64 位元的值。
這是唯一需要類型轉換的情況,即當 lean 和 ATen 模式不一致時。否則,請使用與類型匹配的格式模式。