2010年11月23日 星期二

Visual Studio 2010 做壓力測試 (load test),出現錯誤訊息

使用 Visual Studio 2010 做壓力測試 (load test)好一陣子。有一天做完後,,突然發生了錯誤訊息

Error occured running test. The loadTest completed but an error occurred while collecting the final results. Could not access the load test results repository: Incorrect syntax near 's'.  The label 'http' has already been declared. Label names must be unique within a uqery batch or stored procdure

檢視報表切到 Detail時顯示了下面的訊息

No detail exists at the selected point

使用 「Open and manage results」也找不到剛剛執行的測試結果。

原本想說將 load test 所使用的資料庫清空,重新執行loadtestresultsrepository.sql 就可以解決了。這招也不成功。

看來這是 Visual Studio 的 bug 了。

接著,我想了一下,之前為什麼可以?我修改了什麼後變成這樣了。於是我找到了原因。

我的 load test 中其中一個Web Performance Test 有問題。將該 Web Performance Test 移除,或者在 Test Matrix 中設該test 的比例為0,就可以正常儲存測試結果到資料庫。

為什麼這個 Web Performance Test 會造成 load test 的問題呢?這看起來是 Visual Studio 的 bug

2010年11月7日 星期日

TPL 學習日記(9): 使用TPL 應避免的事情1: Avoid Memory Allocation

有些事情,同樣在使用 TPL 時也需要避免。第一件事情就是使用記憶體的 allocation。

原因

記憶體的 allocation 會導致 GC,記憶體的回收變成的一項負擔。

解決方法1: 使用 stack,避免GC。

範例程式如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TPL_2 {
  class Program {
    static void Main(string[] args) {
      Console.WriteLine("Sequence");
      Console.WriteLine(Time(GoSeq));
      Console.WriteLine("Parallel, using heap");
      Console.WriteLine(Time(GoPalHeap));
      Console.WriteLine("Parallel, using stack");
      Console.WriteLine(Time(GoPalStack));
    }

    private static void GoSeq() {
      var q = (from i in Enumerable.Range(1, 20000000)
               select new { Value = i }).Max(i => i.Value);
      Console.WriteLine(q);
    }

    //anonymous type, 會使用 heap。導致GC 回收記憶體,效能較差
    private static void GoPalHeap() {
      var q = (from i in ParallelEnumerable.Range(1, 20000000)
               select new { Value = i }).Max(i => i.Value);
      Console.WriteLine(q);
    }

    //struct, 會使用 stack,效能較快
    private static void GoPalStack() {
      var q = (from i in ParallelEnumerable.Range(1, 20000000)
               select new MyClass { Value = i }).Max(i => i.Value);
      Console.WriteLine(q);
    }

    struct MyClass {
      public int Value;
    }

    static TimeSpan Time(Action a) {
      Stopwatch w = Stopwatch.StartNew();
      a();
      return w.Elapsed;
    }
  }
}
執行結果

image

想不到吧!使用 Heap 反而使得 TPL 執行效果比不使用更差。改使用 struct 後就變快了。

解決方法2: 使用 Server GC

第二個方法是啟用 Server GC (伺服器記憶體回收),見<gcServer> 項目。簡單的說,使用這個方法,會讓CPU 的每個 Core 有獨立的 GC,因此回收的效能變快了。

.net framework 預設使用 workstation (工作站)的方式回收記憶體,也就是只有一個 GC。使用預設的 workstation 方式時, GC 必須考慮 multi-core 的 concurrency 問題,因此回收的效能較差。

我們加上一個 app.config ,如下

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <gcServer enabled="true" />
  </runtime>
</configuration>

執行結果

image

由結果得知,加上設 gcServer 的設定,雖然仍然使用了 heap,速度還是變快了,但無法與使用 stack相比。

TPL 學習日記(8): BlockingCollection 的 GetConsumingEnumerable

上一回提到了 BlockingCollection,順便提一個值得玩玩的事。

BlockingCollection 實作了IProducerConsumerCollection<T>. 此 interface 定義了製造者/消費者使用方式的安全執行緒集合。而BlockingCollection 是其中最典型的實作。BlockingCollection 可以使用GetConsumingEnumerable 取得加入/移除到集合中的項目。如下範例。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Threading;

namespace TPL_1 {
  class Program {
    static void Main(string[] args) {
      var primes = new BlockingCollection<int>();
      var t = Task.Factory.StartNew(
        () =>
        {
          //取得對BlockingCollection中新增或移除的每個項目
          foreach (var item in primes.GetConsumingEnumerable())
            Console.WriteLine(item);
        });
      for (int i = 0; i < 5000000; i++)
        if (IsPrime(i)) primes.Add(i);
      primes.CompleteAdding(); //結束新增
      t.Wait(); //直到 t 執行完
    }

   
    /// <summary>
    /// 是否為質數
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    static bool IsPrime(int input) {
      int bound = (int)Math.Sqrt(input);
      for (int i = 2; i <= bound; i++)
      {
        if (input % i == 0) return false;
      }
      return true;
    }

    static TimeSpan Time(Action a) {
      Stopwatch w = Stopwatch.StartNew();
      a();
      return w.Elapsed;
    }
  }
}

執行起來,功能平凡無奇。好玩的是,這樣的技巧已經在之前的「讀取大型文字檔」中顯示過了,而GetConsumingEnumerable 以相同的方式實作出來。

CompleteAdding() 方法標示該集合不會再新增項目,因此 GetConsumingenumerable 的巡覽才會結束。

2010年11月5日 星期五

TPL 學習日記(7): Thread-Safe Collections

Parallel library 是一個多執行緒的一套 API,因此在使用時,也需要遵守多執行緒程式的規則。

以下,是一個「找出3 到 5,00,000 之間有多少個質數」的解法。

單一執行緒

首先,我們當然要寫出單一執行緒的作法,再寫成多執行緒的作法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace TPL_1 {
  class Program {
    static void Main(string[] args) {
      //單一執行緒
      Console.WriteLine(Time(
    () =>
    {
      List<int> primes = new List<int>();
      for (int i = 0; i < 2000000; i++)
      {
        if (IsPrime(i)) primes.Add(i);
      }
      Console.WriteLine("Count = " + primes.Count);
    }
  ));
    }
    /// <summary>
    /// 是否為質數
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    static bool IsPrime(int input) {
      int bound = (int)Math.Sqrt(input);
      for (int i = 2; i <= bound; i++)
      {
        if (input % i == 0) return false;
      }
      return true;
    }

    static TimeSpan Time(Action a) {
      Stopwatch w = Stopwatch.StartNew();
      a();
      return w.Elapsed;
    }
  }
}

程式碼很簡單,其實就是將所有的質數找出並加進到一個使用 List<int> 的 instant 中(即 primes),並在最後算出總共有348515個質數。

使用 Parallel library (Wrong)

這次要使用 Parallel library。由之前的經驗,我們會寫成下面的程式碼。

static void Main(string[] args) {
      //單一執行緒
      Console.WriteLine(Time(
    () =>
    {
      List<int> primes = new List<int>();
      for (int i = 0; i < 5000000; i++) {
        if (IsPrime(i)) primes.Add(i);
      }
      Console.WriteLine("Count = " + primes.Count);
    }
  ));
      Console.WriteLine(Time(
          () =>
          {
            var primes = new List<int>();
            Parallel.For(0, 5000000, i =>
            {
              lock(primes) //Watch this line
                if (IsPrime(i)) primes.Add(i);
            });
            Console.WriteLine("Count = " + primes.Count);
          }
        ));
    }

注意到 //Watch this line 的那一行。由於在寫 multi thread 程式, concurrent 非常重要,我加了 lock(primes) 讓存取 primes 可避免掉一致性的問題。結果呢,Multithread 竟然比 Single thread 還慢!真不可思議。

image

使用 Parallel library (Correct)

不要自己發明輪子。上面例子的問題,是自行使用 lock 來解決 concurrency 的問題。如果是在 TPL 的世界中,該問題就應使用 System.Collection.Concurrent namespace 內的集合。以下範例加上新的解法 (BlockingCollection)

static void Main(string[] args) {
      //單一執行緒
      Console.WriteLine(Time(
    () =>
    {
      List<int> primes = new List<int>();
      for (int i = 0; i < 5000000; i++) {
        if (IsPrime(i)) primes.Add(i);
      }
      Console.WriteLine("Count = " + primes.Count);
    }
  ));
      Console.WriteLine(Time(
          () =>
          {
            var primes = new List<int>();
            Parallel.For(0, 5000000, i =>
            {
              lock(primes)
                if (IsPrime(i)) primes.Add(i);
            });
            Console.WriteLine("Count = " + primes.Count);
          }
        ));
      Console.WriteLine(Time(
          () =>
          {
            var primes = new BlockingCollection<int>();
            Parallel.For(0, 5000000, i =>
            {
              if (IsPrime(i)) primes.Add(i);
            });
            Console.WriteLine("Count = " + primes.Count);
          }
        ));
    }

程式執行結果如下

image

結論

TPL 的程式已經很棒了,但有些細節的部份仍然要相當小心,否則甚至比不用還差。

Share with Facebook