CLR via C# 讀書(shū)筆記 2-3 Cache Lines and False Sharing(高速緩沖區(qū)和錯(cuò)誤共享???)
因?yàn)樗接邢?不知道怎么用中文表達(dá)?Cache Lines 和 False Sharing (暫時(shí)把他們翻譯為 高速緩沖區(qū)和錯(cuò)誤共享,如有謬誤,還請(qǐng)有識(shí)之士指正)
現(xiàn)在的cpu 一般擁有多個(gè)核心和一個(gè)cpu內(nèi)的緩存(一般是L2?)
這些緩存一般位于cpu芯片內(nèi), 他的速度遠(yuǎn)遠(yuǎn)高于主板上的內(nèi)存,
一般來(lái)說(shuō)cpu會(huì)把數(shù)據(jù)從內(nèi)存加載到緩存中 ,這樣可以獲得更好的性能(特別是頻繁使用的數(shù)據(jù))
這個(gè)高速緩存默認(rèn)劃分64 Byte為一個(gè)區(qū)域(這個(gè)數(shù)字可能在不同的平臺(tái)上不一樣, 可以通過(guò) ?win32 api 函數(shù) GetProcessorInformation 修改)
一個(gè)區(qū)域在一個(gè)時(shí)間點(diǎn)只允許一個(gè)核心操作
也就是說(shuō)不能有多個(gè)核心同時(shí)操作一個(gè)緩存區(qū)域(現(xiàn)在cpu都是多核心的...)
由于64Byte大的空間可以同時(shí)存的下多個(gè)數(shù)據(jù)結(jié)構(gòu) 例如16個(gè)integer32
例如以下代碼(這個(gè)代碼只是demo用,請(qǐng)不要深究他的命名和設(shè)計(jì)問(wèn)題...)
private class Data
{
public Int32 field1;
public Int32 field2;
}
那么完全可能一個(gè)線程在操作field1 的時(shí)候 , 運(yùn)行于另外一個(gè)cpu上的另外一個(gè)線程想操作field2,就必須等待線程1完成以后才能獲取這個(gè)緩存區(qū)域的訪問(wèn).
這在數(shù)據(jù)操作很密集的時(shí)候會(huì)造成很大的性能損耗
請(qǐng)參考以下代碼
代碼
internal static class FalseSharing
{
private class Data
{
public Int32 field1;
public Int32 field2;
}
private const Int32 iterations = 100000000; // 100 million
private static Int32 s_operations = 2;
private static Int64 s_startTime;
public static void Main()
{
// Allocate an object and record the start time
Data data = new Data();
s_startTime = Stopwatch.GetTimestamp();
// Have 2 threads access their own fields within the structure
ThreadPool.QueueUserWorkItem(o => AccessData(data, 0));
ThreadPool.QueueUserWorkItem(o => AccessData(data, 1));
// For testing, block the Main thread
Console.ReadLine();
}
private static void AccessData(Data data, Int32 field)
{
// The threads in here each access their own field within the Data object
for (Int32 x = 0; x < iterations; x++)
if (field == 0) data.field1++; else data.field2++;
// Whichever thread finishes last, shows the time it took
if (Interlocked.Decrement(ref s_operations) == 0)
Console.WriteLine("Access time: {0:N0}", Stopwatch.GetTimestamp() - s_startTime);
}
}
這段代碼在我的機(jī)器上運(yùn)行了2,471,930,060 (Timestamp) 話說(shuō)我的機(jī)器真是太爛了.....哪位同學(xué)有興趣的也研究下在你機(jī)器上的速度吧......
如果將Data 這個(gè)類改為以下的定義
代碼
[StructLayout(LayoutKind.Explicit)]//聲明顯式指定內(nèi)存布局
private class Data
{
// These two fields are separated now and no longer in the same cache line
[FieldOffset(0)]//內(nèi)存地址偏移量0
public Int32 field1;
[FieldOffset(64)]//內(nèi)存地址偏移量64
public Int32 field2;
}
那么在我機(jī)器上它的運(yùn)行時(shí)間為1,258,994,700(Timestamp)
修改內(nèi)存布局將field2偏移64個(gè)字節(jié)以后, 程序需要更多的空間 (2個(gè)緩存),但是會(huì)有更快的運(yùn)行速度
最實(shí)用的推論:
c# 所有一維數(shù)組繼承于System.Array 并且進(jìn)行了一些特殊的處理, 例如說(shuō)邊界檢查
邊界檢查:當(dāng)你訪問(wèn)任何數(shù)組的元素的時(shí)候,CLR 都要驗(yàn)證索引值必須位于數(shù)組的合法長(zhǎng)度內(nèi),(index <length)
這就意味著無(wú)論訪問(wèn)數(shù)組的哪個(gè)元素,CLR都必須先訪問(wèn)一下Length
Length是位于數(shù)組元素之前的一個(gè)integer32的值 用于表示數(shù)組的長(zhǎng)度
那么在內(nèi)存中數(shù)組的數(shù)據(jù)結(jié)構(gòu)大概如下所示
int[] vals = new int[] { 1, 2, 3, 4, 5 };
// 長(zhǎng)度 第一個(gè)元素 第二個(gè)元素 第三個(gè)元素 第四個(gè)元素 第五個(gè)元素
// 5 1 2 3 4 5
假設(shè)默認(rèn)的緩沖區(qū)大小是64Byte 那么數(shù)組的Length和開(kāi)始的幾個(gè)元素必然在一個(gè)緩沖區(qū)區(qū)域中
那么必須避免有一個(gè)線程A一直在操作Length,或者數(shù)據(jù)開(kāi)始的其他元素
因?yàn)樵诖送瑫r(shí),想要讀取數(shù)組其他部分的數(shù)據(jù) 必須要等待線程A完成以后才能讀取數(shù)組的其他部分.
PS:以下只是個(gè)人推斷 沒(méi)有經(jīng)過(guò)驗(yàn)證
一個(gè)緩沖區(qū)應(yīng)該是允許 并發(fā)讀取,但是不允許并發(fā)寫(xiě)的時(shí)候, 并且寫(xiě)操作會(huì)block其他所有的操作 (例如讀和其他寫(xiě))
posted on 2010-11-25 12:10 聽(tīng)說(shuō)讀寫(xiě) 閱讀(1476) 評(píng)論(1) 收藏 舉報(bào)

浙公網(wǎng)安備 33010602011771號(hào)