自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

飛哥帶你揭秘:為什么HugePage能讓Oracle數(shù)據(jù)庫如虎添翼?

數(shù)據(jù)庫 Oracle
為了提升地址轉(zhuǎn)換效率。CPU 硬件中設(shè)計有 TLB 模塊,用于緩存內(nèi)存中的頁表項,加速訪問。這樣 CPU 在執(zhí)行虛擬地址轉(zhuǎn)換時,就可以避免很多的內(nèi)存訪問,極大地提升效率。

大家如果有人部署過 Oracle 數(shù)據(jù)庫的話,一定也看到過 Oracle 為了性能考慮,是推薦開啟大頁(HugePage)的。

那么為什么開啟大頁 能有性能提升,它的優(yōu)化原理是啥,又是如何實現(xiàn)的呢?今天飛哥就來和你一起深入地聊聊這個 Topic。

一、 內(nèi)核四級頁表之殤

為了更好了解 HugePage,我們需要溫習(xí)一下內(nèi)核的頁表機制。

在這個機制中有兩個前提知識點,那就是

  • 第一、應(yīng)用程序申請內(nèi)存時不會分配物理內(nèi)存,訪問觸發(fā)缺頁中斷時才分配!
  • 第二、頁是內(nèi)核分配物理內(nèi)存的最小單位!

我們應(yīng)用程序使用的都是虛擬內(nèi)存地址。在程序?qū)嶋H運行的時候,需要轉(zhuǎn)換成實際的物理地址。如果轉(zhuǎn)換后的物理地址所在的頁面正好存在,那么直接訪問就可以了。如果頁面不存在,那么需要觸發(fā)缺頁中斷并申請一個完整的頁面后再供應(yīng)用程序繼續(xù)訪問。頁的最小單位是 4 KB。

在《深入理解Linux進程與內(nèi)存》里的第六章「進程如何使用內(nèi)存」中,我們提到過 Linux 將虛擬地址到物理地址中用到的四級頁表機制。

圖片圖片

內(nèi)核四級頁表機制把 64 位的內(nèi)存地址范圍分成了幾段。

  • 第 63-48 位,額。。64位內(nèi)存地址太大了,這段屬于廢棄不用的。
  • 第 39-47(9)位指定在一級頁表 PGD 中索引位置
  • 第 30-38(9)位指定在二級頁表 PUD 中索引位置
  • 第 21-29(9)位指定在對應(yīng)三級頁表 PMD 中索引位置
  • 第 12-20(9)位指定在四級頁表 PTE 中索引位置


大家注意下,每一級頁表管理的地址范圍都是 9 個位。為啥是 9 ,不是 8 ,也不是 10。原因是為了將數(shù)據(jù)結(jié)構(gòu)對齊到 4 KB。這樣具體的一個 PGD/PUD/PMD/PTE,保存著 2 的 9 次方, 512 個 64 位物理地址(8個字節(jié))。512 * 8 = 正好是 4 KB。

在將某進程的一個具體的 64 位的虛擬內(nèi)存地址轉(zhuǎn)換為物理地址時,首先按照上述地址范圍把虛擬地址切分成幾段。然后經(jīng)過下面幾步轉(zhuǎn)換成物理地址。

  • 第一步:從 CPU 中名為 CR3 的寄存器中找到當(dāng)前進程的一級頁表 PGD 的地址
  • 第二步:以虛擬地址中的 39 ~ 47 位作為索引,找到 PUD 所在的內(nèi)存地址
  • 第三步:再以虛擬地址中的 30 ~ 38  位作為索引,找到 PMD 所在的內(nèi)存地址
  • 第四步:再以虛擬地址中的 21 ~ 29  位作為索引,找到 PTE 所在的內(nèi)存地址
  • 第五步:再以虛擬內(nèi)存地址的 0 ~ 11 位作為物理內(nèi)存頁的偏移量,得到最終的物理地址

Linux分頁機制就帶領(lǐng)大家簡單回憶這么一下。今天我們的重點是想說頁表機制帶來的額外的問題。

頁表是存在內(nèi)存里的。完成一個虛擬地址轉(zhuǎn)換的過程中需要把當(dāng)前虛擬地址對應(yīng)的四個頁表全部找出來才能完成虛擬地址到物理地址的轉(zhuǎn)換。那就是一次內(nèi)存 IO 光是虛擬地址到物理地址的轉(zhuǎn)換就要去內(nèi)存查 4 次頁表。再算上真正的內(nèi)存訪問,最壞情況下需要 5 次內(nèi)存 IO 才能獲取一個內(nèi)存數(shù)據(jù)!

為了提升地址轉(zhuǎn)換效率。既然進行地址轉(zhuǎn)換需要的內(nèi)存 IO 次數(shù)多,且耗時。那么干脆就和 CPU 的 L1、L2、L3 的緩存思想一樣,在 CPU 里把頁表中的數(shù)據(jù)盡可能地緩存起來不就行了么,

所以 CPU 硬件中有個 TLB(Translation Lookaside Buffer) 模塊,專門用于加速虛擬地址到物理地址轉(zhuǎn)換速度的緩存。其訪問速度非??欤图拇嫫飨喈?dāng),比 L1 訪問還快。

雖然有了 TLB 加速的方案,但這個方案并不是萬能的。最大的缺點是 TLB 太小了。一般的 CPU 中 L1 TLB 一般也就幾十個條目容量,L2 TLB 一般也就小幾千。

再看需求端,我們假設(shè)每個進程需要 40 GB 物理內(nèi)存,那換算成 4 KB 頁面的話就是大約 1000 萬個頁面,也就對應(yīng) 1000 萬個頁表條目。TLB 里這點點容量還是捉襟見肘。

正因為在四級頁表下有這樣潛在的性能隱患。所以 Oracle 這種內(nèi)存密集型的應(yīng)用就推薦配置 HugePage 來提高它的運行性能了。

二、HugePage 如何使用

可見,四級頁表最大的問題是在于頁面太多時性能較差。頁面一多,管理這些頁面的頁表項就多,TLB緩存命中率就會很差。那如果能把頁面數(shù)量給降下來,TLB 緩存命中率一定會有大幅度的提升。

假如說我們把 4 KB 的頁面換成 2 MB 的頁面,那么同樣對于 40 GB 物理內(nèi)存消耗,那僅僅只需要 2 萬個頁面就夠了。相比于原來的 1000 萬 降低到了 500 之一。

另外這樣不光是 TLB 緩存命中率會有大幅度的提升。內(nèi)核的虛擬地址轉(zhuǎn)換時的頁表機制也可以簡化成下面這樣的三級頁表,少了一次轉(zhuǎn)換開銷。

圖片

所以,一個結(jié)論是把 4 KB 的頁面換成 2 MB 的頁面,可以大幅度提升虛擬地址轉(zhuǎn)換物理地址時的性能!!

那么,如果你想獲取這個性能提升的話,該如何操作呢?

第一步首先是大頁的預(yù)留

預(yù)留的方式分為啟動階段預(yù)留和運行時預(yù)留。

對于啟動階段預(yù)留,需要修改 Linux 內(nèi)核的啟動參數(shù)。編輯/boot/grub/grub.cfg 文件找到啟動參數(shù)行(不同的發(fā)行版可能修改方式會有一些出入)。添加以下內(nèi)容,指定 HugePage 的頁面大小,指定預(yù)留的大頁數(shù)量。:

hugepagesz=2M hugepages=512

對于運行時預(yù)留,直接修改內(nèi)核 hugetlbfs 暴露出來的偽文件即可。

// 預(yù)留特定size的大頁
echo 5 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

第二步是大頁的申請

申請的時候,先打開通過 open 打開 hugepage 偽文件句柄,再通過 mmap 來申請即可。

int main(){
 // 打開 hugepage 句柄
 fd = open("/mnt/huge/hugepage...", O_CREAT|O_RDWR);
   
    // 申請大頁
    addr = mmap(0, MAP_LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
}

這樣,你的應(yīng)用程序就能享受 TLB 緩存命中率提升帶來的飛翔感覺了。

三、內(nèi)核啟動時 HugePage 處理

咱們「開發(fā)內(nèi)功修煉」公眾號的風(fēng)格是不僅要會用,還要懂內(nèi)部原理。接下來飛哥再來帶你看下內(nèi)核是如何管理 HugePage 的!

3.1 回顧普通頁的伙伴系統(tǒng)

在《深入理解Linux進程與內(nèi)存》里的第五章「系統(tǒng)物理內(nèi)存初始化」中介紹過,

  • 內(nèi)核先是通過固件 ACPI E820 規(guī)范探測安裝的內(nèi)存的物理地址范圍
  • 將探測到的內(nèi)存交給 memblock 初期內(nèi)存分配器來管理,同時會再讀取 ACPI 中的 SRAT 表獲取 NUMA 信息
  • 接著在初期內(nèi)存分配器中申請管理所有頁面的 struct page 對象(一個 struct page 一般是 64 字節(jié))
  • 最后釋放其余的可用內(nèi)存交給伙伴系統(tǒng)來管理
start_kernel
-> setup_arch
---> e820__memory_setup   // 內(nèi)核把物理內(nèi)存檢測保存從boot_params.e820_table保存到e820_table中,并打印出來
---> e820__memblock_setup // 根據(jù)e820信息構(gòu)建memblock內(nèi)存分配器,開啟調(diào)試能打印
---> initmem_init         // 內(nèi)存中 NUMA 機制初始化)
---> x86_init.paging.pagetable_init(native_pagetable_init)
-----> paging_init        // 頁管理機制的初始化
-> mm_init
---> mem_init
-----> memblock_free_all  // 向伙伴系統(tǒng)移交控制權(quán)
// file:include/linux/mmzone.h
struct zone {
 ......
 // zone的名稱
 const char  *name;

 // 管理zone下面所有頁面的伙伴系統(tǒng)
 struct free_area free_area[MAX_ORDER];
 ......
}

圖片圖片

3.2 空閑 HugePage 的管理

相比伙伴系統(tǒng)中 4KB 頁面的管理,內(nèi)核對 HugePage 頁面的管理要簡單許多。內(nèi)核中維持一個各種 HugePage 頁面(內(nèi)核支持多種大小的 HugePage,不僅僅只有 2 MB)的 struct hstate 數(shù)組。

// file:mm/hugetlb.c
struct hstate hstates[HUGE_MAX_HSTATE];

在每一個 hstate 成員內(nèi),有一個空閑鏈表 hugepage_freelists,會把所有的空閑頁面給串起來。

圖片

我們來看大致看下空閑頁面的初始化過程。內(nèi)核啟動過程中,還會按照一定的順序執(zhí)行初始化函數(shù)。HugePage 的初始化函數(shù) hugetlb_init 通過 subsys_initcall 注冊。

// file:mm/hugetlb.c
subsys_initcall(hugetlb_init);

這樣內(nèi)核啟動的時候,就會執(zhí)行到 hugetlb_init 進行 HugePage 的初始化。

// file:mm/hugetlb.c
static int __init hugetlb_init(void)
{
 ...
 // 初始化默認大頁 state,空閑大內(nèi)存頁鏈表 hugepage_freelists
 hugetlb_add_hstate(HUGETLB_PAGE_ORDER);
 // 申請大內(nèi)存頁, 并且保存到 hugepage_freelists 鏈表中
 hugetlb_init_hstates();
 ...

 // 創(chuàng)建/sys/kernel/mm/hugepages相關(guān)目錄文件
 hugetlb_sysfs_init();
 // 創(chuàng)建/sys/device/system/node/node*/hugepages相關(guān)目錄文件
 hugetlb_register_all_nodes();
 ...
}

hugetlb_init 函數(shù)主要完成兩個工作:

第一:初始化默認大頁 state。在 Linux 中是支持多種規(guī)格的大頁的,存在一個全局變量 states 數(shù)組,其中每一個元素都對應(yīng)一個規(guī)格的大頁的管理數(shù)據(jù)結(jié)構(gòu),包括所有空閑頁面管理用的鏈表 hugepage_freelists。

第二:為系統(tǒng)申請空閑的大內(nèi)存頁,并且保存到空閑鏈表 hugepage_freelists 中。

第三:創(chuàng)建 hugetlbfs 相關(guān)偽文件,如 /sys/kernel/mm/hugepages、/sys/device/system/node/node*/hugepages。用戶后續(xù)可以通過這些偽文件來和內(nèi)核交互。

我們來重點看下申請空閑大內(nèi)存頁的邏輯,這是依次調(diào)用 hugetlb_init_hstates -> hugetlb_hstate_alloc_pages,在執(zhí)行到 hugetlb_hstate_alloc_pages_onenode 中完成的。

// file:mm/hugetlb.c
static void __init hugetlb_hstate_alloc_pages_onenode(struct hstate *h, int nid)
{
 ...
 for (i = 0; i < h->max_huge_pages_node[nid]; ++i) {
  page = alloc_fresh_huge_page(h, gfp_mask, nid,
     &node_states[N_MEMORY], NULL);
  if (page)
   break;
 }

 free_huge_page(page);
 return 1;
}

其中 alloc_fresh_huge_page 是在申請頁面,free_huge_page 會將其放到空閑鏈表 hugepage_freelists 中。

四、mmap 申請內(nèi)存

大頁的內(nèi)存申請內(nèi)核工作原理大概分三步:

  • 第一先是要打開 HugePage 偽文件句柄,
  • 第二是通過 mmap 申請大頁
  • 第三是在訪問缺頁中斷時實際申請真正的物理大頁
int main(){
 // 打開 hugepage 句柄
 fd = open("/mnt/huge/hugepage...", O_CREAT|O_RDWR);
   
    // 申請大頁
    addr = mmap(0, MAP_LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
}

4.1 打開 HugePage 偽文件句柄

調(diào)用 open 打開 hugetlbfs 下的文件時,會執(zhí)行到 hugetlb_file_setup 函數(shù),在這里會給申請文件內(nèi)核對象,為它指定它所綁定的各種 operations 方法。

// file:fs/hugetlbfs/inode.c
struct file *hugetlb_file_setup(const char *name, ...)
{
 ...
 file = alloc_file_pseudo(inode, mnt, name, O_RDWR,
     &hugetlbfs_file_operations);
 ...
}

其中 hugetlbfs_file_operations 指定了這類文件的各種具體的方法。

const struct file_operations hugetlbfs_file_operations = {
 .read_iter  = hugetlbfs_read_iter,
 .mmap   = hugetlbfs_file_mmap,
 .fsync   = noop_fsync,
 .get_unmapped_area = hugetlb_get_unmapped_area,
 ......
};

這樣當(dāng)對該文件執(zhí)行 mmap 操作時,就會調(diào)用到內(nèi)核中的 hugetlbfs_file_mmap 函數(shù)。

4.2 mmap 分配虛擬內(nèi)存

mmap 系統(tǒng)調(diào)用執(zhí)行經(jīng)過如下的復(fù)雜調(diào)用鏈后,最終會調(diào)用到 file 內(nèi)核對象的 map 方法。

mmap                        // offset轉(zhuǎn)成頁為單位
+-- sys_mmap_pgoff          // 通過fd獲取file
    +-- vm_mmap_pgoff       // 信號量保護,映射完成后populate
        +-- do_mmap_pgoff   // 簡單封裝
            +-- do_mmap     // 映射長度頁對齊,prot和flags檢查,設(shè)置vm_flags,獲取映射虛擬地址
                +-- mmap_region      // 地址空間檢查,vma_merge,vma分配及初始化
                    |-- call_mmap    // 文件映射,簡單封裝
                    |   +-- file->f_op->mmap    // 調(diào)用實際文件的mmap方法
                    ....

執(zhí)行到的 file->f_op->mmap 是一個函數(shù)指針。在上一小節(jié)我們看到對于 hugetlbfs 下的文件,其 mmap 函數(shù)指針對應(yīng)的是 hugetlbfs_file_mmap 函數(shù)。

// file:fs/hugetlbfs/inode.c
static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
 ...
 // 為映射分配所需的大頁框
 hugetlb_reserve_pages(inode,
    vma->vm_pgoff >> huge_page_order(h),
    len >> huge_page_shift(h), vma,
    vma->vm_flags)
 ...
}

在該函數(shù)中主要做的就是調(diào)用 hugetlb_reserve_pages 預(yù)留大頁。

4.3 缺頁中斷處理

當(dāng)缺頁中斷發(fā)生時,內(nèi)核會調(diào)用到 handle_mm_fault 函數(shù)。在這里對于 HugePage、普通缺頁、透明大頁的處理都是不一樣的。

// file:mm/memory.c
vm_fault_t handle_mm_fault(struct vm_area_struct *vma, ...)
{
 ...
 // 是否是大頁缺頁
 if (is_vm_hugetlb_page(vma))
  ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
 else
  // 普通的缺頁中斷,包括透明大頁也都在這里
  ret = __handle_mm_fault(vma, address, flags);
 ...
}

HugePage 缺頁會執(zhí)行到 hugetlb_fault 函數(shù),然后再調(diào)用 hugetlb_no_page。

static vm_fault_t hugetlb_no_page(struct mm_struct *mm, ...)
{
 page = find_lock_page(mapping, idx);
 if (!page) {
  ...
  // 1. 從空閑大內(nèi)存頁鏈表 hugepage_freelists 中申請一個大內(nèi)存頁
  page = alloc_huge_page(vma, haddr, 0);
 }

 // 2. 通過大內(nèi)存頁的物理地址生成頁表表項
 new_pte = make_huge_pte(vma, page, ((vma->vm_flags & VM_WRITE)
    && (vma->vm_flags & VM_SHARED)));
 // 3. 將頁表表項掛到頁表中
 set_huge_pte_at(mm, haddr, ptep, new_pte);
 ...
 return ret;
}

在 hugetlb_no_page 中主要做了兩件事:

  • 第一件:調(diào)用 alloc_huge_page 從空閑鏈表中 hugepage_freelists 摘一個頁面下來 
  • 第二件:設(shè)置頁表。先是通過大內(nèi)存頁的物理地址生成頁表表項,再將頁表表項掛到頁表中

這樣,應(yīng)用程序就申請到了大頁物理內(nèi)存了。

五、總結(jié)

我們應(yīng)用程序使用的都是虛擬內(nèi)存地址。在程序?qū)嶋H運行的時候,需要轉(zhuǎn)換成實際的物理地址。

為了提升地址轉(zhuǎn)換效率。CPU 硬件中設(shè)計有 TLB 模塊,用于緩存內(nèi)存中的頁表項,加速訪問。這樣 CPU 在執(zhí)行虛擬地址轉(zhuǎn)換時,就可以避免很多的內(nèi)存訪問,極大地提升效率。

但可惜的是 TLB 緩存容量都不大,一般 CPU 中 L1 TLB 一般也就幾十個條目容量,L2 TLB 一般也就小幾千,我手頭的一臺服務(wù)器 L2 TLB 才是 1500 個條目。

如果使用 4 KB 的小頁面。假設(shè)每個進程需要 40 GB 物理內(nèi)存,每個頁面 4 KB,那就是大約 1000 萬個頁面,也就要管理 1000 萬個頁表條目。區(qū)區(qū) 1500 個 TLB 緩存條目空間,顯然是捉襟見肘。

如果使用 2 MB 的 HugePage, 40 GB / 2 MB,只需要 2 萬個頁面。管理的頁表條目一下子從 1000 萬下降到了 2萬,這樣 1500 個條目就挺充裕的了。

使用 HugePage 能幫助 TLB 緩存命中率得到了大大的提升。應(yīng)用程序在執(zhí)行虛擬地址到物理地址的轉(zhuǎn)換過程中就會節(jié)約許多開銷。

Oracle 數(shù)據(jù)庫是一個存儲密集型的應(yīng)用,會申請大量的內(nèi)存,也會涉及到大量的內(nèi)存訪問。那么用 HugePage 優(yōu)化一下性能的話,對于它來講再合適不過了。

要補充提的一點是,如果你的應(yīng)用程序使用的內(nèi)存很小,例如只有幾百 M,那建議你還是不要費這個勁兒了,提升不了多少。

責(zé)任編輯:武曉燕 來源: 開發(fā)內(nèi)功修煉
相關(guān)推薦

2021-12-07 09:12:32

Iptables 原理工具

2014-02-14 09:22:36

AWSOracle云數(shù)據(jù)庫

2020-03-27 16:05:49

數(shù)據(jù)庫數(shù)據(jù)MySQL

2020-02-19 15:01:30

數(shù)據(jù)庫SQL技術(shù)

2011-03-25 09:27:40

Oracle數(shù)據(jù)庫回滾前退

2020-08-10 09:07:00

數(shù)據(jù)庫IT技術(shù)

2020-11-10 08:38:43

數(shù)據(jù)庫HugePages內(nèi)存

2024-01-08 08:15:57

數(shù)據(jù)庫優(yōu)化內(nèi)存

2021-10-22 05:52:27

數(shù)據(jù)庫調(diào)整大小容量

2025-04-03 11:04:40

2010-04-23 09:23:44

Oracle 數(shù)據(jù)庫

2011-05-26 10:30:12

Oracle數(shù)據(jù)庫約束

2015-08-21 12:59:38

Oracle數(shù)據(jù)庫

2011-03-10 13:24:26

2020-02-25 17:04:05

數(shù)據(jù)庫云原生分布式

2023-12-13 21:56:14

云數(shù)據(jù)庫性能云架構(gòu)師

2020-05-28 11:25:55

AI 數(shù)據(jù)人工智能

2011-03-15 14:54:08

NoSQL

2021-02-18 09:23:47

數(shù)據(jù)庫分區(qū)數(shù)據(jù)庫倉庫

2011-03-16 08:54:45

Oracle數(shù)據(jù)庫索引
點贊
收藏

51CTO技術(shù)棧公眾號