2012-02-08 22 views
3

我目前正在爲公式語言實現一個運行時(即一組函數)。某些公式需要傳遞給它們的上下文,我創建了一個名爲EvaluationContext的類,其中包含我需要在運行時訪問的所有屬性。ThreadLocal性能vs使用參數

使用ThreadLocal <EvaluationContext>似乎是一個很好的選擇,可以使該上下文對運行時函數可用。另一種選擇是將上下文作爲參數傳遞給需要它的函數。

我更喜歡使用ThreadLocal,但我想知道是否有任何性能損失,而不是通過方法參數傳遞評估上下文。

+0

你有沒有想過從'ThreadLocal <>'使用'懲罰'?如果您的圖書館的消費者是多線程的,該怎麼辦? – 2012-02-08 00:18:24

+0

@Eugen Rieck - 我想他們可以通過一個演員線程序列化所有的調用,但運行時本身可能已經是線程安全的,因此這會增加相當大的額外開銷,爲次要的方便。 – ChaosPandion 2012-02-08 00:27:32

+0

@Eugen Rieck-我不太確定當你說「可用性懲罰」時,我明白你的意思。我的庫的使用者是多線程的,但是,計算一個公式的請求將在一個上下文中的一個線程上發生。但是我可以從不同線程調用相同的運行時函數,因此需要爲每個線程設置上下文。運行時函數具有線程安全性,具有通過參數和可能的ThreadLocal上下文成員所需的所有功能。 – costa 2012-02-08 00:40:40

回答

2

我創建了下面的程序,使用參數比使用ThreadLocal字段更快。

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

namespace TestThreadLocal 
{ 
    internal class Program 
    { 
    public class EvaluationContext 
    { 
     public int A { get; set; } 
     public int B { get; set; } 
    } 

    public static class FormulasRunTime 
    { 
     public static ThreadLocal<EvaluationContext> Context = new ThreadLocal<EvaluationContext>(); 

     public static int SomeFunction() 
     { 
     EvaluationContext ctx = Context.Value; 
     return ctx.A + ctx.B; 
     } 

     public static int SomeFunction(EvaluationContext context) 
     { 
     return context.A + context.B; 
     } 
    } 



    private static void Main(string[] args) 
    { 

     Stopwatch stopwatch = Stopwatch.StartNew(); 
     int N = 10000; 
     Task<int>[] tasks = new Task<int>[N]; 
     int sum = 0; 
     for (int i = 0; i < N; i++) 
     { 
     int x = i; 
     tasks[i] = Task.Factory.StartNew(() => 
               { 
                //Console.WriteLine("Starting {0}, thread {1}", x, Thread.CurrentThread.ManagedThreadId); 
                FormulasRunTime.Context.Value = new EvaluationContext {A = 0, B = x}; 
                return FormulasRunTime.SomeFunction(); 
               }); 
     sum += i; 
     } 
     Task.WaitAll(tasks); 

     Console.WriteLine("Using ThreadLocal: It took {0} millisecs and the sum is {1}", stopwatch.ElapsedMilliseconds, tasks.Sum(t => t.Result)); 
     Console.WriteLine(sum); 
     stopwatch = Stopwatch.StartNew(); 

     for (int i = 0; i < N; i++) 
     { 
     int x = i; 
     tasks[i] = Task.Factory.StartNew(() => 
     { 
      return FormulasRunTime.SomeFunction(new EvaluationContext { A = 0, B = x }); 
     }); 

     } 
     Task.WaitAll(tasks); 

     Console.WriteLine("Using parameter: It took {0} millisecs and the sum is {1}", stopwatch.ElapsedMilliseconds, tasks.Sum(t => t.Result)); 
     Console.ReadKey(); 
    } 
    } 
} 
0

不會有任何性能影響,但在這種情況下您將無法進行任何並行計算(這可能非常有用,特別是在公式域中)。 如果你絕對不想這樣做,你可以去ThreadLocal。

否則,我建議你看看「state monad」「pattern」,它可以讓你在沒有任何顯式參數的情況下無縫地通過你的計算(公式)傳遞你的狀態(上下文)。

+0

這看起來很有趣。我要看看它... – costa 2012-02-08 00:50:23

+0

有*會*是一個性能影響!線程局部性不是免費的。它使用指針間接實現到特殊的處理器寄存器。 – usr 2012-02-08 22:56:16

+0

不太確定爲什麼你認爲指針間接尋址特別慢,但無論如何它與「特殊處理器寄存器」無關。您可以簡單地將TLS的東西存儲在線程棧的底部。現在大多數Java實現只是簡單地將它存儲在掛起Thread.currentThread()的字典中。 – tumtumtum 2013-12-31 12:05:28

0

我想你會發現,在進行頭對頭的比較中,訪問ThreadLocal比訪問參數花費的時間要長得多,但最終它可能不會有顯着的區別 - 這一切都取決於其他你在做。

0

我認爲ThreadLocal設計很髒,而且很有創意。使用參數肯定會更快,但性能不應該成爲您唯一的擔憂。參數將會更清晰地理解。我建議你去參數。

2

繼續costaanswer;

如果你嘗試N作爲10000000

int N = 10000000;

你會看到沒有多大的差別(約107.4 103.4秒),的。

如果值變大,差值變小。所以,如果你不介意三秒慢,我認爲這是可用性和品味之間的差異。

PS:在代碼中,int返回類型必須轉換爲long。