2012年4月25日 星期三

[C#] 程式最佳化心得(持續更新)


[C#] 程式最佳化心得(持續更新)
作者:JB

上次系統從Oracle移轉MS Sql Server,雖然吃盡苦頭,但是也學到了不少東西;
尤其是SQLindex的部分。 去年主管問小弟,有沒有辦法再將跑三個半小時的程式再加速,小弟當時很無奈的說:「對不起! 我已經盡力了。」

時值今日,剛好有機會再度修改這支程式,也讓我有機會自打嘴巴。 深入了解後,才知道可以改善的幅度這麼大。

1.      Algorithm

小弟用Sql Server Profiler觀察AP裡面執行最久的一段Sql,發現需要跑一秒多; 去看一下執行計畫後,才發現沒有吃到index。 一般是馬上去建index,但是我看了一下目前已建立的index,發現只要改這段Sql的邏輯,它就可以吃到目前的index了。

恩,再次執行後,發現這段Sql已經改善到0.6XX秒了,但是AP的整體效能並沒有提昇太多,因為這段SqlAP裡面,反覆執行了一萬多次,這樣算一算總執行時間就要1.5個小時!
於是我去細看這邊的程式邏輯,在不影響邏輯的情況下,多加上這段Sql的執行條件,讓其執行次數減少到幾百次馬上執行時間減少到十幾分鐘!

所以小弟真的覺得要最佳化AP,首先應該是回頭看看演算法有沒有可以調整的地方
J

2.      Database

原以為在在Oracle移轉MS Sql Server的過程,AP只要改改Sql,原有的index建一建就好。 結果兩個資料庫的index運作原理似乎不太..一樣(抱歉,小弟不是DBA,只能這麼粗淺的解釋)
也就是說,原本在Oracle有吃到indexSql,在MS Sql server就不一定了喔。
所以記得要把Sql再拿出來看一下執行計畫!

參考這篇文章:Top 10 steps to optimize data access in SQL Server
以下列出ㄧ些小弟認為很重要的幾項。

(1)       查看執行計畫!

(2)       DistinctGroup by時,避免對欄位進行運算,如Trim(否則完全吃不到index)

(3)       避免對WHERE條件裡面的欄位進行Convert,例如:
where CONVERT(NUMERIC, COL_AMT) = 80

應該寫成
where COL_AMT = ‘000080’
(4)       避免用COUNT來查看表格裡面有沒有資料,而是用Exists (因為Exists只要掃到一筆就回傳True)

(5)       避免把兩個不同型別的欄位,拿來當作JOIN的條件。(恩,常常遇到 ~_~)

(6)       待續

3.      Tools

除了寫好程式,也要好的工具來測試寫好的程式,Visual Studio 2010的效能分析工具實在太佛心來著了。



小弟再慢慢補上心得... 

A paint for Kitagaw



看見北川景子的笑容,就不禁告訴自己也要這麼開心,縱使生活有時不如人意...

A paint for Kitagaw.

 But the words for myself.




2012年4月23日 星期一

[C#] lock 的用法


參考文章:

Object thisLock=new Object();
lock(thisLock)
{
//Criticalcodesection.
}


lock關鍵字會在區塊開始執行時呼叫Enter,並在區塊結束時呼叫Exit
一般而言,請避免鎖定public型別或程式碼無法控制的執行個體。
有三種常見的建構,分別為 lock(this)lock(typeof(MyType))lock("myLock"),違反這項方針:

·             lock(this)在可公開存取執行個體的情況下,會是問題所在。
·             lock(typeof(MyType))在可公開存取MyType的情況下,會是問題所在。
·             lock("myLock")會是問題所在,因為使用相同字串的處理序中若有任何其他程式碼,將會共用相同的鎖定。

最佳作法是定義要鎖定的private物件,或者定義privatestatic物件變數保護所有執行個體通用的資料。

PS. 上述三種情況,有可能會造成Deadlock

Design Pattern : Singleton in MultiThread ap


Design Pattern : Singleton Pattern

作者:JB

關於「Singleton, C# in Depth』已經描述了非常詳盡的細節。
我比較喜歡拿前輩的例子:取號機。
如果郵局有兩台取號機,如何保證同時兩台機器不會取到相同的號碼呢?
Singleton Pattern目的在於設立一個只能被建立一次instanceSingeton class,也就是說,如果我們在程式裡要嘗試建立第二個該Singeton classinstance時,是不被允許的。
本範例目的在於實做一支有四支執行緒的取號程式(亦即四個取號機同時取號)。
測試以下四種Singleton Pattern

1.      Non thread-safe
2.      Thread-safe using double-check locking
3.      Thread-safe, eager singleton
4.      Thread-safe, lazy singleton

一、 C# Windows Form應用程式為例,建立專案後,加入以下四個Singleton class
1.          Non thread-safe

public sealed class Counter01

{

  static Counter01 instance = null;

  private int CNT = 0;

  Counter01() //建構子

  {

    Console.WriteLine("建立Counter01物件!");

  }

  public static Counter01 GetInstance //建立或取得instance

  {

    get

    {

      if (instance == null)

      {

        instance = new Counter01();

      }

      return instance;

    }

  }

  public int GetNumber() //取得新號碼

  {

    CNT++;

    return CNT;

  }

}



備註1. 宣告關鍵字sealed class 表示此類別不可被繼承,詳見MSDN
備註2. 由建構子的輸出,可在runtime看出此類別實際上被建立了幾個instance
備註3. GetInstance這個事件,目的在於建立或取得靜態的instance
       Non thread-safe
指的就是這一段。

2.          Thread-safe using double-check locking
static Counter02 instance = null;
static readonly object padlock = new object(); //用來LOCK建立instance的程序。
private int CNT = 0;
Counter02()
{
 
Console.WriteLine("建立Counter02物件!");
}
public static Counter02 GetInstance
{
 
get
 
{
   
if(instance==null)
   
{
     
lock(padlock) //lock此區段程式碼,讓其它thread無法進入。
     
{
       
if(instance==null)
       
{
         
instance = new Counter02();
       
}
     
}
   
}
   
return instance;
 
}
}
public int GetNumber()
{
 
CNT++;
 
return CNT;
}

這邊跟第一種寫法的不同處,是使用lock(object) 來確定只有一條thread可以建立instance

3.           Thread-safe, eager singleton
因為第二種寫法過於繁雜,故有大師再改寫為第三種,亦即在類別裡面,直接宣告了instance = new Counter03,所以當我們使用Counter03時,instance會隨即建立, 所以稱為eager (急性子的) singleton
因為宣告為靜態的的變數,每支ap只會建立一次,所以它是thread-safe
public sealed class Counter03
{
  static Counter03 instance = new Counter03();
  private int CNT = 0;
  Counter03()
  {
    Console.WriteLine("
建立Counter03物件!");
  }
  public static Counter03 GetInstance
  {
    get
    {
      return instance;
    }
  }
  public int GetNumber()
  {
    CNT++;
    return CNT;
  }
}

備註1. 因為已經事先建立了instance,所以GetInstance並不用考慮建立instance的事情。

4.          Thread-safe, lazy singleton
第四種應該是終極版了,為了改善eager songleton的效能,大師們又想出了利用 inner class(也就是nested class)來建立instance。 當我們在呼叫GetInstance時,才會去建立這個inner class裡面的靜態instance
同樣的,因為是宣告為internal static readonly Counter04 instance,所以它是唯一,所以是thread-safe


public sealed class Counter04
{
 
private int CNT = 0;
 
Counter04()
 
{
   
Console.WriteLine("建立Counter04物件!");
 
}
 
public static Counter04 GetInstance
 
{
   
get
   
{
     
return InnerClass.instance;
   
}
 
}
 
public int GetNumber()
 
{
   
CNT++;
   
return CNT;
 
}

 
class InnerClass
 
{
   
static InnerClass()
   
{
   
}
   
internal static readonly Counter04 instance = new Counter04();
 
}
}
5.          其實在『C# in Depth』,有提到.NET4.0的新寫法: Lazy<T> type

二、 建立取號機(Thread)
建立了四種Singleton class後,我們接著來寫Multi-thread的部分,下面只列出部分程式碼囉。
目的是建立四台取號機,每台取號機各取五個號碼。 如果是thread-safe,四台取號機不會拿到相同的號碼,而且最大的號碼是20

1.          測試畫面

四個按鈕分別對應四種不同的Singleton方法。



2.          建立Global Thread集合、ThreadStart
private List<Thread> myThread;
ThreadStart Starter;

3.           在每個按鈕Click事件裡面,指定ThreadStart對應的事件 (指定取號方法的類別 Singleton class),然後再執行步驟4.

private void bt_Add1_Click(object sender, EventArgs e)
{
 
Starter = new ThreadStart(ThreadWork01);
 
Print();
}
/// <summary>
/// ThreadStart事件
/// </summary>
private void ThreadWork01()
{
 
for (int i = 1; i <= 5; i++)
 
{
   
int value = Counter01.GetInstance.GetNumber(); //指定取值方法的類別為Counter01
    String sOutput = String.Format("{0}  {1}-->
號碼:{2}",DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), Thread.CurrentThread.Name, value);
    
Console.WriteLine(sOutput);
    
Thread.Sleep(1000);
 
}
}

4.          建立四台取號機(四條Thread),然後開始取號(Thread.Start()
private void Print()
{
 
for (int index = 0; index < 4; index++)
 
{
    Thread thread;

   
thread = new Thread(Starter); //指定ThreadStart事件
   
thread.Name = "取號機-" + index.ToString();
   
myThread.Add(thread);
   
thread = null;
 
}
 
foreach (Thread thread in myThread)
 
{
   
thread.Start();
 
}
}
三、 執行結果
1.          Non thread-safe
建立Counter01物件!
建立Counter01物件!
2012/04/23 11:31:22  取號機-1-->號碼:1
2012/04/23 11:31:22  取號機-2-->號碼:2
2012/04/23 11:31:22  取號機-0-->號碼:1
2012/04/23 11:31:22  取號機-3-->號碼:3

2012/04/23 11:31:26  取號機-2-->號碼:18
2012/04/23 11:31:26  取號機-0-->號碼:19
可清楚的看到,Counter01被建立了兩次,所以出現取到兩次號碼 : 1
2.          Thread-safe using double-check locking
建立Counter02物件!
2012/04/23 11:35:44  取號機-2-->號碼:3
2012/04/23 11:35:44  取號機-1-->號碼:2
2012/04/23 11:35:44  取號機-0-->號碼:1
2012/04/23 11:35:44 
取號機-3-->號碼:4
2012/04/23 11:35:45  取號機-2-->號碼:6
2012/04/23 11:35:45  取號機-1-->號碼:5
2012/04/23 11:35:45  取號機-0-->號碼:7
2012/04/23 11:35:45  取號機-3-->號碼:8
2012/04/23 11:35:46  取號機-2-->號碼:10
2012/04/23 11:35:46  取號機-1-->號碼:9
2012/04/23 11:35:46 
取號機-0-->號碼:11
2012/04/23 11:35:46  取號機-3-->號碼:12
2012/04/23 11:35:47  取號機-1-->號碼:14
2012/04/23 11:35:47  取號機-2-->號碼:13
2012/04/23 11:35:47  取號機-3-->號碼:15
2012/04/23 11:35:47  取號機-0-->號碼:16
2012/04/23 11:35:48  取號機-2-->號碼:17
2012/04/23 11:35:48  取號機-1-->號碼:18
2012/04/23 11:35:48  取號機-0-->號碼:19
2012/04/23 11:35:48  取號機-3-->號碼:20

3.          Thread-safe, eager singleton
2.
4.          Thread-safe, lazy singleton
2.

2012年4月17日 星期二

寫好整合測試及壓力測試文件

今天終於寫好了專案的整合測試和壓力測試文件,
Visual Studio 2010的負載測試和效能精靈,終於可以派上用場了 :)

2012年4月16日 星期一

[C#] 建立第一支C# MSMQ程式 (First MSMQ program in C#)

How to create my first MSMQ program in C#

The FORCE studio

建立第一支C# MSMQ程式
作者:JB

We can read this article : 瞭解MSMQ,控制ASP進程 to get how a Message Queue Service works.

Before writing a MSMQ program , we must install the Message Queue Service in Windows.

We will use the library called
System.Messaging.MessageQueu to create and send our message in the C# project.

1         InstallTake Windows Vista for example
1.1      Install Message Queue service


1.2      The MSMQ manager is in 「控制台」「電腦管理」「訊息佇列」


1.3      We can check our MSMQ service as well.


2         Create my first MSMQ program in C# (Use a windows projectfor example)

2.1      The final output winform would be looked like this :

TexBox : The string message which will be send to the MSMQ.
Send msg to MQ】:Send the String in TextBox to MSMQ.
Start to get MQ】:Start the timer to get a new message from MSMQ.
Stop to get MQ】:Stop the timer to cancel getting messages from MSMQ.
TimerUsed to get the message from MSMQ every other time.

2.2       The Common method
MessageQueue.Formatter
MessageQueue.Label
MessageQueue.Path
MessageQueue.Peek
MessageQueue.Receive
MessageQueue.Send

Now we are going to write the codes!

2.3      Make a Queue Class
2.3.1             Property
public MessageQueue mq;
               public String sReceiveData;

2.3.2               Constructer
public Queue(String sType, String QueuePath)
{
 
try
 
{
    //
建立一個MSMQ
   
if (!MessageQueue.Exists(QueuePath))
   
{
     
MessageQueue.Create(QueuePath);
   
}
    //
MessageQueue物件指向 指定的MSMQ
   
mq = new MessageQueue(QueuePath);
   
mq.Formatter = new System.Messaging.XmlMessageFormatter();
   
//mq.ReceiveCompleted += MQ_ReceiveCompleted;
   
mq.Refresh();
   
sReceiveData = "";
 
}
 
catch (Exception ex)
 
{
   
throw ex;
 
}
}

2.3.3               Send message event
public void SendByLabel(String sBody, String sMsg)
{
 
try
 
{
   
mq.Send(sBody, sMsg);
 
}
 
catch (Exception ex)
 
{
   
throw ex;
 
}
}

2.3.4               Peek event
public bool Peek()
{
 
try
 
{
   
TimeSpan timeout = new TimeSpan(0, 0, 10); //設定timeout
   
if (mq.Peek(timeout) != null)
   
{
     
return true;
   
}
   
else
   
{
     
return false;
   
}
 
}
 
catch (MessageQueueException ex)
 
{
   
Console.WriteLine(ex.ErrorCode);
   
if (ex.ErrorCode == -2147467259) //已無訊息佇列
   
{
     
return false;
   
}
   
else
   
{
     
throw ex;
   
}
 
}
}

2.3.5               Receive event
public void Receive()
{
 
try
 
{
   
TimeSpan timeout = new TimeSpan(0, 0, 10); //設定timeout
   
Message msg = mq.Receive(timeout);
   
sReceiveData = msg.Label;
 
}
 
catch (Exception ex)
 
{
   
throw ex;
 
}
}

2.3.6               Done with class Queue.

2.4      Next step, we will create a global Queue item in our MainForm.cs:

Queue
MyQueue;

 And in the constructer of MainForm, we initial it …

MyQueue = new Queue("private", ".\\Private$\\MyQueue");

2.5      In the Click event of Send msg to MQ, we will send a message to MyQueue.
MyQueue.SendByLabel("MSMQ Msg Title", tb_send.Text);

Send a message to MSMQ will be looked like this…




2.6      Now how receive the message from MSMQ? We can only get ONE message each time.
So we can use the TIMER to repeat this step.
I set the Timer’s
Interval to 10000 (10 sec).
Surely you might change the value, but if the interval is too small, the program will PEEK the MSMQ too often that will get the performance pretty bad.
So in timer_Tick event, we write the codes for receiving a message…

private void timer_Tick(object sender, EventArgs e)
{
 
timer.Stop();
 
try
 
{
   
if (MyQueue.Peek() == true) //Peek訊息佇列
   
{
     
MyQueue.Receive(); //取出訊息佇列
     
String sMsg = MyQueue.sReceiveData;
     
if (sMsg.Length > 0)
     
{
       
lb_ReciveData.Items.Add(sMsg);
     
}
   
}
 
}
 
catch (Exception ex)
 
{
   
MessageBox.Show("timer_Tick失敗:" + ex.Message);
 
}
 
finally
 
{
   
timer.Start();
 
}
}

So we take a PEEK step before we RECEIVE it. The PEEK will return the first message in the MSMQ. The RECEIVE step will get it, that means delete the message after receiving it.

2.7      Since we finished the Timer event, the Start to get MQ/Stop to get MQ is used to start/stop the Timer.

2.8       Important!
Always set a TIMEOUT when doing PEEKRECEIVE.
   If not, your program will suspend while there is no message in the MSMQ.
If there is no message in the MSMQ, the method of PEEKRECEIVE will return a  
  
MessageQueueException. The error code is -2147467259.
   Remember to catch it!



3         Test screenshot