群益API 程式開久了記憶體與磁碟一直漲?排查方法與元件 log 的處理
程式開著開著,記憶體一路往上長,開一天就到好幾 GB。硬碟也越來越滿,找到元件的 log 資料夾一看,已經好幾百 MB。
群益API 記憶體或磁碟一直漲的時候,第一步不是改程式,而是分清楚到底是誰在長。
先說清楚本文的定位:官方文件沒有關於記憶體增長或 log 清理的說明。 以下是通用的排查方法,以及我們自己環境裡的實測,不是官方規格。

目錄
群益API 記憶體一直漲?先分清楚是誰在長
程式的記憶體至少有兩種:.NET 的受控堆積(你的 C# 物件住的地方),以及原生記憶體(COM 元件等非受控程式碼用的)。兩種的處理方式完全不同,所以要先分開看。
第一步:同時看兩個數字。
- 私有記憶體:可以用
Process.PrivateMemorySize64取得,微軟文件的說明是「取得為相關聯進程配置的私用記憶體數量」,讀取前要先呼叫Refresh()才是最新值。 - 受控堆積:可以用
GC.GetTotalMemory取得,微軟文件的說明是擷取堆積大小(不含碎片)。
私有記憶體一直漲、受控堆積不動,長的就是原生記憶體,不是你的 .NET 物件。
第二步:定期強制回收一次,比較前後。 GC.GetTotalMemory(true) 會等垃圾回收發生後再回傳。回收之後私有記憶體還是降不下來,也指向原生記憶體。
要注意,微軟文件也寫明,垃圾回收器並不保證所有無法存取的記憶體都會被收集——所以這一步是用來「比較」,不是用來「修好」。
排除法怎麼做?一次關一類呼叫
確認是原生記憶體在長之後,下一步是找出是哪一類呼叫造成的。
第三步:一次關掉一類呼叫,各跑一段時間。 例如分別關掉報價訂閱、帳務查詢、回報處理,看關掉哪一類之後就不漲了。
第四步:每一輪的使用情境要相同。 視窗開幾個、訂了多少商品、查詢頻率多少,前後要一致,對照才有意義。
找到是哪一類呼叫之後,再決定怎麼處理——例如降低那一類呼叫的頻率、改成需要時才呼叫。先確定來源,再決定改哪裡。
元件的 log 放在哪裡?會自己清嗎?
元件會自己寫 log。官方 SKCenterLib_SetLogPath 的說明是:
設定LOG檔存放路徑。預設LOG存放於執行之應用程式下,如要變更LOG路徑,此函式需最先呼叫。
備註則寫:
預設LOG位置為以下(與預設SKCOM.dll 同一層),資料夾名稱為CapitalLog
說明寫「執行之應用程式下」,備註寫「與預設SKCOM.dll 同一層」——應用程式的目錄和元件的目錄不一定是同一個。請在自己的電腦上找一次 CapitalLog 資料夾實際在哪裡。
官方沒有說明 log 會不會自動清理。 在我們的環境裡,元件的 log 大約每天 44MB,不會自動清理,累積到數百 MB。這個數字跟你開的視窗數、訂的商品數有關,只當量級參考。
所以要自己定期清理舊的 log 檔,保留幾天由你決定。要把 log 放到別的位置,照官方說明最先呼叫 SKCenterLib_SetLogPath。
log 為什麼會一天暴增數百 MB?
這是我們自己踩過的,原因在我們的程式,不在元件。
元件會把每一次 RequestStocks 呼叫寫進 log——這本身是正常行為。問題是,我們的程式當時每次訂閱異動都會把整籃商品重送一次;換一整籃商品時逐檔處理,就變成整籃重送了很多次。結果那一天的報價 log,一個小時就到了數百 MB。
做法是:把一連串的訂閱異動合併成一次送出。 在一段批次區間裡只記下「有變動」,區間結束時才送一次整籃。換一整籃只送一次,log 就不會暴增,呼叫次數也少了。
所以看到 log 暴增,先檢查自己的程式是不是在重複呼叫。
群益API 記憶體之外:自己的快取也要會清
長時間開著的程式,自己保存的 Tick、K 棒也會一直長。
快取要有「清掉」的時機。〈群益API Tick 回補是什麼〉引過官方的說法:
T盤切換T+1盤,不保留前一盤資料,開發者需自行清除前一盤資料。
時段切換,就是一個清掉的時機。 也可以另外設一個上限當保險,但正常情況應該靠時段切換清掉,不要靠上限。
群益API 記憶體與 log 的 4 條處理原則+程式碼
- 先分清楚是誰在長:私有記憶體對照受控堆積;強制回收前後比;排除法一次關一類呼叫,情境要相同。
- 元件的 log 自己定期清;要換位置,最先呼叫
SKCenterLib_SetLogPath。 - 訂閱異動合併成一次送出,不要逐檔重送整籃。
- 自己的快取要有清掉的時機,時段切換是其中一個。
// 要變更 log 位置:官方說明此函式需最先呼叫(我們的理解是放在登入之前)
m_pSKCenter.SKCenterLib_SetLogPath(@"D:\YOUR_LOG_FOLDER");
// 訂閱異動合併成一次送出:批次區間內只記下「有變動」,結束時才送一次整籃
bool _basketDirty = false;
int _batchDepth = 0;
void BeginBatch() { _batchDepth++; }
void EndBatch() { if (--_batchDepth == 0) FlushIfDirty(); } // 最外層結束時才送
void Subscribe(string stockNo)
{
AddToBasket(stockNo);
_basketDirty = true;
if (_batchDepth == 0) FlushIfDirty(); // 不在批次區間裡:照常立刻送
}
void FlushIfDirty()
{
if (!_basketDirty) return; // 沒有變動就不送
_basketDirty = false;
SendWholeBasket(); // 整籃只送一次(RequestStocks)
}
// 使用:一次換一整籃
// BeginBatch(); foreach (var s in newSymbols) Subscribe(s); EndBatch();幾點補充:
AddToBasket、SendWholeBasket是示意名稱,片段只示範「合併」的形狀。實際程式要讓BeginBatch與EndBatch成對執行(例如用try/finally),片段為了簡短沒有寫。- 清理 log 檔的程式碼沒有放,那屬於你自己的維運設計。
- 整籃怎麼送、有什麼限制,見〈群益API 訂閱額度有多少〉。
常見問題
強制回收了,群益API 記憶體還是降不下來,是什麼原因?
先比較私有記憶體與受控堆積。如果受控堆積沒有跟著漲、強制回收之後也降不下來,長的部分就不是可以被回收的受控物件。接著用排除法,一次關一類呼叫,找出是哪一類造成的。
CapitalLog 資料夾可以刪嗎?
官方沒有說明 log 會不會自動清理;在我們的環境裡它不會。可以自己定期清理舊的 log 檔,保留幾天由你決定。
log 一小時就幾百 MB,是元件出問題了嗎?
先檢查自己的程式。元件會把每一次 RequestStocks 呼叫寫進 log,呼叫次數多,log 就大。我們踩過的情況是程式逐檔重送整籃,改成把訂閱異動合併成一次送出就解決了。
想把 log 放到別的位置,要怎麼做?
呼叫 SKCenterLib_SetLogPath。官方說明這個函式需最先呼叫。
風險揭露
期貨與選擇權屬高槓桿商品,價格波動可能造成超過原始保證金的損失,交易人須自負交易責任。本文為程式開發技術教學,說明長時間執行時記憶體與磁碟增長的排查方式,不構成投資建議,也不保證任何交易結果。程式化交易並不降低市場風險,程式因資源耗盡而變慢或中止,可能讓你在市場變化時無法即時處理委託與部位。
投資人教育資源
| 機構 | 資源 |
|---|---|
| 臺灣期貨交易所(TAIFEX) | 期貨及選擇權數位學習網 |
| 證券暨期貨市場發展基金會 | 證券期貨市場教育推廣 |
| 金融智慧網 | 金管會金融知識平台 |
| 中華民國期貨業商業同業公會 | 期貨業法規與宣導 |
| 證券投資人及期貨交易人保護中心 | 投資人保護與申訴 |
延伸閱讀
- 〈群益API Tick 回補是什麼〉——時段切換時要自己清除前一盤資料。
- 〈群益API 訂閱額度有多少〉——整籃訂閱的額度與限制。
- 〈群益API 報價停止更新怎麼辦〉——自救重送訂閱也是呼叫次數,一樣要有退避與上限。
- 〈群益API 事件回呼跑在哪個執行緒〉——回呼裡只做收集。
參考資料
SKCenterLib_SetLogPath的說明與備註,依群益官方元件說明文件「登入」章節。官方文件沒有關於記憶體增長或 log 清理的說明。- GC.GetTotalMemory 方法 – Microsoft Learn
- Process.PrivateMemorySize64 屬性 – Microsoft Learn
- log 的大小與暴增經驗:我們環境的實測,非官方文件記載。
免責聲明
本文章僅作為群益API實作經驗分享,不構成投資建議,且策略及程式皆應自行撰寫。期貨及衍生性金融商品交易屬高風險投資,請謹慎評估自身風險承擔能力。







