2021年7月16日 星期五

如何提升寫入 Log Performance

撰寫程式時,多少都需要寫 LOG 記錄,
一般使用時都不成問題,不過當需要大量寫入資料就會發現一點點的效能差異影響是很大的,
之前寫了一個 MQTT Broker(Server) ,平時自己用都沒問題,
但最近測試大量連線和封包傳送,就發現速度慢了一截,
LOG 的第三方元件我使用 NLOG,
平時有需要,就一筆一筆寫入 Log,
若需要大量寫入,我會修改 NLog 設定,以 Buffer 方式寫入
大概的設定如下所示

 
一般不使用 Buffer方式
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="true">
  <targets>
    <target Name="TraceTarget" xsi:type="File" layout="${level:uppercase=true:padding=-5} ${date:format=yyyy\-MM\-dd\ HH\:mm\:ss\.fffffff} - ${message}" fileName="${basedir}/Log/Trace/Trace.log" archiveFileName="${basedir}/Log/Trace/Trace${date:yyyyMMdd\_HH}.{#}.log" archiveAboveSize="5242880" archiveNumbering="Sequence" concurrentWrites="true" keepFileOpen="false" maxArchiveFiles="100" encoding="UTF-8" />
  </targets>
  <rules>
    <logger Name="Trace" minlevel="DEBUG" writeTo="TraceTarget" LogWvalue="DEBUG" />
  </rules>
</nlog>

使用 Buffer 方式
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="true">
  <targets>
    <target xsi:type="BufferingWrapper" Name="TraceBufferingWrapper" bufferSize="100">
      <target Name="TraceTarget" xsi:type="File" layout="${level:uppercase=true:padding=-5} ${date:format=yyyy\-MM\-dd\ HH\:mm\:ss\.fffffff} - ${message}" fileName="${basedir}/Log/Trace/Trace.log" archiveFileName="${basedir}/Log/Trace/Trace${date:yyyyMMdd\_HH}.{#}.log" archiveAboveSize="5242880" archiveNumbering="Sequence" concurrentWrites="true" keepFileOpen="false" maxArchiveFiles="100" encoding="UTF-8" />
    </target>
  </targets>
  <rules>
    <logger Name="Trace" minlevel="DEBUG" writeTo="TraceBufferingWrapper" LogWvalue="DEBUG" />
  </rules>
</nlog>

雖然這樣的寫法就能提升效能,不過程式中寫 Log 的次數頻繁,內部效能還需要再進一步修正,
利用 ConcurrentQueue 先將要寫的 Log 內容推到 Queue 中,每隔 500 ms 再將 Queue 中的內容取出,並寫入 Log
    private string _logSetFile = @".\LogSetting.xml";
    ConcurrentQueue<string> _logQ = new ConcurrentQueue<string>();
    System.Timers.Timer _timer = new System.Timers.Timer();

    public LogTest()
    {
        if (System.IO.File.Exists(_logSetFile) == false)
        {
            try
            { Logger.CreatDefaultLogSettingFile(_logSetFile); }
            catch (Exception ex)
            { MessageBox.Show(ex.ToString()); return; }
        }
        Logger.Initialze(_logSetFile);

        _timer.Elapsed += LogTimeElapsed;
        _timer.Interval = 500;

    }
    
    public void StartLogTestWithQ()
    {
        Logger.CreateInstance("LogTestWithQ");
        _timer.Start();
        _sw2.Start();
        for (int i = 1; i <= 10000; i++)
        {
            _logQ.Enqueue("第 " + i.ToString() + " 筆");
        }
        _sw2.Done();
        _logQ.Enqueue("測試完成花費時間  " + _sw2.ElapsedTime().ToString());
        MessageBox.Show("測試完成花費時間  " + _sw2.ElapsedTime().ToString());
    }
    public void StartLogTestWithoutQ()
    {
        Logger.CreateInstance("LogTestWithoutQ");
        _sw2.Start();
        for (int i = 1; i <= 10000; i++)
        {
            Logger.LogMessage("LogTestWithoutQ", "第 " + i.ToString() + " 筆", LogLevel.Debug);
        }
        _sw2.Done();
        Logger.LogMessage("LogTestWithoutQ", "測試完成花費時間  " + _sw2.ElapsedTime().ToString(), LogLevel.Debug);
        MessageBox.Show("測試完成花費時間  " + _sw2.ElapsedTime().ToString());
    }

    /// <summary>
    /// 利用時間計數,一次寫入 Log ,不要占用太多資源
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="e"></param>
    private void LogTimeElapsed(object obj, System.Timers.ElapsedEventArgs e)
    {
        StringBuilder sb = new StringBuilder();
        while (_logQ.Count > 0)
        {
            string result = "";
            if (_logQ.TryDequeue(out result))
            {
                Logger.LogMessage("LogTestWithQ", result, LogLevel.Debug);
            }
        }

    }


經過測試果然需要 Log Queue 搭配 NLog Buffer 的方式速度才會快
測試寫入 10000 筆資料,結果如下
//LogTestWithQ none Buffer (Log File 寫入時間長,但程式執行時間短)
DEBUG 2021-07-16 10:52:22.2869563 - 第 1 筆
DEBUG 2021-07-16 10:52:28.7547517 - 測試完成花費時間  0.0014641

//LogTestWithoutQ none Buffer (速度最慢)
DEBUG 2021-07-16 10:52:11.8478062 - 第 1 筆
DEBUG 2021-07-16 10:52:16.2525868 - 測試完成花費時間  4.4130297


//LogTestWithQ use Buffer (***速度最快)
DEBUG 2021-07-16 11:01:58.9000957 - 第 1 筆
DEBUG 2021-07-16 11:01:58.9549269 - 測試完成花費時間  0.0018626

//LogTestWithoutQ use Buffer
DEBUG 2021-07-16 11:01:53.5231884 - 第 1 筆
DEBUG 2021-07-16 11:01:53.6112196 - 測試完成花費時間  0.102756

2020年1月3日 星期五

好用又簡單的免費居家裝潢3D繪圖工具

最近因為裝潢需要,要和老婆和小孩討論家具擺設及空間,
但是女生對於這種空間概念似乎比較薄弱,
只好利用 3D 繪圖,再輔助3D虛擬實境的方式跟他們說明會清楚一些,
上網找了一些3D繪圖,還有居家空間設計相關的軟體,
發現 https://www.homestyler.com/ ,可以簡單又快速的建立模型,
首先只要先進入官網,建立帳號,
再來開啟新的設計專案,就可以開始拉物件,
第一步驟就是建立房子的牆面,記得要封好牆面,不然存檔會跳出警告訊息,
網站還能切換簡體中文,看到中文感覺親切多了,

記得左下角選擇你習慣的長度單位,(一開始我沒注意到,還一個一個轉成英呎)


在編輯的同時,還可以切換3D檢視,或鳥瞰圖,

基本元件畫完,可以利用 [渲染] -- [創建全景] ,
將設計圖轉為全景圖(3D虛擬實境),

最好是將每個空間轉成全景圖,(每個全景圖都可以各別分享給朋友觀看),
建議單一全景圖還不用分享,等到全屋漫遊圖完成後,再分享給朋友,
這樣才能一次觀看所有房間

做好全部的全景圖,就可以利用全屋漫遊的功能,將所有全景圖串在一起,
成品可以看到有一個小飛機的符號,還有標籤,只要點選就會進入下一個全景,
先確認可以完整看完全屋,再按下分享(自動生成 QR Code 還有 URL),讓朋友一起討論觀看。

除了電腦版,手機直接開啟也可以看,還能使用 VR 裝置觀看,非常方便簡單。
這次隨手畫了簡單的示意圖,只放上大概的樣示,並轉成全屋漫遊圖,
可以看一下成品大概長這樣,
這個成品圖花了我不到一天的時間,(我還是第一次使用,稍微摸了一下)
頭腦裡先有初步的構想,再找出類似的物件拖拉進來就差不多完成了。

除了 3D 虛擬實境,還可以把房屋格局 (不含裝潢物件) 轉成 AutoCAD 的 DWG 檔,基本功能算是還不錯。

其它功能我還沒試,但目前為止,大致上都很容易使用,成果也不錯,
真的值得推薦新手使用。


2019年11月25日 星期一

利用 MSSQL mdf 檔案,快速將 SQL Server DB 轉移

因為公司一台測試環境電腦的DB突然掛了,
需要將另一個DB快速轉移過來,
怕自己以後忘記,先記錄下來步驟。

將要轉移的DB 複製到本機的資料夾中,
總共有2個檔案
1. xxx.mdf
2. xxx_log.ldf (可有可無)
檔案位置可能路徑如下,
C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA

開啟 SQL Server Management Studio 程式

使用預設最高權限登入資料庫


點選 [資料庫] -> 右鍵 (附加)

選擇要附加的資料庫檔名

如果選擇的 DB 沒有 xxx_log.ldf  檔案,會出現找不到檔案的警告,只要移除就可以了

接下來點選 [資料庫] - [新加入的DB名稱] - [安全性] - [使用者]
不能選到主要的 [安全性] ,刪錯就完了
如果裡面有指定 User Name ,必須移除,因為該 User ID 已不能使用,

再重新建立 User (視需求而定,是否要同名稱,或是新的 User 都可以)
這裡要選的是根目錄那層的安全性,不是資料庫裡的

然後根據需求,輸入帳號密碼,還有驗證方式
最後選擇預設資料庫,指到剛才加入的 DB Name

在左邊的選取頁面中,點選 [伺服器角色],勾選 Public (依實際需求)

左邊的選取頁面中,點選 [使用者對應] ,勾選要對應的 DB Name 和角色成員

加入後就可以在原來的
[資料庫] - [新加入的DB名稱] - [安全性] - [使用者]
找到新加入的 User Name 了

最後使用新建立的 UserID / Password 連線資料庫看看,是不是 Table 都可以看到了。

如果資料庫不想用需要缷離,只要點選該資料庫,右鍵 [工作]->[缷離] 就可以了



2019年5月14日 星期二

在64位元環境下動態載入DLL



繼2011年發佈一篇文章
將非 COM 元件的外部 DLL ,包入 .NET 開發的執行檔中
直到現在又遇到問題了,趕緊寫下來記錄
本來專案都 Complier 成 x86,所以在64位元系統下仍然以 32位元在RUN,
但最近公司要求換成 AnyCPU,這下子就出問題了,

''原來的寫法,在64位元系統會取到 IntPtr 值為 0 的問題
    Friend Declare Function GetProcAddress Lib "kernel32" (ByVal ModuleHandle As Integer, ByVal ProcName As String) As Integer
    ''改成 IntPtr ,執行到 GetProcAddress 會有數學運算溢位的錯誤
    Friend Declare Function GetProcAddress Lib "kernel32" (ByVal ModuleHandle As Integer, ByVal ProcName As String) As IntPtr 
    

後來在
https://www.daniweb.com/programming/software-development/threads/207639/getprocaddress-and-function-pointers
的文章發現一個寫法,
    Friend Declare Function GetProcAddress Lib "kernel32" (ByVal ModuleHandle As IntPtr, <[In](), MarshalAs(UnmanagedType.LPStr)> ByVal lpProcName As String) As IntPtr


我一直以為在 64位元 GetProcAddress 回傳的值要改成 IntPtr,漏了傳入參數也要跟著換,
   '最後換成以下寫法就可以
   Friend Declare Function GetProcAddress Lib "kernel32" (ByVal ModuleHandle As IntPtr, ByVal ProcName As String) As IntPtr

換完後,好奇的我再試一下 x86 是否可以通用,
結果是可以的,再查了一下 IntPtr ,才發現這個元件根據 OS 的位元數,所回傳的值也會不同,
也就是從頭到尾只要統一用 IntPtr 就可以了,嗯~又學到一課了!
public class HelloWorld {

    public static void main (String[] args) {

        System.out.println("Hello, world!\n");

    }

}

2019年1月24日 星期四

挑戰 9x9乘法表 練習小程式 (小學數學 加法,減法,乘法 練習)

為了小朋友的9x9乘法表練習,寫了一個挑戰9x9乘法表的小程式,
主要目的是訓練小朋友熟悉9x9乘法表,
可以選擇時間長度,最少1分鐘
再來是要做的題目數多少?


當點撃[開始挑戰] 按鈕時,會進入另一個答題畫面,

只要在答案欄中輸入答案,直接按下 Enter 就可以繼續下一題,
結束後會有統計資料,若是答題錯誤,在右邊的記錄欄可以清楚看到紅色標記

當時間有限,有時又要思考比較久,小朋友可以直接按下 Enter 讓答案空白,
在事後統計時,可以針對答錯的部份再多加練習。
程式中也加了一些音效,增加趣味性,希望小朋友願意多練習!

執行檔下載 (7z 檔案, 173 kB)
程式碼下載 (VB.Net 2015)



2019/10/28 更新
1. 新增加法,減法的練習
2. 新增測驗範圍設定畫面

在程式主畫面中,點選題目範圍的綠色框內容,會跳出設定畫面
預設 加減法最大到99,乘法最大數到10


設定題目最大的數目,若勾選加入測驗,
則出題會包含所選的算式,

如果回答錯誤,右邊記錄欄會順便把正確解答放在後面


V1.0.1.1 執行檔下載 (7z 檔案, 181 kB)
V1.0.1.1 程式碼下載 (VB.Net 2015) (7z 檔案, 162 kB)

檔案壓縮為 7Z 若無法解壓縮請到官網自行下載軟體安裝(免費). https://www.developershome.com/7-zip/