2019年1月9日 星期三

Pro .Net Memory Management - CH2

Pro .Net Memory Management - CH2

Low Level Memory Management

一般電腦的記憶體架構

  • Processor:包含了Arithmetic and Logical Units (ALUs),Floating-Point Units (FPUs), registers, and instruction execution pipelines
  • Front Side Bus (FSB):用來連線Processor 和 NorthBridge
  • Northbridge:控制Processor和Memory之間的通訊
  • RAM:記憶體
  • Memory Bus:連接RAM和Notrhbridge
  • Southbridge:處理所有IO相關功能, 如硬體、USB的
  • Storage I/O:硬碟

Memory

  • 記憶體的組成?包含 internal memory cells 和 buffer
  • 記憶體目前有兩種
    1. Static Random Access Memory:速度很快,沒有電資料就消失。一般放在CPU內做為CPU的Cache
    2. Dynamic Random Access Memory:使用電容製成,由於電容電力消秏的特性,所以要時常Refresh(充電)。運作上的速度有可能受到充電的影響而延遲
  • 記憶體的位置:記憶體中的資料是以網格的方式放置。要讀取特定位置的資料時,要告訴記憶體是第幾個Row,第幾個Column。由於設定上的因素,切換Row的時間遠大於切換Column。因此在讀取記憶體時,最好設計成先讀完Column後,再換下一個Row讀。而不是一直反覆的切換Row
  • RAS:Row Access Strobe 用來發出要使用那一個Row的訊號
  • CAS:Column Access Strobe 用來發出要使用一個Column的訊號
  • 記憶體效能相關時間
    • tCL:從CAS發動到生效的時間
    • tRCD:CAS和RAS連續發動所需要的最小時間
    • tRP:Row要使用前所需要的準備時間,也就是預充電(Precharge)
    • tRAS:Row從沒升到升起的時間,幾乎就是上面三個時間的總合
  • 也就是說,如果記憶體是準備好的,那讀取時間就是立刻,若只是要換Column,那就是需要一個tCL的時間。若是要換Row那就要先 tRP然後再tCL再tRCD
  • 這邊的討論最重要要說明沒有排序過的記憶體在存取上是很浪費時間的,所以一個好的記憶體管理應該要能夠將記憶體總是保擇著有序的狀況,的得讀取時的時候不會頻繁的切換Row

CPU

  • 對CPU來說,它是採用Register Machine的方式設定得,所以是使用Register來處理資料的,速度也是最快的。
  • 由於CPU的速度遠大於記憶體的傳輸速度,所以CPU在設計上會有一個Cache的區域
  • Cache Hit & Chche Miss
    • 當CPU要資料時,會先向Cache找,如果有,立即使用,這種稱為 Cache Hit。反之要不到資料時就稱為 Cache Miss。要不到資料的時候,會先將資料從記憶體載到Cache中再使用
  • Data Locality:Cache的資料可以有效的增進效率的原因有以下兩個
    • temporal locality:一個現在使用的資料多半會在很短的時間再被使用到
    • spatial locality:一個被使用到的資料,其周圍的資料有很高的機率會在短時間內被使用到
  • Cache Line:從記憶體傳到Cache的大小是固定的,就是64Bytes。這個區塊稱為 Cache Line。這意謂著若只是要一個Bytes,也的傳64個Bytes到記憶體中。
  • DRAM一次只能處理8個Bytes,所以每一次DRAM要運作8次才能滿足一次CPU要求記憶體的操作
  • Data Alignment:CPU 會以特定的倍數去存取資料,所以類別中設定的欄位大小能符合特定的倍數,可以有效能的效率。這個動作是在Compile和資料結構中去實作的。
  • Non-temporal Access:由於CPU沒有直接處理記憶體而是都要透過Cache,這會造成若要配置一塊大卻不用的記憶體時,都會從Cache -> Memory。最佳化下,我們希望這些資料是直接進記憶體而不要經過Cache。在C++中是可以透過特殊的指令達成,.Net中沒有現成的方法,只可以透過P/Invoke 呼叫 _mm_stream_si128 完成
  • Prefetching:是指預讀記憶體,由CPU去預測未來可能用到的記憶體,先讀進來。當CPU Cache Miss 多次後,它會改變它的預讀模式。或是使用者也可以用C++強制呼叫。但就算我們很了解Prefectching的模式,但程式在運作可能多執行緒等影響,就改變了預讀的行為。因此實際上 .Net 是沒有啟用這個功能的。
  • Hierarchical Cache
    • 當前的CPU在Cache設計上是多層式的,舉Intel I7,就有L1,L2和L3三種快取記憶體。L1的速度最快,容量最小。不過就算是L3,其速度也比一般記憶體快上5倍
    • 在.Net裡面,若讀取記憶體的空間超過L1、L2和L3則會對效能各有不同的影響。
    • Multicore Hierarchical Cache:指的在多核心下,依設計,每一個核心會有自己的L1,L2,共享L3。
    • simultaneous multithreading mechanism (SMT):意指一個Core跑兩個Thread的,在這種情況下,每一個Thread用一半的L1,L2。除非有特殊的需求。
    • 在多核下,代表每一個Core有自己的Cache,但這些Cache可能有代表相同的資料,因此如何做到Cache的同步就需要機制。
    • 在 .Net中,若使用一個陣列,讓多個執行緒同時去讀,由於每一個執行緒交由一個Core去跑,應該就會形成不同的Core操作各自的Cache,但這個Cache內的資料其實是相同的問題。為了處理這個問題,我們可以將每個Core要跑的陣列內容各間隔64Bytes (一個Cache Line)。這樣每個Core在操作Cache的時候,所使用的區塊都不相同,就不需要再操作Cache的同時,要考慮到惟護Cache的同步。
    • 特別要說明的是.Net的陣列是有表頭的,就在陣列的前幾個Byte。因此,若我們在分核操作資料時,若陣列一剛開始的記憶體區塊也被納入操作的話,由於.Net陣列在讀取時,都會取要範圍檢查,此檢查所需要的資料在陣列一開始,所以每次有其他區塊在操作,可能會影響第一個區塊的效能。因此可以將資料Offset,略過第一個區塊不使用,以免多工時造成影響。

Operation System

  • 作業系統使用 virtual memory 的技術,讓軟體透過 virtual address space 來取用memory。在這種設計下,軟體可以將所有電腦的memory都視為是自己的,不過考慮其他的軟體
  • 由於virtual memory 的技術,使得memory可以不一定是RAM,也可以是一般的硬碟
  • operating system memory manager 的責任有四個
    • 將 virtual memory mapping 到 實體memory
    • 將memory與 hard drive 之間的資料做交換,這種動作會透過類別 swap file 或是 page file 來產生
    • 處理 Memory-Mapped File (本文不討論)
    • 處理 Copy-On-Write 機制 (本文不討論)
  • virtual memory的技術主要是在CPU中的MMU和OS互相配合完成
  • virtual memory內的單位稱為page
  • virtual memory對應到實體記憶體的話,不是逐byte對應,而是先將所有的記憶體分成page,然後再記錄目前用的記憶體是第幾個page再offset多少
  • 在OS中會對每一個Process產生一個Page Directory 用來讓Process使用的記憶體對應到實體記憶體上。由於每一個Process都會有一個Page Directory設計的不好,會造成浪費額外的記憶體。目前的Page Directory都用4層的結構
  • Page Directory 的四層也是會放在L1,L2,L3 Cache內,每次要找記憶體時,若是都要找4層,也不好。因此還有一個Translation Look Aside Buffer 來記錄selector及 page’s physical address
  • Large pages 是一種一次配置連續多個Page的機制,這樣可以避免將Page大小設定太大造成浪費的問題。因此,我們可以將Page設成一般大小。針對特殊的應用,使用Large pages。如資料庫軟體
  • Large pages
  • Windows的記憶體管理中,page有四種型式
    • Free 沒有被指派給任何的Process或是OS
    • Committed (private) 已經指派給某個Process。這種頁面可以改放到硬碟中或從硬碟中再轉換回來。這種記憶體也可以呼叫Lock,確保此記憶體區塊不會被移動
    • Reserved 保留給某個Process,但只有指派virtual address 而沒有真的使用到實體RAM上。這多半是為了要預先留下一個連續的區塊而使用。當然也是有方法在指派的同時立Committed,如果有確定就是會用到那麼大的話
    • Shareable 保留給某個Process,但其他的Process也可以看得到。這種用在MMF或是 DLL、Resource( Font …)
  • 如果操作Free 或是 Reserved 的記憶體的話,會產生Access Violation Exception。因為這些的virtual address 都還沒有對應到真實的記憶體上
  • 在Windows的記憶體管理中,如下分類
    • Working set 代表目前virtual address space 有真實對映到記憶體中的部分。Working set 有可以再細分成以下四類
      • Private working set 代表是 committed page 在 真實記憶體的部分
      • Shareable working set 代表所有shareable pages 的大小,不管是否已被share
      • Shared working set 目前正被其他Process share的 sahreable pages 的大小
      • Private bytes 所有的committed page 的大小,含記憶體和硬體部分
      • Virtual bytes 就是 Private bytes 再加上 reserved memory 的部分
      • Paged bytes 存在page file內的virtual bytes 的大小
    • 一般要說CLR用了多少記憶體,可以看 private working set 。這代表就是Process所有用到的實體記憶體的大小
  • Windows 在幫Process配置記憶體時,有最小的單位,每一次都會佔64KB。假想Windows已經將記憶體用64KB都切分好。如果使用者只要64KB中間的2KB,還是會從這64KB記憶體開始的位置,一直配到需要的2KB。且這2KB之後的記憶體到下一個64KB之前的記憶體,都會被視為無效浪費掉。
  • Linux和Windwos的記憶體操作有兩個很大的不同
    • Linux由於採用了 Lazy 配置記憶體的方式,所以沒有辦法像Windows有配置 Reserved 記體體的方式
    • Windows 有system call 可以讓GC監控讓些Page有改變,但Linux沒有提供
      <configuration> <runtime> <Thread_UseAllCpuGroups enabled="true"/> <GCCpuGroup enabled="true"/> <gcServer enabled="true"/> </runtime> </configuration>