CPU Cache對于并發編程的影響

文章目錄

  • 引子
  • CPU Cache對于并發的影響
  • 讀寫順序對性能的影響
  • 字節對齊對Cache的影響
  • 小結

引子

下面給出兩個極其相似的代碼,運行出的時間卻是有很大差別:
代碼一

#include <stdio.h>
#include <pthread.h>
#include <stdint.h>
#include <assert.h>
#include<chrono>const uint32_t MAX_THREADS = 16;void* ThreadFunc(void* pArg)
{for (int i = 0; i < 1000000000; ++i) // 10億次累加操作{++*(uint64_t*)pArg;}return NULL;
}int main() {static uint64_t aulArr[MAX_THREADS * 8];pthread_t aulThreadID[MAX_THREADS];auto begin = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());for (int i = 0; i < MAX_THREADS; ++i){assert(0 == pthread_create(&aulThreadID[i], nullptr, ThreadFunc, &aulArr[i]));}for (int i = 0; i < MAX_THREADS; ++i){assert(0 == pthread_join(aulThreadID[i], nullptr));}auto end = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());printf("%lld",end.count() - begin.count());
}

耗時: 26396ms

代碼二

#include <stdio.h>
#include <pthread.h>
#include <stdint.h>
#include <assert.h>
#include<chrono>const uint32_t MAX_THREADS = 16;void* ThreadFunc(void* pArg)
{for (int i = 0; i < 1000000000; ++i) // 10億次累加操作{++*(uint64_t*)pArg;}return NULL;
}int main() {static uint64_t aulArr[MAX_THREADS * 8];pthread_t aulThreadID[MAX_THREADS];auto begin = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());for (int i = 0; i < MAX_THREADS; ++i){assert(0 == pthread_create(&aulThreadID[i], nullptr, ThreadFunc, &aulArr[i * 8]));}for (int i = 0; i < MAX_THREADS; ++i){assert(0 == pthread_join(aulThreadID[i], nullptr));}auto end = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());printf("%lld",end.count() - begin.count());
}

耗時: 6762ms

這兩者的主要差別就在于pthread_create傳入的一個是aulArr[i]一個是aulArr[i * 8]

CPU Cache對于并發的影響

cpu cache在做數據同步的時候,有個最小的單位:cache line,當前主流CPU為64字節。
多個CPU讀寫相同的Cache line的時候需要做一致性同步,多CPU訪問相同的Cache Line地址,數據會被反復寫臟,頻繁進行一致性同步。當多CPU訪問不同的Cache Line地址時,無需一致性同步。
在上面的程序中:
static uint64_t aulArr[MAX_THREADS * 8];
占用的數據長度為:8byte * 8 * 16;
8byte * 8=64byte
程序一,每個線程在當前CPU讀取數據時,訪問的是同一塊cache line
程序二,每個線程在當前CPU讀取數據時,訪問的是不同塊的cache line,避免了對一個流水線的反復擦寫,效率直線提升。
在這里插入圖片描述

讀寫順序對性能的影響

CPU會有一個預讀,順帶著將需要的塊兒旁邊的塊兒一起讀出來放到cache中。所以當我們順序讀的時候就不需要從內存里面讀了,可以直接在緩存里面讀。
順序讀

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include<chrono>
#include "string.h"int main() {const uint32_t BLOCK_SIZE = 8 << 20;// 64字節地址對齊,保證每一塊正好是一個CacheLinestatic char memory[BLOCK_SIZE][64] __attribute__((aligned(64)));assert((uint64_t)memory % 64 == 0);memset(memory, 0x3c, sizeof(memory));int n = 10;auto begin = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());while (n--){char result = 0;for (int i = 0; i < BLOCK_SIZE; ++i){for (int j = 0; j < 64; ++j){result ^= memory[i][j];}}}auto end = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());printf("%lld",end.count() - begin.count());
}

亂序讀

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include<chrono>
#include "string.h"int main() {const uint32_t BLOCK_SIZE = 8 << 20;// 64字節地址對齊,保證每一塊正好是一個CacheLinestatic char memory[BLOCK_SIZE][64] __attribute__((aligned(64)));assert((uint64_t)memory % 64 == 0);memset(memory, 0x3c, sizeof(memory));int n = 10;auto begin = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());while (n--){char result = 0;for (int i = 0; i < BLOCK_SIZE; ++i){int k = i * 5183 % BLOCK_SIZE;  // 人為打亂順序for (int j = 0; j < 64; ++j){result ^= memory[k][j];}}}auto end = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());printf("%lld",end.count() - begin.count());
}

順序讀耗時13547ms,隨機亂序讀耗時21395ms。
如果一定要隨機讀的話該怎么優化呢?
如果我們知道我們下一輪讀取的數據,并且不是要立即訪問這個地址的話,使用_mm_prefetch指令優化,告訴CPU提前預讀下一輪循環的cacheline
有關該指令可以參考官方文檔:https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/84szxsww(v=vs.100)
使用該命令后,再看看運行時間:

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include<chrono>
#include "string.h"
#include "xmmintrin.h"int main() {const uint32_t BLOCK_SIZE = 8 << 20;// 64字節地址對齊,保證每一塊正好是一個CacheLinestatic char memory[BLOCK_SIZE][64] __attribute__((aligned(64)));assert((uint64_t)memory % 64 == 0);memset(memory, 0x3c, sizeof(memory));int n = 10;auto begin = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());while (n--){char result = 0;for (int i = 0; i < BLOCK_SIZE; ++i){int next_k = (i + 1) * 5183 % BLOCK_SIZE;_mm_prefetch(&memory[next_k][0], _MM_HINT_T0);int k = i * 5183 % BLOCK_SIZE;  // 人為打亂順序for (int j = 0; j < 64; ++j){result ^= memory[k][j];}}}auto end = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());printf("%lld",end.count() - begin.count());
}

從原來的21395ms優化到15291ms

字節對齊對Cache的影響

在2GB內存,int64為單元進行26億次異或。分別測試地址對齊與非對齊 在順序訪問和隨機訪問下的耗時

非地址對齊地址對齊耗時比
順序訪問7.8s7.7s1.01:1
隨機訪問90s80s1.125:1

在順序訪問時,Cache命中率高,且CPU預讀,此時差別不大。
在隨機訪問的情況下,Cache命中率幾乎為0,有1/8概率橫跨2個cacheline,此時需讀兩次內存,此時耗時比大概為:7 / 8 * 1 + 1 / 8 * 2 = 1.125
結論就是:
1、cacheline 內部訪問非字節對齊變量差別不大
2、跨cacheline訪問代價主要為額外的內存讀取開銷
所以除了網絡協議以外,避免出現1字節對齊的情況。可以通過調整成員順序,減少內存開銷。

小結

1、多線程盡量避免讀寫相同的cache line內存
2、線程訪問對象盡可能與cacheline地址對齊
3、盡可能對內存做順序讀寫,否則可使用CPU預讀指令
4、變量保持地址對齊

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/376670.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/376670.shtml
英文地址,請注明出處:http://en.pswp.cn/news/376670.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

textarea 在瀏覽器中固定大小和禁止拖動

http://blog.sina.com.cn/s/blog_641d569301011naz.html HTML 標簽 textarea 在大部分瀏覽器中只要指定行&#xff08;rows&#xff09;和列&#xff08;cols&#xff09;屬性&#xff0c;就可以規定 textarea 的尺寸&#xff0c;大小就不會改變&#xff0c;不過更好的辦法是使…

hibernate操作時報錯

報錯&#xff1a;[ERROR] AbstractBatcher Exception executing batch: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1原因&#xff1a;視具體情況而定&#xff0c;我這邊是代碼被修改過…

bugfix:MySQL內存使用率無限增長以及kill手法

問題&#xff1a;昨天mysql 宕機了一次&#xff0c;重啟&#xff0c;然后繼續運行業務代碼的時候發現問題&#xff0c;mysql內存占用率上升較快&#xff0c;于是搜了搜&#xff0c;遇到一個&#xff1a; http://blog.itpub.net/29510932/viewspace-2129312/ 根據思路&#xff0…

軟工之初識

我們之前已經在完全不懂軟件工程的情況下&#xff0c;已經做完了兩個小系統&#xff0c;雖然能夠運行&#xff0c;但其中有很多的問題&#xff0c;學習軟工就是讓我們在工程學原理的指導之下去開發和設計軟件。 軟件工程同大多數書講的都是一樣的&#xff0c;首先對軟件工程有一…

perf +火焰圖使用

以mysqld進程為例&#xff1a; [rootVM-90-225-centos ~]# ps -ef | grep mysqld root 9808 9621 0 19:30 pts/7 00:00:00 grep --colorauto mysqld root 16104 1 0 17:30 pts/0 00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir/usr/loc…

Mysql 遇到的編碼問題。

今天幫小朋友做一個項目&#xff0c;碰到一個挺搞的問題。在幫她安裝mysql的時候一直是next&#xff0c;沒有去注意一些細節&#xff0c;不曉得有沒有漏掉設置編碼那一部分。。 結果在用sql文件導入數據庫MySQL -h localhost -u root -p xxx < e:\xxx.sql 執行的時候錯誤提…

在一個字符串中找到第一個只出現一次的字符

題目&#xff1a;在一個字符串中找到第一個只出現一次的字符&#xff0c;如輸入abaccdeff&#xff0c;則輸出b&#xff1b;具體實現如下&#xff1a;#include <iostream> #include <string> using namespace std; void FindChar(const string &strBuf) {int nA…

py腳本:獲取進程信息

這里以mysqld進程為例子 # pip install psutil import psutil import time import re, sys# x:進程name y:非進程name # 由于這里監控的是mysqld&#xff0c;如果不加限制的話會先識別mysqld_safe&#xff0c;所以要加上mysql_safe的判別 def processinfo(x, y):p_list psut…

sysctl -P 報錯解決辦法

sysctl -P 報錯解決辦法問題癥狀修改 linux 內核文件 #vi /etc/sysctl.conf后執行sysctl -P 報錯error: "net.bridge.bridge-nf-call-ip6tables" is an unknown keyerror: "net.bridge.bridge-nf-call-iptables" is an unknown keyerror: "net.bridg…

-bash: belts.awk: command not found

執行awk命令時&#xff0c;沒有問題。可是執行awk腳本時&#xff0c;出現這個問題&#xff1a;-bash: belts.awk: command not found。 既然之前直接執行awk命令沒有問題&#xff0c;說明awk已經裝了&#xff0c;本身是沒有問題的。那就說明路徑不對&#xff0c;執行echo $PATH…

nagios快速安裝

1、安裝軟件包&#xff08;準備軟件包&#xff09; yum install httpd gcc glibc glibc-common gd gd-devel 2、建立一個賬戶 創建一個名為nagios的帳號并給定登錄口令 /usr/sbin/useradd nagios passwd nagios 創建一個用戶組名為nagcmd用于從Web接口執行外部命令。將nagios用…

零拷貝機制在文件傳輸中的使用手法

文章目錄文件傳輸&#xff08;讀取與發送&#xff09;中的拷貝與上下文切換零拷貝技術sendfilesendfile SG-DMAmmap writespliceDirect I/O經典應用文件傳輸&#xff08;讀取與發送&#xff09;中的拷貝與上下文切換 如果服務端要提供文件傳輸的功能&#xff0c;最簡單的方式…

Effective Modern C++翻譯(3)-條款2:明白auto類型推導

條款2 明白auto類型推導 如果你已經讀完了條款1中有關模板類型推導的內容&#xff0c;那么你幾乎已經知道了所有關于auto類型推導的事情&#xff0c;因為除了一個古怪的例外&#xff0c;auto的類型推導規則和模板的類型推導規則是一樣的&#xff0c;但是為什么會這樣呢&#xf…

信息論與編碼復習

若信源有m種消息&#xff0c;且每個消息是以相等可能產生的&#xff0c;則該信源的信息量可表示為Ilogm。 信息率是通過接收到的信息可獲得的發送信息的信息量,即互信息。單位:bit/符號。 信息速率是單位時間內傳輸的信息量。單位:bit/s 碼字CodeWord。由若干個碼元組成&#x…

拖拽碰撞效果最終版

拖拽碰撞效果最終版&#xff0c;沒準還有bug&#xff0c;不過現在在各個瀏覽器下效果是對的&#xff0c;代碼需要精簡一些&#xff0c;以后有時間了在弄吧&#xff0c;現在先不理了&#xff0c;感冒了&#xff0c;沒有心情整理 <!DOCTYPE HTML> <html lang"en-US…

Python 如何利用函數修改函數外list?

#在函數內修改列表的時候&#xff0c;在列表后面加上[:]&#xff0c;無論幾維列表均可。 def foo(listA):listA[:] [1,2,3] def foo2(listB):listB [1,2,3] listA [4,5,6] listB [4,5,6] foo(listA) foo2(listB) print listA #result: [1,2,3] print listB #result: [4,5,6…

圖片壓縮android bitmap compress(圖片壓縮)

本文純屬個人見解&#xff0c;是對前面學習的總結&#xff0c;如有描述不正確的地方還請高手指正~ 有些場景中&#xff0c;須要照相并且上傳到服務&#xff0c;但是由于圖片的巨細太大&#xff0c;那么就 上傳就 會很慢(在有些網絡情況下)&#xff0c;而且很耗流量&#xff0c;…

linux進程間通信快速入門【一】:管道編程

介紹 管道本質上就是一個文件&#xff0c;前面的進程以寫方式打開文件&#xff0c;后面的進程以讀方式打開。這樣前面寫完后面讀&#xff0c;于是就實現了通信。雖然實現形態上是文件&#xff0c;但是管道本身并不占用磁盤或者其他外部存儲的空間。在Linux的實現上&#xff0c;…

返回長度hdu 1518 square

查了好多資料&#xff0c;發現還是不全&#xff0c;干脆自己整理吧&#xff0c;至少保證在我的做法正確的&#xff0c;以免誤導讀者&#xff0c;也是給自己做個記載吧&#xff01; 題目的意思是比較明顯的&#xff0c;就是當初給你m根木棒&#xff0c;當初讓你判斷利用這些木棒…

POJ 3233 Matrix Power Series 矩陣快速冪 + 二分

題意&#xff1a;求矩陣的次方和 解題思路&#xff1a;最容易想到方法就是兩次二分因為 我們可以把一段 A^1 A^2 .......A^K 變成 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) 當k 為奇數的時候 或者 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) A^K 當K 為偶數的時候…