群益API 記憶體與磁碟增長的排查方向示意圖

群益API 程式開久了記憶體與磁碟一直漲?排查方法與元件 log 的處理

程式開著開著,記憶體一路往上長,開一天就到好幾 GB。硬碟也越來越滿,找到元件的 log 資料夾一看,已經好幾百 MB。

群益API 記憶體或磁碟一直漲的時候,第一步不是改程式,而是分清楚到底是誰在長。

先說清楚本文的定位:官方文件沒有關於記憶體增長或 log 清理的說明。 以下是通用的排查方法,以及我們自己環境裡的實測,不是官方規格。

群益API 記憶體與磁碟增長的排查方向示意圖

群益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 條處理原則+程式碼

  1. 先分清楚是誰在長:私有記憶體對照受控堆積;強制回收前後比;排除法一次關一類呼叫,情境要相同。
  2. 元件的 log 自己定期清;要換位置,最先呼叫 SKCenterLib_SetLogPath。
  3. 訂閱異動合併成一次送出,不要逐檔重送整籃。
  4. 自己的快取要有清掉的時機,時段切換是其中一個。
// 要變更 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實作經驗分享,不構成投資建議,且策略及程式皆應自行撰寫。期貨及衍生性金融商品交易屬高風險投資,請謹慎評估自身風險承擔能力。

延伸閱讀|相關文章

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *