使用共享記憶體時的陷阱與最佳實踐 - 先整理同步、可見性、壽命、ABI、安全性
· 小村 豪 · Shared Memory, IPC, Concurrency, C++, C#, Windows 開發
影像 frame、檢查結果、時序日誌、行情資訊、巨大緩衝。 想在同一台機器內以低延遲交換大型資料時,共享記憶體相當有吸引力。
但是這裡稍微危險的是,共享記憶體以 「快速 IPC」 的面貌接近。 實際上共享記憶體是 「能減少複製代價但會把整合性責任推回應用程式側的 IPC」。
- 快
- 靈活
- 但 protocol 是自製
- 出事時症狀華麗
大致這 4 點套組。
本文以 Windows 的 file mapping 和 POSIX shm_open / mmap 為念頭,整理 實務上使用共享記憶體的卡點以及降低事故率的設計。
C/C++ 或 C# 的 MemoryMappedFile,本質幾乎相同。1
1. 先講結論(一句話)
先相當粗略但實務上有用的說法如下。
- 共享記憶體是把 相同位元組序列 讓多個行程可見的機制,不是同步本身23
- 快的是大型資料 在同一機器內交換時。只小型控制訊息的話,pipe / socket / named pipe / queue 更輕鬆的情況相當多
- 共享記憶體中,可見 和 可安全讀取 是不同問題
- 不要把
volatile當設計根基。原子性、順序、等待 要分開思考45 - 原生指標、
HANDLE、file descriptor、std::string、std::vector、std::mutex直接放進去,通常之後會哭 - 放到共享記憶體的資料靠向 固定寬度整數 + 明確佈局 + 有版本的標頭 比較安全
- 光在 標頭放 magic / version / size / state / generation / heartbeat 事故調查的容易度就相當不同
- 共享記憶體的難點不是速度,是 初始化、壽命、復原、權限、ABI
- Windows 是
CreateFileMapping/OpenFileMapping/MapViewOfFile,POSIX 是shm_open/ftruncate/mmap為骨架63 - 最不易出事的是從 SPSC(single-producer single-consumer)的環形緩衝 或 雙緩衝 開始
簡言之 共享記憶體快但粗略使用會得「好像自動同步病」。避免這個是最初的勝負。
2. 共享記憶體共享什麼、不共享什麼
共享記憶體粗略說是把 相同的物理頁 映射到多個行程的虛擬位址空間的機制。
Windows 用 file mapping object 和 view,POSIX 把 shared memory object mmap。273
這裡重要的是 2 點。
- 共享的是內容的位元組序列,不是虛擬位址本身
- coherent 和 同步 是不同事
Windows 的文件也說從同 file mapping object 做的 view 在同一時點 coherent。 但那不代表 讀者隨時都能讀到一致的更新完成記錄。8
例如,
- writer 打算寫
length - 接著寫
payload - 接著寫
ready flag
順序寫入,但 reader 側若無任何同步就讀,可能看到 新的 length 和舊的 payload 的組合。
共享記憶體不會自動修正。
也就是共享記憶體共享的是 位元組。 不共享的是 意義、順序、完成通知、復原方針。 這一帶全部需要自己設計。
3. 共享記憶體適合的場面 / 不適合的場面
| 場面 | 適合否 | 理由 |
|---|---|---|
| 在同一機器內傳遞大的 frame 或緩衝 | 適合 | 容易減少複製次數 |
| 高頻感測器值、影像、音訊、行情等 | 適合 | 容易瞄準低延遲・高吞吐 |
| 只交換小型命令或回應 | 不太適合 | 控制所需的同步成本相對重 |
| 與其他機器交換 | 不適合 | 共享記憶體基本上同機前提 |
| 不同語言、不同版本長期共存 | 困難 | 需要 ABI 和版本設計 |
| 也需要持久化 | 視目的 | file-backed mapping 有力但持久化與 IPC 的職責易混 |
實務上 控制走訊息系、資料本體走共享記憶體 的分離相當強。 例如,
- UI 行程 → worker 行程通知「使用下一張 frame」用 event / pipe / socket
- 實際 frame 本體用共享記憶體
這種構成。 相當和平。
4. 最初該決定的 4 件事
設計共享記憶體時最初該決定的是以下 4 件。
4.1 分開 control plane 和 data plane
先決定放什麼到 shared memory。
- data plane: 影像、音訊、記錄序列、批量資料
- control plane: 開始、停止、錯誤、重連、重初始化、通知
光分開這 2 個,shared memory 側的設計就相當單純。
4.2 縮小並行模型
- SPSC: 1 producer / 1 consumer
- MPSC: 多 writer / 1 consumer
- SPMC: 1 writer / 多 reader
- MPMC: 多 writer / 多 reader
難度大致以此順序上升。 從一開始用 MPMC 相當勇猛。通常之後會出記憶體順序的妖怪。
4.3 決定擁有者和壽命
- 誰建立
- 誰初始化
- 誰刪除
- 參與者中途掉落時誰復原
這裡曖昧,每次啟動順或重啟空氣都變混濁。
4.4 決定 ABI 和版本
- 佈局
- 型大小
- alignment
- reserved 區域
- version / feature flags
- 相容性的有無
shared memory 不是 API 而是 ABI(binary interface) 的話題。 這裡粗略,就會源碼相容但只在執行時壞的討厭事故。
5. 常見陷阱
5.1 不同步
最多的是這個。
「都看同一個記憶體,寫了應該能讀」
讀得到的情況有。 但那不代表能以 正確的時機、正確的單位、正確的順序 讀到。
Windows 和 POSIX 都以 共享記憶體的存取與別的同步手段組合 為前提。 Windows 的說明也寫著對共享 view 的存取要用 mutex / semaphore / event 等協調。2 POSIX 的說明也說對 shared memory 的存取需要同步。9
5.2 想用 volatile 解決
volatile 不是拯救共享記憶體設計的魔法。
至少 atomicity 和 mutual exclusion 是不同問題。45
例如放 volatile bool ready; 用 busy loop 的設計,
- 浪費 CPU
- payload 和 ready 的順序保證曖昧
- 不可攜
- 易撈到中途狀態
通常沒好事。
再者 Windows 的 WaitOnAddress 是 同行程內 thread 用。
作為跨行程等待機制不應考慮,比較安全。10
5.3 讓人讀到中途狀態
共享記憶體出事時的外觀相當平常。
- 只標頭新
- 只 payload 舊
- 只長度更新完成
- 2 個欄位的組合壞了
只原子更新單一 scalar 的話事情相對單純,但公開 由多欄位構成的記錄 的話,需要 commit 步驟。
典型是以下之一。
- mutex 整個守
- 做成 雙緩衝 最後切換「現在的有效緩衝編號」
- 做成 環形緩衝 每個 slot 持有 state / sequence
- 1 writer / 多 reader 的話用 sequence counter 取 snapshot
光「最後立 ready flag」,該 flag 以何種記憶體順序寫 / 讀 不決定,設計上仍嫩。 共享記憶體中,公開時機本身就是協議。
5.4 直接放指標或複雜物件
這相當頻繁。
- 原生指標
HANDLE- file descriptor
std::stringstd::vectorstd::unordered_mapstd::mutexCRITICAL_SECTION
把這些直接放到 shared memory,想從別行程使用。通常會開始小地獄。
理由單純,虛擬位址或 process-local 的資源只在該 process 的脈絡有意義。 Windows 的 view,就算同 mapping 在別 process map,虛擬位址也未必一致。711
所以需要參照就用 相對於基址的 offset 持有。
typedef struct ShmRef {
uint64_t offset; // 從 segment 開頭的相對位置
uint32_t length;
uint32_t kind;
} ShmRef;
這樣各 process 可以 base + offset 轉換成自己的位址。
5.5 ABI 壞了
shared memory 是 二進位的約定,不是程式碼。 也就是以下的差異全部有效。
int/long的大小bool的表現enum的 underlying typewchar_t的大小- 32bit / 64bit 的差
#pragma pack- compiler / language 的差異
- alignment / padding
- little-endian / big-endian
同機內 endianness 通常對齊,但光是 ARM64 對應或 mixed toolchain 進來也相當普遍地偏移。
所以放到 shared memory 的結構強烈建議以下。
uint32_t/uint64_t等的 固定寬度整數- 明確的 padding / reserved
- header 中有
version,header_size,record_size,total_size - 必要時
static_assert(sizeof(...)) - 不放 non-trivial object
5.6 初始化競爭
共享記憶體容易被「製作側應該已初始化」的想法搞壞。
Windows 中 CreateFileMapping 遇到既有名稱會 回傳既有物件,GetLastError() 能知 ERROR_ALREADY_EXISTS。
pagefile-backed 的 mapping 初始頁以 0 開始。8
POSIX 中新 shared memory object 一開始長度為 0,用 ftruncate 加大小。新確保的位元組為 0 初始化。O_CREAT | O_EXCL 的 create 是原子的。3
不知這差異,
- open 就立即用
- 沒初始化完成旗標
- 參與者同時初始化
- 不看 version mismatch
這樣做會依啟動順序壞。
至少在標頭放以下 state 比較好。
INITIALIZINGREADYBROKEN
然後 只讓 creator 初始化,joiner 等 READY。
光這作法世界就會安靜很多。
5.7 不考慮當機復原
writer 在更新共享資料中掉了怎麼辦。 這裡未定義就上正式,故障時表情會突然嚴肅。
Windows 的 mutex 所有 thread 不 release 就結束會變 abandoned,wait 側會收到 WAIT_ABANDONED。意思是 共享資源可能是不確定狀態。12
POSIX 的 robust mutex 也是 owner 死時 EOWNERDEAD 回傳,修復後呼叫 pthread_mutex_consistent()。1314
重要的是這裡不要「總之繼續」。 復原至少需要以下之一。
- generation 號碼
- 最終 commit 完成的 sequence
- heartbeat
- dirty / clean flag
- journal 式的 2 段 commit
- 損壞時的全重初始化步驟
5.8 false sharing 與快取行競爭
常說共享記憶體快。 但 hot 計數器擠在同個 cache line,CPU 間 line 跑來跑去,會景氣地變慢。
典型例是,
- producer 更新
write_index - consumer 更新
read_index - 兩者在同一 cache line
這個。
此情況,
- hot field 分到別的 cache line
- 高更新頻率和低更新頻率的欄位分開
- 意識 1 writer 1 cache line
光這些就相當改變。 常出現對齊 64 bytes 的話題,但 64 bytes 在多數 CPU 上常見只是常見值,不是絕對法則 的心情看就好。
5.9 輕視名稱・權限・安全性
named shared memory 方便,但名稱和權限粗略會出事。
Windows 中,
- 有
Global\和Local\namespace - 從 session 0 以外 新建
Global\的 file mapping 需要SeCreateGlobalPrivilege - object name 與 event / semaphore / mutex / waitable timer / job 共用 namespace
也就是,
- 用
"Global\\MyApp"感覺 service 和 desktop app 能共享 - 但權限失敗
- 而且之前已有同名 mutex,變成
ERROR_INVALID_HANDLE
這種非常 Windows 風的泥濘會出。
POSIX 側也輕視 shm_open 的 mode 或 umask,會意外看得太廣或反而開不了。3
shared memory 不是 只是記憶體所以安全。 從有讀權限的 process 看得很自然。 放機密資訊的話,與一般記憶體一樣要以 paging / swap / dump / 權限的脈絡思考。
5.10 粗略地改大小和升級
「事後想稍微擴大」共享記憶體是相當危險的需求。
實務上 大小在該世代中不變 比較安全。 需要擴充的話,
- 做新的 version / name / generation 的 segment
- 切換參與者
- 關舊 segment
事故率比較低。
5.11 把通知全部塞進 shared memory
常見的是,
- 共享記憶體寫
ready = 1 - 對方
while (!ready) Sleep(1);
這個。
一開始會動。 但後來會以
- 浪費 CPU
Sleep(1)讓延遲抖動- 難察覺漏掉
- 逾時或終止通知難寫乾淨
這樣的形式回來。
共享記憶體靠向 資料面,通知逃到 能等的 primitive 較好。
- Windows: event / semaphore / mutex / named pipe 等217
- POSIX: semaphore / process-shared mutex + condvar 等1819
5.12 以為「這樣也能跟其他機器共享」
會有想用 file-backed mapping map 網路上的共享檔,這樣也許能跨機器 shared memory 化的瞬間。
這裡危險。
Windows 的 CreateFileMapping 的說明也說 對 remote file 不保證 coherence。
同頁 2 台 writable map,各自只看得到自己的寫,磁碟更新時也不 merge。8
共享記憶體基本上是 同機 的機制。 跨機器的話直接選 socket / RPC / message broker 比較保持理智。
6. 最佳實踐
6.1 分開 control plane 和 data plane
shared memory 只放 批量資料,通知和狀態轉移逃到別通道。
- shared memory: frame, sample, batch, snapshot
- event / semaphore / pipe / socket: ready, consumed, stop, error, reconnect
這分離比起效能,先讓 設計的透視 變好。
6.2 開頭放固定標頭
至少強烈建議在開頭放這種標頭。
typedef struct SharedHeader {
uint32_t magic;
uint16_t abi_version;
uint16_t header_size;
uint32_t state; // 0=initializing, 1=ready, 2=broken
uint32_t flags;
uint64_t total_size;
uint64_t generation;
uint64_t heartbeat_ns;
uint64_t payload_offset;
uint64_t payload_size;
uint64_t write_seq;
uint64_t read_seq;
uint8_t reserved[64];
} SharedHeader;
要點,
magic擋掉不同物件或未初始化abi_version和header_size擋掉佈局差異state擋掉初始化中generation偵測重建heartbeat看生死reserved為將來擴充留退路
。
shared memory 辛苦的是「難看見發生什麼」。 所以 觀測用 metadata 從最初就持有。
6.3 用偏移參照
參照不用 pointer 用 offset 持有。
- 用
base + offset解析 - 放
offset + length的範圍檢查 - 決定 invalid value 的 sentinel
光這些 address mismatch 系的事故就大幅減少。
6.4 縮小並行模型
shared memory 的 writer 多,突然就難。 所以最初這兩個較強。
- SPSC ring buffer
- 1 writer / 多 reader 的 snapshot
需要多 writer 的話,
- 只 enqueue 用 lock-free / atomic
- 實資料更新集中到 1 個 consumer
這樣 減少整合性的責任點 通常較成功。
6.5 明示 commit protocol
「從哪個瞬間可以讀」用文字說明不了的設計很危險。
例如雙緩衝,
- 寫到非公開側緩衝
- 確定校驗和或長度
- 以 release 切換 active buffer index
- reader 以 acquire 讀 active index
- 讀完後確認 index 沒變
這樣決定 公開的儀式。
6.6 大小按世代固定
比起 resize in place,
name = MyShm.v3abi_version = 3generation = 42
這樣切世代比較易維護。
共享記憶體不像 API 那樣「呼叫時做型別檢查」。 所以 不破壞一次決定的 ABI 很重要。
6.7 放入可觀測性
至少有以下會有幫助。
- 最終更新時間
- 最終成功 sequence
- drop 數 / overwrite 數
- version mismatch 數
- attach / detach 數
- last error code
- heartbeat
shared memory 壞時通常日誌薄。 自行放 counters,故障對應會輕鬆。
6.8 先做異常系測試
只正常系不夠。至少看以下。
- writer 更新中強制終止
- reader 延遲讓 ring 溢出
- version mismatch 連線
- 32bit / 64bit 混在
- 跨 session open
- 權限不足
- 先行程序保留舊世代就重啟
- huge data 連續傳送時 cache miss / NUMA 影響
shared memory 比起正常系,破壞方式測試 的價值更大。
7. Windows 和 POSIX 看的重點
| 觀點 | Windows | POSIX |
|---|---|---|
| 建立 / open | CreateFileMapping / OpenFileMapping / MapViewOfFile6 |
shm_open / ftruncate / mmap3 |
| 非磁碟連動的共享 | 指定 INVALID_HANDLE_VALUE 的 pagefile-backed mapping68 |
POSIX shared memory object + mmap3 |
| 初始值 | pagefile-backed pages 以 0 初始化8 | 新 object 長度 0。新確保位元組以 0 初始化3 |
| 同步 | mutex / semaphore / event / interlocked 等25 | process-shared mutex / condvar / semaphore2018 |
| 不該跨行程用的 | CRITICAL_SECTION, WaitOnAddress2110 |
PTHREAD_PROCESS_PRIVATE 原樣的 mutex / condvar2019 |
| owner death | WAIT_ABANDONED12 |
robust mutex + EOWNERDEAD / pthread_mutex_consistent()1314 |
| name 的刪除 | 最終 handle / view 釋放後消失28 | shm_unlink 刪名稱。參照還在則實體保留到最後2223 |
| namespace / 權限 | Global\ / Local\、ACL、SeCreateGlobalPrivilege1524 |
mode, umask, 命名空間, O_CREAT|O_EXCL3 |
C# 的 MemoryMappedFile 本質上也是 Windows 的 file mapping 的封裝。
所以,
- 以同名 open
- 另外用 mutex / event
- 對 view 以明確佈局讀
- 不直接放物件參照
這些基本不變。1
8. 先看的檢核表
- 是否真的需要共享記憶體。同機大型資料 嗎
- 是否分開 control plane 和 data plane
- 並行模型能否降到 SPSC / 1 writer 多 reader
- 開頭標頭是否有 magic / version / size / state / generation / heartbeat
- 是否沒放 pointer /
HANDLE/ fd / STL object /std::mutex - 是否有 reader 不看中途狀態的 commit protocol
- 初始化者是否定在 1 人
- 異常終止時的 復原步驟 是否有
- 名稱和權限是否明示
Global\是否真的必要- 是否以 resize in place 為前提
- 是否試過 writer kill / reader stall / version mismatch / 權限不足
9. 總結
共享記憶體好好用相當強。 特別,
- 影像
- 音訊
- 感測器序列
- 大型批次
- 高頻 snapshot
這類 同機大型資料 真的有效。
但共享記憶體的本體比起「快」更是 責任的移轉。 減少複製或跨核心訊息的代價是,
- 同步
- 可見性
- 初始化
- ABI
- 復原
- 權限
- 可觀測性
要這邊承擔。
所以最初的 1 個這樣做比較安全。
- SPSC ring buffer 或雙緩衝
- 開頭固定標頭
- offset 參照
- 用別通道通知
- 有 version / generation / heartbeat
- 有異常系測試
從這個形式開始,shared memory 會相當自然的工具。 相反地,從一開始當「什麼都能放的快速共用記憶體」處理,會漸漸不是應用程式而是考古學。
10. 參考資料
- Windows: file mapping 和 named shared memory 的基本682
- Windows: namespace / security / synchronization1524512
- POSIX:
shm_open,shm_unlink,mmap, process-shared / robust synchronization322162013 - .NET:
MemoryMappedFile的概要1
-
Microsoft Learn, “記憶體對應檔案” https://learn.microsoft.com/en-us/dotnet/standard/io/memory-mapped-files / Microsoft Learn, “MemoryMappedFile 類別” https://learn.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles.memorymappedfile?view=net-10.0 ↩ ↩2 ↩3
-
man7.org, “shm_open(3)” https://man7.org/linux/man-pages/man3/shm_open.3.html ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Microsoft Learn, “/volatile (volatile Keyword Interpretation)” https://learn.microsoft.com/en-us/cpp/build/reference/volatile-volatile-keyword-interpretation?view=msvc-170 / Microsoft Learn, “volatile (C++)” https://learn.microsoft.com/en-us/cpp/cpp/volatile-cpp?view=msvc-170 ↩ ↩2
-
Microsoft Learn, “Interlocked Variable Access” https://learn.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access / Microsoft Learn, “MemoryBarrier function” https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-memorybarrier ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, “Creating Named Shared Memory” https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, “Scope of Allocated Memory” https://learn.microsoft.com/en-us/windows/win32/memory/scope-of-allocated-memory ↩ ↩2
-
Microsoft Learn, “CreateFileMappingA function” https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
man7.org, “POSIX Shared Memory” training slides https://man7.org/training/download/ipc_pshm_slides-mkerrisk-man7.org.pdf ↩
-
Microsoft Learn, “WaitOnAddress function” https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitonaddress ↩ ↩2
-
Microsoft Learn, “MapViewOfFileEx function” https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex / Microsoft Learn, “MapViewOfFile function” https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile ↩
-
Microsoft Learn, “Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/mutex-objects ↩ ↩2 ↩3
-
man7.org, “pthread_mutex_lock(3p)” https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html / man7.org, “pthread_mutexattr_setrobust(3)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html ↩ ↩2 ↩3
-
man7.org, “pthread_mutex_consistent(3)” https://man7.org/linux/man-pages/man3/pthread_mutex_consistent.3.html / man7.org, “pthread_mutex_consistent(3p)” https://man7.org/linux/man-pages/man3/pthread_mutex_consistent.3p.html ↩ ↩2
-
Microsoft Learn, “Kernel object namespaces” https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces ↩ ↩2 ↩3
-
man7.org, “mmap(2)” https://man7.org/linux/man-pages/man2/mmap.2.html ↩ ↩2
-
Microsoft Learn, “Using Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/using-mutex-objects ↩
-
man7.org, “sem_init(3)” https://man7.org/linux/man-pages/man3/sem_init.3.html / man7.org, “sem_init(3p)” https://man7.org/linux/man-pages/man3/sem_init.3p.html ↩ ↩2
-
Microsoft Learn, “Critical Section Objects” https://learn.microsoft.com/en-us/windows/win32/sync/critical-section-objects ↩
-
man7.org, “shm_unlink(3p)” https://man7.org/linux/man-pages/man3/shm_unlink.3p.html ↩ ↩2
-
man7.org, “shm_open(3)” (shm_unlink semantics) https://man7.org/linux/man-pages/man3/shm_open.3.html ↩
-
Microsoft Learn, “File Mapping Security and Access Rights” https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping-security-and-access-rights ↩ ↩2
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 應用安全處理子行程的 checklist - Job Object、結束傳播、標準輸入輸出、watchdog 的最佳實務
在 Windows 應用上安全處理子行程,關鍵不在挑啟動 API,而是設計行程樹的擁有者與結束流程。本文整理 Job Object 的 KILL_ON_JOB_CLOSE、GUI 與 console 的 graceful shutdown、stdio 平行抽乾與 EOF、w...
用 Native AOT 把 C# 做成原生 DLL 的方法 - 用 UnmanagedCallersOnly 從 C/C++ 呼叫
從現有 C/C++ 應用程式以 in-process 方式呼叫 C# 邏輯時,本文示範以 Native AOT 將類別庫發佈為原生 DLL,並用 UnmanagedCallersOnly 公開 cdecl 進入點。透過 handle、錯誤碼與扁平 C ABI 設計交界面,整...
序列通訊應用的陷阱 - 先釐清 1 byte 單位、逾時、流控、重連、USB 轉換、UI 凍結
從設備整合與儀器控制的實作現場出發,整理序列通訊應用最容易踩到的陷阱。把訊息邊界、逾時語意、流控線設定、single writer、session 重連與 hex dump 日誌一一拆開,幫助讀者把「偶爾才壞」的 byte 序列處理改造成可預測且容易調查的結構。
Windows Forms、WPF、WinUI 該選哪個 - 新規開發、既有資產、發佈、UI 表現的判斷表
從既有資產的規模、畫面是表單中心還是表現力中心、現代 Windows UI 是否為產品要件、發佈與運營怎麼跑這四個觀點,整理 WinForms、WPF、WinUI 該選哪個的判斷表,並提醒只想用 Windows App SDK 不必全面遷移到 WinUI。
各種程式語言的速度測量與比較應該怎麼做 - C# / C++ / Java / Go 以相同條件比較的實踐指南
整理在公平的條件下比較 C#、C++、Java、Go 速度的具體做法,包含區分啟動時間與穩態的 throughput、區分 cold 與 warm、固定建置條件與環境、以共通輸入確認正確性、看中位數與分布等流程,理解到不應以一個 bench 決定語言優劣的視角。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。