《每個(gè)程序員都應(yīng)該了解的硬件知識》讀后

原文鏈接:每個(gè)程序員都應(yīng)該了解的硬件知識

?????????????????????

我 - 2024-06-01 21:27:35

本文旨在通過多個(gè)可運(yùn)行的 benchmark 介紹常見的優(yōu)化細(xì)節(jié)以及與之相關(guān)的硬件知識,為讀者建立一個(gè)簡單、有效的硬件心智模型

這里的benchmark啥意思

?????????????????????

HuluAI - 2024-06-01 21:27:35

在你提到的語境中,benchmark 是指一套用于評估計(jì)算機(jī)系統(tǒng)性能的基準(zhǔn)程序或測試。通過運(yùn)行這些基準(zhǔn)測試,可以測量和比較系統(tǒng)在執(zhí)行特定任務(wù)或應(yīng)用程序時(shí)的表現(xiàn)。

具體來說,benchmark 通常用于以下目的:

  1. 性能評估:評估計(jì)算機(jī)硬件(如 CPU、內(nèi)存、存儲設(shè)備、緩存等)和軟件(如編譯器優(yōu)化、操作系統(tǒng)、庫等)的性能。
  2. 優(yōu)化驗(yàn)證:評估不同優(yōu)化策略(如編程優(yōu)化、編譯優(yōu)化、硬件配置調(diào)優(yōu)等)的效果。
  3. 比較分析:比較不同計(jì)算機(jī)系統(tǒng)或配置在相同任務(wù)下的性能表現(xiàn)。

在上述文中,通過多個(gè)可運(yùn)行的 benchmark 可以展示和驗(yàn)證常見的性能優(yōu)化細(xì)節(jié)。這些 benchmark 可能包括:

  • 算法性能測試:評估不同算法在相同輸入條件下的執(zhí)行效率。
  • 微基準(zhǔn)測試(Microbenchmark):測試特定硬件部件(如 CPU 緩存、內(nèi)存子系統(tǒng)等)的性能。
  • 應(yīng)用基準(zhǔn)測試:使用真實(shí)世界中的應(yīng)用程序或工作負(fù)載來評測整體系統(tǒng)性能,例如圖形渲染、數(shù)據(jù)庫查詢、科學(xué)計(jì)算等。

通過這些 benchmark,讀者可以更好地理解如何進(jìn)行性能優(yōu)化以及硬件在這些優(yōu)化中的作用。

?????????????????????

我 - 2024-06-01 21:28:30

pub fn row_major_traversal(arr: &mut Vec<Vec<usize>>) {
    let n = arr.len();
    for i in 0..n {
        assert!(arr[i].len() == n);
        for j in 0..n {
            arr[i][j] += j;
        }
    }
} 

這是什么語言代碼

?????????????????????

HuluAI - 2024-06-01 21:28:30

這段代碼看起來是Rust語言的代碼,用于執(zhí)行一個(gè)二維數(shù)組的行優(yōu)先遍歷并修改其中的元素。下面是代碼的解釋:

  • pub 關(guān)鍵字表明這是一個(gè)公共函數(shù),可以在程序的其他部分訪問。
  • fn 關(guān)鍵字用來定義一個(gè)函數(shù)。
  • row_major_traversal 是這個(gè)函數(shù)的名稱,意味著它執(zhí)行的操作是按行(row)主要順序進(jìn)行遍歷。
  • arr: &mut Vec<Vec<usize>> 是函數(shù)的參數(shù),表示一個(gè)可變的二維 usize 類型的向量(vector),也就是一個(gè)二維數(shù)組。
  • let n = arr.len(); 聲明了一個(gè)變量 n,賦值為二維數(shù)組的長度,也就是矩陣的行數(shù)。
  • 接下來的兩個(gè) for 循環(huán)用于遍歷二維數(shù)組中的每個(gè)元素,然后對每個(gè)元素執(zhí)行 arr[i][j] += j; 的操作,即該位置的值加上其在行中的索引 j
  • assert!(arr[i].len() == n); 是一個(gè)斷言(assertion),用于在每次循環(huán)迭代時(shí)檢查當(dāng)前行的長度是否和矩陣的行數(shù)相等,以確保二維數(shù)組是一個(gè)方陣(每行長度相等)。

總體來說,這段代碼是一個(gè)簡單的二維數(shù)組行優(yōu)先遍歷的函數(shù),它遍歷二維數(shù)組 arr 的每一行,對每個(gè)元素執(zhí)行加上其在行中的索引值的操作。

?????????????????????

我 - 2024-06-01 21:30:09

在現(xiàn)代的存儲架構(gòu)中,cpu 和主存之間是 cache 。cpu 中的寄存器、高速緩存、內(nèi)存三者的數(shù)據(jù)讀寫速度越來越慢。
這里的主存是啥意思?

?????????????????????

HuluAI - 2024-06-01 21:30:09

在計(jì)算機(jī)系統(tǒng)中,主存(Main Memory)通常指的是內(nèi)存模塊,也稱為隨機(jī)存取存儲器(RAM,Random Access Memory)。主存是計(jì)算機(jī)中用于存儲程序運(yùn)行時(shí)數(shù)據(jù)和指令的地方,是CPU可以直接訪問的內(nèi)存存儲區(qū)域。

主存在計(jì)算機(jī)系統(tǒng)中扮演著關(guān)鍵的角色,因?yàn)樗鎯α水?dāng)前正在運(yùn)行的程序、數(shù)據(jù)結(jié)構(gòu)以及操作系統(tǒng)本身的信息。當(dāng)程序被執(zhí)行時(shí),數(shù)據(jù)和指令都會從主存中加載到CPU中進(jìn)行處理。主存通常比CPU中的寄存器和高速緩存(Cache)慢,但比較硬盤或固態(tài)硬盤等長期存儲介質(zhì)來說速度要快很多。

在存儲層次結(jié)構(gòu)中,存儲設(shè)備的速度和容量呈現(xiàn)出遞減且成本遞增的趨勢,即從寄存器(Register)速度最快、容量最小,到高速緩存(Cache)速度較快、容量適中,再到主存(Main Memory)速度較慢、容量較大,最后是磁盤或固態(tài)硬盤速度最慢、容量最大。

因此,對于計(jì)算機(jī)系統(tǒng)的設(shè)計(jì)和性能優(yōu)化來說,合理利用和管理這些存儲設(shè)備的層次結(jié)構(gòu)是非常重要的,以確保程序能夠高效地訪問數(shù)據(jù)并提升整體運(yùn)行效率。

?????????????????????

我 - 2024-06-01 21:31:44

L1 L2 cache refrence是什么

?????????????????????

HuluAI - 2024-06-01 21:31:44

"L1" 和 "L2" cache 是計(jì)算機(jī)系統(tǒng)中用于加速CPU對數(shù)據(jù)和指令訪問的兩種高速緩存級別。這兩種緩存通常被稱為處理器緩存。

  • L1 Cache(一級緩存):

    • L1 Cache 是與CPU核心緊密結(jié)合的高速緩存,用于存儲近期被頻繁訪問的指令和數(shù)據(jù)。它通常分為 L1i (instruction cache,指令緩存) 和 L1d (data cache,數(shù)據(jù)緩存) 兩部分,分別用于存儲指令和數(shù)據(jù)。
    • L1 Cache 速度非???,與CPU核心的速度接近,但容量較小。
    • L1 Cache 以字節(jié)或者緩存行為單位進(jìn)行數(shù)據(jù)訪問。
    • L1 Cache 的命中率一般比較高,因?yàn)樗鎯α薈PU當(dāng)前正在執(zhí)行的指令和數(shù)據(jù)。
  • L2 Cache(二級緩存):

    • L2 Cache 通常作為位于 L1 Cache 和主存之間的緩存層,用于存儲一些 L1 Cache 沒有的數(shù)據(jù),并為L1 Cache 提供更大的數(shù)據(jù)存儲空間。
    • 相比于 L1 Cache,L2 Cache 容量更大,速度可能稍慢一些,但比主存還是要快很多。
    • CPU通常會先在 L1 Cache 中查找數(shù)據(jù),如果未命中,則繼續(xù)在 L2 Cache 中查找,以提高數(shù)據(jù)訪問效率。

"Cache Reference"(緩存引用)是指CPU嘗試在緩存中查找數(shù)據(jù)時(shí)的操作。當(dāng)CPU需要訪問數(shù)據(jù)或指令時(shí),它會首先檢查 L1 Cache,如果數(shù)據(jù)在 L1 Cache 中找到了,就會產(chǎn)生一個(gè) L1 Cache Reference(一級緩存引用)。如果在 L1 Cache 中未找到,CPU會進(jìn)一步查找 L2 Cache,如果數(shù)據(jù)在 L2 Cache 中找到,則會產(chǎn)生一個(gè) L2 Cache Reference(二級緩存引用)。如果數(shù)據(jù)既不在 L1 也不在 L2 Cache 中,就會發(fā)生緩存失效(Cache Miss),此時(shí)CPU需要從主存中獲取數(shù)據(jù),這通常會花費(fèi)更多的時(shí)間。

綜合來看,L1 和 L2 Cache 的設(shè)計(jì)旨在減少CPU訪問主存的延遲,提高數(shù)據(jù)訪問速度,從而加速計(jì)算機(jī)系統(tǒng)的整體性能。

?????????????????????

我 - 2024-06-01 21:34:49

當(dāng) cpu 讀取一個(gè)數(shù)據(jù)的時(shí)候,會先嘗試從 cache 中讀取。如果發(fā)生 cache miss 的時(shí)候,才會將數(shù)據(jù)從主存中加載到 cache 中再讀取。而值得注意的是,cpu 每一次的讀取都是以 cache line 為單位的。也就是說,cpu 在讀取一個(gè)數(shù)據(jù)的時(shí)候,也會將該數(shù)據(jù)相鄰的、一個(gè) cache line 內(nèi)的數(shù)據(jù)也加載到 cache 中。

這里的cache line是什么東西,通俗易懂解釋一下

?????????????????????

HuluAI - 2024-06-01 21:34:49

在計(jì)算機(jī)系統(tǒng)中,緩存(cache)是用于暫時(shí)存放經(jīng)常訪問的數(shù)據(jù)副本的一種高速存儲設(shè)備。而緩存行(cache line)是組成緩存的最小單位之一。

通俗地來說,緩存行就好比是存儲在緩存中的一小塊數(shù)據(jù),當(dāng)CPU需要訪問內(nèi)存中的某個(gè)數(shù)據(jù)時(shí),它不僅會將這個(gè)特定的數(shù)據(jù)加載到緩存中,還會連帶著將與這個(gè)數(shù)據(jù)相鄰的一小塊數(shù)據(jù)也一起加載,這一小塊數(shù)據(jù)就是緩存行。

為什么要以緩存行為單位進(jìn)行數(shù)據(jù)加載呢?這是出于空間局部性和時(shí)間局部性的考慮:

  • 空間局部性:一般來說,程序在執(zhí)行時(shí)會訪問相鄰的數(shù)據(jù),因?yàn)閿?shù)據(jù)通常是存儲在一起的,這就體現(xiàn)了空間局部性。通過加載整個(gè)緩存行,CPU可以同時(shí)獲取到鄰近數(shù)據(jù),而不僅僅是單獨(dú)的那個(gè)請求數(shù)據(jù),從而在未來有可能提高訪問效率。
  • 時(shí)間局部性:如果一個(gè)數(shù)據(jù)被訪問,那么在短時(shí)間內(nèi)它很可能會再次被訪問。通過加載整個(gè)緩存行,可以預(yù)先將周圍的數(shù)據(jù)加載到緩存中,以利用這種時(shí)間局部性。

因此,當(dāng)CPU發(fā)生緩存不命中(cache miss)時(shí),會從主存中加載整個(gè)緩存行到緩存中,而不僅僅是所需的單個(gè)數(shù)據(jù),這樣在未來訪問相鄰數(shù)據(jù)時(shí),就可以直接從緩存中獲取,加快數(shù)據(jù)訪問速度。這種緩存行的加載方式有助于提高計(jì)算機(jī)系統(tǒng)的性能和效率。

?????????????????????

我 - 2024-06-01 21:36:32

cache line的數(shù)據(jù)是對主存的數(shù)據(jù)的引用還是拷貝的副本呢?

?????????????????????

HuluAI - 2024-06-01 21:36:32

Cache line中的數(shù)據(jù)通常是主存數(shù)據(jù)的副本,而不是直接引用主存中的數(shù)據(jù)。當(dāng)CPU需要訪問某個(gè)數(shù)據(jù)時(shí),會從主存中加載一個(gè)緩存行到緩存中,包括目標(biāo)數(shù)據(jù)及其鄰近的數(shù)據(jù),并在緩存中維護(hù)這些數(shù)據(jù)的副本。

這種設(shè)計(jì)有幾個(gè)重要的原因:

  1. 提高訪問速度: 直接引用主存數(shù)據(jù)可能會增加訪問延遲,因?yàn)橹鞔娴脑L問速度相比緩存較慢。通過將數(shù)據(jù)副本加載到緩存中,CPU可以更快地訪問這些數(shù)據(jù),提高程序的執(zhí)行效率。

  2. 并發(fā)性和一致性: 如果多個(gè)處理器同時(shí)訪問同一塊內(nèi)存,直接引用主存數(shù)據(jù)可能導(dǎo)致一致性問題。通過數(shù)據(jù)副本的方式,每個(gè)處理器可以在緩存中保持獨(dú)立的數(shù)據(jù)副本,避免了并發(fā)訪問導(dǎo)致的一致性問題。

  3. 減少總線流量: 如果所有核心直接引用主存數(shù)據(jù),會增加總線流量并降低整體系統(tǒng)性能。通過在緩存中存儲數(shù)據(jù)副本,可以減少主存和CPU之間的通信頻率,降低總線壓力。

所以,總體來說,cache line中的數(shù)據(jù)是主存數(shù)據(jù)的副本,通過副本的方式存儲在緩存中,以提高訪問速度、保證并發(fā)一致性和減少總線流量。當(dāng)緩存中的數(shù)據(jù)發(fā)生更新時(shí),會根據(jù)一定的緩存一致性協(xié)議(如MESI協(xié)議)來保證數(shù)據(jù)的一致性。

?????????????????????

我 - 2024-06-01 21:38:30

    std::intrinsics::black_box(ri);

解釋代碼

?????????????????????

HuluAI - 2024-06-01 21:38:30

這行代碼看起來是 Rust 語言中使用的一個(gè)函數(shù)調(diào)用 std::intrinsics::black_box(ri);。

  • std::intrinsics::black_box 是 Rust 標(biāo)準(zhǔn)庫中的一個(gè)函數(shù),通常用于防止編譯器在優(yōu)化代碼時(shí)過度優(yōu)化掉某些操作,特別是對于編譯器認(rèn)為無用的變量或函數(shù)。
  • 在這里,black_box 函數(shù)會接收一個(gè)參數(shù) ri,這個(gè)參數(shù)的值會被放入一個(gè)黑匣子,告訴編譯器不要對這個(gè)值進(jìn)行不必要的優(yōu)化。

這樣的操作通常用在對性能有嚴(yán)格要求的代碼中,避免編譯器過度優(yōu)化掉一些看似無用的計(jì)算,確保代碼的行為和性能符合預(yù)期。

綜合而言,std::intrinsics::black_box(ri); 這段代碼的作用是告訴 Rust 編譯器不要對變量 ri 進(jìn)行過度優(yōu)化,確保在性能評估或其他需要準(zhǔn)確測量程序行為的場景中,編譯器不會刪除相關(guān)的代碼或計(jì)算。

?????????????????????

我 - 2024-06-01 21:41:09

你提到了過度優(yōu)化,我想知道就如下代碼中:

pub fn row_major_traversal(arr: &mut Vec<Vec<usize>>) {
    let n = arr.len();
    for i in 0..n {
        assert!(arr[i].len() == n);
        let ri: usize = rand::random();
        std::intrinsics::black_box(ri);
        for j in 0..n {
            arr[i][j] += j;
        }
    }
}

如果不放到黑匣子,它是怎么會被優(yōu)化的?如果放到了,又是怎么防止優(yōu)化的?詳細(xì)解釋

?????????????????????

HuluAI - 2024-06-01 21:41:09

在上述代碼中,如果 ri 變量沒有被放入 std::intrinsics::black_box 中,編譯器可能會對其進(jìn)行一定的優(yōu)化。

如果不放到黑匣子里,可能會發(fā)生的優(yōu)化:

  1. 無用變量消除(Dead Code Elimination):編譯器可能會發(fā)現(xiàn) ri 的值在后續(xù)代碼中沒有被使用,因此會將這部分代碼(生成 ri 和對 ri 的操作)優(yōu)化掉。

  2. 循環(huán)優(yōu)化/向量化(Loop Optimization/Vectorization):編譯器可能會嘗試對循環(huán)進(jìn)行優(yōu)化,包括循環(huán)展開、循環(huán)變量拷貝優(yōu)化等,以提高代碼執(zhí)行效率。

  3. 內(nèi)聯(lián)展開(Inlining):編譯器可能會將 rand::random() 內(nèi)聯(lián)到代碼中,或者通過其他方式優(yōu)化 rand::random() 的調(diào)用。

使用 std::intrinsics::black_box(ri); 的作用:

  1. 防止無用變量消除:將 ri 放入 black_box 中可以告訴編譯器確實(shí)需要保留這個(gè)變量,避免被優(yōu)化掉。

  2. 效果檢查:通過 black_box,在性能測試中可以確保 ri 是真實(shí)地被使用,而不會被編譯器優(yōu)化掉。

  3. 準(zhǔn)確性保證:有時(shí)編譯器會根據(jù)代碼的邏輯,認(rèn)為某些變量或操作是無效的,但在某些情況下,這些操作可能對整體程序邏輯或性能測試起到重要作用。

通過使用 black_box,程序員可以確保編譯器不會影響代碼的預(yù)期行為,尤其在進(jìn)行性能優(yōu)化或性能測試時(shí)。這種方法常用于避免編譯器對關(guān)鍵性能測試代碼進(jìn)行過度優(yōu)化,造成不準(zhǔn)確的測試結(jié)果。

?????????????????????

我 - 2024-06-01 21:43:07

pub fn iter_with_step(arr: &mut Vec<usize>, step: usize) {
    let n = arr.len();
    let mut i = 0;
    for _ in 0..1000000 {
        unsafe { arr.get_unchecked_mut(i).add_assign(1); }
        i = (i + step) % n;
    }
}

?????????????????????

HuluAI - 2024-06-01 21:43:07

這段代碼展示了一個(gè)函數(shù) iter_with_step,它接受一個(gè)可變引用的 Vec 和一個(gè)步長 step。函數(shù)中存在一些值得注意的地方:

  • unsafe { arr.get_unchecked_mut(i).add_assign(1); } 這里使用了 unsafe 塊,意味著其中的操作是不受 Rust 內(nèi)存安全保證的,需要開發(fā)人員自行保證其正確性。
  • arr.get_unchecked_mut(i) 用于獲取索引 i 處的可變引用,這個(gè)方法不會進(jìn)行邊界檢查,因此使用時(shí)需要確保索引在合法范圍內(nèi)。
  • add_assign(1) 可能是一個(gè)自定義的函數(shù)調(diào)用,對 arr.get_unchecked_mut(i) 返回的引用進(jìn)行了增加操作。
  • i = (i + step) % n; 這行代碼是用于更新循環(huán)索引 i,按照 step 的步長進(jìn)行更新,并通過取余運(yùn)算確保 i 處于有效范圍內(nèi),避免越界。

代碼中的重要點(diǎn)和注意事項(xiàng):

  1. unsafe塊的使用unsafe 塊應(yīng)當(dāng)謹(jǐn)慎使用,開發(fā)人員需要確保其中的操作不會違反 Rust 的安全保證,特別是在涉及到指針操作、內(nèi)存訪問時(shí)。

  2. 邊界檢查:在使用 arr.get_unchecked_mut(i) 時(shí),需要注意確保索引 i 在合法范圍內(nèi),否則可能導(dǎo)致未定義行為。

  3. 性能考量:在循環(huán)中對數(shù)組元素進(jìn)行增加操作時(shí),考慮了性能因素,可能是為了避免 Rust 中常見的借用檢查限制,以及部分場景需要更低層次的內(nèi)存訪問來提高性能。

  4. 循環(huán)處理:通過按照一定的步長更新索引 i,可以實(shí)現(xiàn)每次循環(huán)訪問數(shù)組中不同位置的元素,這種技巧常用于循環(huán)處理數(shù)組或其他數(shù)據(jù)結(jié)構(gòu)。

總的來說,這段代碼展示了 Rust 中的一些底層操作和性能考量,同時(shí)也提醒了開發(fā)者在使用 unsafe 以及對指針進(jìn)行操作時(shí)需要額外小心以避免潛在的問題。

?????????????????????

我 - 2024-06-01 21:45:49

cache associativity
翻譯

?????????????????????

HuluAI - 2024-06-01 21:45:49

"Cache associativity" 的中文翻譯是 "緩存關(guān)聯(lián)性"。

在計(jì)算機(jī)體系結(jié)構(gòu)中,緩存關(guān)聯(lián)性指的是緩存中特定數(shù)據(jù)存儲位置與主存中相應(yīng)數(shù)據(jù)的存儲位置之間的映射關(guān)系。緩存關(guān)聯(lián)性通??梢苑譃槿N類型:

  1. 直接映射(Direct-Mapped Cache):每個(gè)主存塊只能映射到唯一的緩存行中。這種結(jié)構(gòu)簡單高效,但有可能會發(fā)生較多的沖突失效(Cache Conflict Miss)。

  2. 全相聯(lián)映射(Fully Associative Cache):主存塊可以映射到任意空閑的緩存行中,沒有固定映射關(guān)系。這種方式可以減少沖突失效,但需要更多的硬件實(shí)現(xiàn)。

  3. 組相聯(lián)映射(Set Associative Cache):緩存被劃分為多個(gè)組,每個(gè)組包含多個(gè)緩存行。主存塊可以映射到組內(nèi)的任意一個(gè)緩存行中。組相聯(lián)映射結(jié)合了直接映射和全相聯(lián)映射的優(yōu)點(diǎn),既能減少沖突失效,又不需要大量的硬件支持。

緩存關(guān)聯(lián)性的選擇會影響緩存的性能和成本。不同的關(guān)聯(lián)性類型在不同的應(yīng)用場景中有各自的優(yōu)缺點(diǎn),需要根據(jù)具體情況進(jìn)行選擇。

?????????????????????

我 - 2024-06-01 21:46:33

cache 的大小是要遠(yuǎn)小于主存的。這就意味著我們需要通過某種方式將主存的不同位置映射到緩存中。

具體是如何進(jìn)行映射的呢?簡單明了介紹

?????????????????????

HuluAI - 2024-06-01 21:46:33

在計(jì)算機(jī)系統(tǒng)中,主存(內(nèi)存)的大小通常遠(yuǎn)大于緩存的大小。為了有效管理緩存和確定主存中數(shù)據(jù)如何映射到緩存中,通常會使用不同的映射方式,其中主要的映射方法包括:

  1. 直接映射(Direct Mapping)

    • 在直接映射中,主存中的每個(gè)塊只能映射到緩存中的一個(gè)固定位置(緩存行)。
    • 映射規(guī)則通常使用一個(gè)取模操作來確定主存塊應(yīng)該映射到的緩存位置。例如,主存塊的地址經(jīng)過取模運(yùn)算后得到在緩存中的位置。
    • 這種映射方法簡單高效,但容易發(fā)生沖突失效(Cache Conflict Miss),即不同的主存塊映射到同一個(gè)緩存行中。
  2. 全相聯(lián)映射(Fully Associative Mapping)

    • 在全相聯(lián)映射中,主存中的任何塊都可以映射到緩存中的任意位置,沒有固定的映射規(guī)則。
    • 這種映射方式消除了沖突失效,但相應(yīng)地需要更多的硬件開銷來實(shí)現(xiàn)對所有緩存行的搜索,并決定要替換的緩存行。
  3. 組相聯(lián)映射(Set Associative Mapping)

    • 組相聯(lián)映射結(jié)合了直接映射和全相聯(lián)映射的優(yōu)點(diǎn),將緩存劃分為若干組,每個(gè)組包含多個(gè)緩存行。
    • 每個(gè)主存塊可以映射到一個(gè)組內(nèi)的任意緩存行中,而不是只能映射到一個(gè)固定位置。
    • 這種映射方式既減少了沖突失效,又相對節(jié)約硬件資源。

通過這些映射方式,計(jì)算機(jī)系統(tǒng)可以根據(jù)具體情況管理緩存中的數(shù)據(jù),避免因數(shù)據(jù)存放的方式導(dǎo)致的性能下降。不同的映射方式在性能和硬件復(fù)雜度之間存在權(quán)衡,需要根據(jù)系統(tǒng)需求進(jìn)行選擇。

?????????????????????

我 - 2024-06-01 21:58:07

能給我展示一下主存/緩存結(jié)構(gòu)圖例嗎?文字講解有些抽象

?????????????????????

HuluAI - 2024-06-01 21:58:07

當(dāng)涉及到主存和緩存的結(jié)構(gòu)時(shí),可以通過以下示意圖來展示它們之間的關(guān)系:

主存/緩存結(jié)構(gòu)示意圖:

         +------------------------------------------+
         |                  Main Memory               |
         +------------------------------------------+
         |                                          |
         |              +---------------------+      |
         |              |      Cache          |      |
         |              +---------------------+      |
         |              |                     |      |
         |              |        Tag          |      |
         |              +---------------------+      |
         |              |                     |      |
         |              |       Index         |      |
         |              +---------------------+      |
         |              |                     |      |
         |              |      Data           |      |
         |              +---------------------+      |
         |                                          |
         +------------------------------------------+

在這個(gè)簡單的示意圖中,主存和緩存之間的結(jié)構(gòu)被展示出來,包括主存存儲了所有數(shù)據(jù),而緩存則充當(dāng)主存的快速訪問存儲。

  • Main Memory(主存): 主存是計(jì)算機(jī)系統(tǒng)中存儲數(shù)據(jù)和程序的主要地方,但其訪問速度相對較慢。

  • Cache(緩存): 緩存是位于CPU和主存之間的高速存儲器,用于存儲主存中最近或頻繁訪問的數(shù)據(jù)副本,以加快CPU對數(shù)據(jù)的訪問速度。

緩存通常由以下幾部分組成:

  • Tag: 標(biāo)記區(qū)域存儲了主存中某塊數(shù)據(jù)的地址標(biāo)簽,用于比較判斷緩存中的數(shù)據(jù)是否是所需的數(shù)據(jù)塊。

  • Index: 索引用于確定數(shù)據(jù)應(yīng)該存儲在緩存中的哪個(gè)位置,通常與主存的地址相關(guān)聯(lián)。

  • Data: 數(shù)據(jù)區(qū)域存儲實(shí)際的數(shù)據(jù)副本,用于快速訪問 CPU 需要的數(shù)據(jù)。

這樣的結(jié)構(gòu)幫助計(jì)算機(jī)系統(tǒng)更高效地管理數(shù)據(jù),減少CPU等待主存數(shù)據(jù)加載的時(shí)間,并提高整體性能。通過緩存的存在,計(jì)算機(jī)能更快地訪問最常用的數(shù)據(jù),避免頻繁訪問主存造成的性能損失。

?????????????????????

我 - 2024-06-01 21:58:43

怎么映射的呢

?????????????????????

HuluAI - 2024-06-01 21:58:43

在緩存與主存之間的數(shù)據(jù)映射過程中,使用不同的映射方法決定著數(shù)據(jù)在緩存中的位置。

不同的映射方式和映射過程:

  1. 直接映射(Direct Mapping)

    • 每個(gè)主存塊直接映射到緩存中的一個(gè)固定位置(緩存行)。
    • 映射規(guī)則通常使用一個(gè)取模操作來確定主存塊應(yīng)該映射到的緩存位置。
    • 例如,如果緩存有 8 個(gè)緩存行,主存中的某塊數(shù)據(jù)的地址經(jīng)過取模 8 的操作后確定映射到緩存中的哪個(gè)位置。
  2. 全相聯(lián)映射(Fully Associative Mapping)

    • 主存中的任何塊都可以映射到緩存中的任意位置,沒有固定的映射規(guī)則。
    • 在這種情況下,緩存中的每個(gè)緩存行都可能存儲來自主存的任意數(shù)據(jù)塊,因此沒有規(guī)定的映射方式。
  3. 組相聯(lián)映射(Set Associative Mapping)

    • 緩存被劃分為多個(gè)組,每個(gè)組包含多個(gè)緩存行。
    • 每個(gè)主存塊可以映射到一個(gè)組內(nèi)的任意緩存行中,而不是只能映射到一個(gè)固定的位置。
    • 映射到哪個(gè)組由數(shù)據(jù)的地址中某些位決定,然后在組內(nèi)再根據(jù)某些位來確定具體的緩存行。

每種映射方式都有其優(yōu)點(diǎn)和缺點(diǎn),根據(jù)不同的應(yīng)用場景和硬件設(shè)計(jì)需求選擇合適的映射方式。映射的過程決定了在緩存中存儲和訪問數(shù)據(jù)的方式,直接影響到系統(tǒng)的性能表現(xiàn)和硬件消耗。不同的映射方式涉及到數(shù)據(jù)塊在緩存中的具體存儲位置選擇,以及在緩存訪問時(shí)如何定位和替換數(shù)據(jù),這些都是設(shè)計(jì)緩存系統(tǒng)時(shí)需要考慮的關(guān)鍵因素。

?????????????????????

我 - 2024-06-01 21:59:36

直接映射則規(guī)定主存中的某一行只能映射到緩存中的特定行。 還是抽象,請以圖例說明

?????????????????????

HuluAI - 2024-06-01 21:59:36

以下是一個(gè)簡單的示意圖,說明了直接映射的緩存結(jié)構(gòu)示例:

直接映射緩存結(jié)構(gòu)示意圖:

+-----------------------------------------------+
|                  Main Memory                 |
+-----------------------------------------------+
|                                               |
|         +---------------------+         +-----+
|         |        Cache        |         | Tag |
|         +---------------------+         +-----+
|         |                     |         |Index|
|         |        Cache Line 0  |         +-----+
|         |                     |         | Data|
|         +---------------------+         +-----+
|         +---------------------+         +-----+
|         |        Cache Line 1  |         | Tag |
|         |                     |         +-----+
|         +---------------------+         |Index|
|         |        Cache Line 2  |         +-----+
|         |                     |         | Data|
|         +---------------------+         +-----+
|                      ...                          |
|                                               |
+-----------------------------------------------+

在這個(gè)示意圖中,可以看到以下幾個(gè)關(guān)鍵部分:

  • Main Memory(主存): 主存儲器中存儲著所有的數(shù)據(jù)。

  • Cache(緩存): 緩存被分為多個(gè)緩存行(Cache Line),每個(gè)緩存行可以存儲一個(gè)數(shù)據(jù)塊,通常包含 Tag、Index 和 Data 三個(gè)部分。

  • Tag: 用于存儲主存塊的地址標(biāo)簽,用于確定緩存中的數(shù)據(jù)是否是所需的數(shù)據(jù)塊。

  • Index: 用于確定數(shù)據(jù)在緩存中的位置,根據(jù)主存地址的特定位來索引緩存行。

  • Data: 存儲真實(shí)的數(shù)據(jù)內(nèi)容。

在直接映射中,每個(gè)主存塊(例如主存中的一行數(shù)據(jù))只能映射到緩存中的特定緩存行。這意味著某個(gè)主存塊的映射位置在緩存中是固定的,通常通過地址的某些位來確定。直接映射結(jié)構(gòu)相對簡單,但可能因?yàn)闆_突導(dǎo)致某些主存塊需要頻繁替換緩存中的數(shù)據(jù)。這種方式雖然簡單,但在處理某些訪問模式時(shí)容易發(fā)生沖突失效(Cache Conflict Miss)。

?????????????????????

我 - 2024-06-01 22:01:24

什么時(shí)候會發(fā)生cache conflict miss?

?????????????????????

HuluAI - 2024-06-01 22:01:24

緩存沖突失效(Cache Conflict Miss)是指在使用直接映射或其他有限度映射方式時(shí)發(fā)生的一種緩存失效情況。它通常在以下情況下發(fā)生:

  1. 多個(gè)主存塊映射到同一個(gè)緩存行: 在直接映射或組相聯(lián)映射中,不同的主存塊可能被映射到同一個(gè)緩存行上。如果兩個(gè)或多個(gè)主存塊想要映射到相同的緩存行,就會發(fā)生沖突。

  2. 并發(fā)訪問相同緩存行: 當(dāng)多個(gè)內(nèi)存塊同時(shí)訪問同一緩存行時(shí),由于緩存行只能存儲一個(gè)數(shù)據(jù)塊,就會引起沖突失效。

  3. 訪問模式產(chǎn)生沖突: 如果訪問模式導(dǎo)致不同的主存塊競相存儲于同一緩存行,就會增加沖突失效的概率。

  4. 冷啟動: 在系統(tǒng)剛啟動或數(shù)據(jù)訪問模式變化時(shí),由于緩存中可能沒有所需的數(shù)據(jù),也會導(dǎo)致緩存沖突失效。

當(dāng)發(fā)生緩存沖突失效時(shí),CPU需要等待主存數(shù)據(jù)加載到緩存中,導(dǎo)致訪問時(shí)間延遲,從而影響系統(tǒng)性能。

為了減少緩存沖突失效的發(fā)生,可以采取以下方法:

  • 優(yōu)化緩存映射方式,如使用組相聯(lián)映射可以減少直接映射的沖突。
  • 調(diào)整數(shù)據(jù)結(jié)構(gòu)和訪問模式,避免多個(gè)主存塊映射到同一緩存行。
  • 實(shí)現(xiàn)替換算法,盡可能減少數(shù)據(jù)被替換的頻率。

通過合理設(shè)計(jì)緩存系統(tǒng)結(jié)構(gòu)和優(yōu)化數(shù)據(jù)訪問方式,可以減少沖突失效,提高系統(tǒng)整體性能。

?????????????????????

我 - 2024-06-01 22:05:16

每個(gè)主存塊(例如主存中的一行數(shù)據(jù))只能映射到緩存中的特定緩存行,難道主存的一行數(shù)據(jù)大小與緩存行的大小相等?這里說的映射是一一對應(yīng)的意思嗎

?????????????????????

HuluAI - 2024-06-01 22:05:16

在計(jì)算機(jī)系統(tǒng)中,主存與緩存之間的映射并不意味著主存的一行數(shù)據(jù)大小與緩存行的大小相等,也不是一一對應(yīng)的關(guān)系。我將進(jìn)一步解釋這些概念:

主存塊與緩存行的關(guān)系:

  1. 主存塊(Main Memory Block)與緩存行(Cache Line)的大?。?/strong>

    • 主存塊是內(nèi)存中連續(xù)的一塊數(shù)據(jù),可以是任意大小,例如 64 字節(jié)。
    • 緩存行是緩存中的最小數(shù)據(jù)單元,通常與主存塊的大小沒有直接關(guān)系,其大小通常是緩存的設(shè)計(jì)參數(shù)之一,比如 64 字節(jié)。
  2. 緩存的映射方式不是一一對應(yīng)的:

    • 在直接映射緩存結(jié)構(gòu)中,雖然每個(gè)主存塊只能映射到緩存中的特定緩存行,但并不要求主存塊和緩存行的大小完全相等。主存塊可以被分割成適應(yīng)緩存行大小的塊進(jìn)行映射。
  3. 映射關(guān)系:

    • 映射的目的是為了將主存中的數(shù)據(jù)復(fù)制到緩存中,以提高CPU對數(shù)據(jù)的訪問速度。具體的映射方式(直接映射、組相聯(lián)映射、全相聯(lián)映射)決定了主存塊如何映射到緩存中的位置。
  4. 一一對應(yīng)的情況:

    • 在直接映射中,每個(gè)主存塊確實(shí)映射到緩存中的特定緩存行,但并不要求它們的大小相同。主要依賴于地址映射和地址標(biāo)簽匹配。

因此,主存塊與緩存行的大小可以不同,映射關(guān)系通常是根據(jù)緩存的設(shè)計(jì)和映射方式來確定的,并不要求一一對應(yīng)或大小完全相等。映射的關(guān)鍵點(diǎn)在于將主存內(nèi)容快速復(fù)制到緩存中,使得CPU能夠更快地訪問所需的數(shù)據(jù)。

?????????????????????

我 - 2024-06-01 22:07:09

大小不同,是如何映射的呢? 比如主存塊大小128字節(jié),其對應(yīng)的特定緩存行是64字節(jié),請問這128字節(jié)如何映射復(fù)制的?

?????????????????????

HuluAI - 2024-06-01 22:07:09

當(dāng)主存塊的大小與緩存行的大小不匹配時(shí),通常會按照以下方式進(jìn)行映射和復(fù)制:

  1. 主存塊大?。ɡ?128 字節(jié))與緩存行大小(例如 64 字節(jié))不匹配時(shí):

  2. 分塊映射:

    • 主存塊會被分割成多個(gè)適應(yīng)緩存行大小的塊,每個(gè)小塊稱為一個(gè) Cache Block。在這種情況下,一個(gè)主存塊可能需要被分成兩個(gè) Cache Blocks,每個(gè)大小為 64 字節(jié)。
  3. 部分映射和填充:

    • 通過地址的位來確定主存塊的哪個(gè)部分映射到緩存行。例如,主存塊的前 64 字節(jié)可能映射到第一個(gè)緩存行,而后 64 字節(jié)映射到緊鄰的下一個(gè)緩存行。
  4. 讀取和存儲:

    • 當(dāng)CPU請求讀取主存塊時(shí),如果該主存塊之前沒有緩存在緩存中,緩存控制器會根據(jù)映射規(guī)則將主存塊在主存中的對應(yīng)部分復(fù)制到緩存中的對應(yīng)緩存行中。
  5. 訪問新數(shù)據(jù):

    • 如果在緩存中找不到所需數(shù)據(jù)的全部內(nèi)容,CPU可能需要多次訪問主存來獲取完整的數(shù)據(jù)塊。這種情況下,需要多次從主存中加載數(shù)據(jù)到緩存中,考慮到主存塊的不對齊。

在這種情況下,進(jìn)行部分映射和填充是一種常見的做法,以便使主存塊的數(shù)據(jù)能夠在緩存中適當(dāng)?shù)貙R和存儲。這樣可以最大限度地利用緩存的性能優(yōu)勢,同時(shí)考慮到主存塊和緩存行大小不匹配的情況。

?????????????????????

我 - 2024-06-01 22:10:16

我們可以看到,L1 cache 大小為 4 x 32 KB (128KB) ,采取 8 路組相聯(lián),緩存行大小為 64 bytes 。也就是說,該緩存共有 4x32x1024 byte/64 byte = 2048 行,共分為 2048/8 = 256 組。也就是說,當(dāng)?shù)鷶?shù)組的步長為 時(shí),數(shù)據(jù)更可能會被分到同一個(gè)組內(nèi),導(dǎo)致 cache miss 更加頻繁,從而導(dǎo)致效率下降。解釋這句話?

?????????????????????

HuluAI - 2024-06-01 22:10:16

在這種情況下,假設(shè)有一個(gè)大小為128KB的L1緩存,采用8路組相聯(lián)(8-way set-associative)映射方式,每個(gè)緩存行大小為64字節(jié)。根據(jù)提供的信息,可以解釋為什么當(dāng)?shù)鷶?shù)組的步長為stride時(shí),數(shù)據(jù)更有可能被映射到同一個(gè)組內(nèi),導(dǎo)致緩存失效更頻繁,從而影響程序的效率:

  1. 組相聯(lián)映射情況:

    • 緩存分為256組,每組包含8路緩存行,共有2048個(gè)緩存行。
    • 每個(gè)組內(nèi)的緩存行被8路關(guān)聯(lián),意味著當(dāng)一個(gè)數(shù)據(jù)塊被映射到某個(gè)組后,它可以存儲在該組內(nèi)的任意一個(gè)緩存行中的其中一路。
  2. 步長為stride時(shí)的情況:

    • 當(dāng)?shù)鷶?shù)組的步長為stride時(shí),數(shù)組元素被訪問的間隔為stride。
    • 如果多個(gè)數(shù)據(jù)元素落在同一個(gè)組內(nèi),因?yàn)榻M內(nèi)緩存行有限,會發(fā)生緩存失效。
    • 如果這些數(shù)據(jù)元素互相競爭同一個(gè)組內(nèi)的緩存行,就會導(dǎo)致頻繁的緩存失效。
  3. 緩存失效頻繁的影響:

    • 頻繁的緩存失效會導(dǎo)致CPU需要從主存中讀取數(shù)據(jù),增加延遲,降低程序性能。
    • 緩存失效頻繁也會降低緩存的效率,因?yàn)閿?shù)據(jù)不再能夠被重復(fù)利用,需要頻繁地從主存中加載。
  4. 解決方法:

    • 可以考慮優(yōu)化數(shù)據(jù)訪問模式來避免不同數(shù)據(jù)競爭同一組內(nèi)的緩存行,減少緩存失效。
    • 或者通過優(yōu)化緩存的映射方式或增加更多的緩存行,以減少競爭、提高緩存的利用率。

因此,當(dāng)?shù)鷶?shù)組的步長為stride時(shí),如果數(shù)據(jù)更有可能被映射到同一個(gè)組內(nèi),就會增加緩存失效的頻率,降低程序的效率。這種情況需要注意,并可以通過調(diào)整程序訪存模式或優(yōu)化緩存策略來改善。

?????????????????????

我 - 2024-06-01 22:12:53

解釋每一行代碼,直接在源代碼的后面加上注釋輔助閱讀

?????????????????????

HuluAI - 2024-06-01 22:12:53

下面是對你提供的匯編代碼中每一行代碼的注釋說明:

dependent(...):
    mov     rax, rdx            ; 將 rdx 寄存器的值移動到 rax 寄存器
    mov     rdx, QWORD PTR [rsi] ; 將 rsi 地址處的 64 位值移動到 rdx 寄存器
    mov     rcx, QWORD PTR [rdi] ; 將 rdi 地址處的 64 位值移動到 rcx 寄存器
    mov     rdi, QWORD PTR [rax] ; 將 rax 地址處的 64 位值移動到 rdi 寄存器
    xor     eax, eax            ; 將 eax 寄存器置零
.L2:
    mov     esi, DWORD PTR [rdx+rax]        ; 將 rdx+rax 地址處的 32 位值移動到 esi 寄存器
    add     DWORD PTR [rcx+rax], esi       ; 將 esi 寄存器的值加到 rcx+rax 地址處的值中
    mov     esi, DWORD PTR [rdi+rax]        ; 將 rdi+rax 地址處的 32 位值移動到 esi 寄存器
    add     DWORD PTR [rdx+4+rax], esi      ; 將 esi 寄存器的值加到 rdx+4+rax 地址處的值中
    add     rax, 4            ; rax 寄存器加 4
    cmp     rax, 39996        ; 比較 rax 和 39996
    jne     .L2               ; 如果不相等,則跳到標(biāo)簽 .L2
    mov     eax, DWORD PTR [rdx+39996]       ; 將 rdx+39996 地址處的 32 位值移動到 eax 寄存器
    add     DWORD PTR [rcx+39996], eax      ; 將 eax 寄存器的值加到 rcx+39996 地址處的值中
    ret                       ; 函數(shù)返回

independent(...):
    mov     rax, QWORD PTR [rdi]       ; 將 rdi 地址處的 64 位值移動到 rax 寄存器
    mov     rcx, rdx            ; 將 rdx 寄存器的值移動到 rcx 寄存器
    mov     rdx, QWORD PTR [rsi]       ; 將 rsi 地址處的 64 位值移動到 rdx 寄存器
    lea     rdi, [rax+4]        ; 將 rax+4 的地址加載到 rdi 寄存器(不執(zhí)行訪存)
    mov     esi, DWORD PTR [rdx]        ; 將 rdx 地址處的 32 位值移動到 esi 寄存器
    add     DWORD PTR [rax], esi       ; 將 esi 寄存器的值加到 rax 地址處的值中
    lea     r8, [rdx+4]         ; 將 rdx+4 的地址加載到 r8 寄存器(不執(zhí)行訪存)
    mov     rsi, QWORD PTR [rcx]       ; 將 rcx 地址處的 64 位值移動到 rsi 寄存器
    lea     rcx, [rdx+20]       ; 將 rdx+20 的地址加載到 rcx 寄存器(不執(zhí)行訪存)
    cmp     rdi, rcx            ; 比較 rdi 和 rcx
    lea     rdi, [rax+20]       ; 將 rax+20 的地址加載到 rdi 寄存器(不執(zhí)行訪存)
    setnb   cl                 ; 條件設(shè)置:cl = !CF,若進(jìn)位則 cl=0
    cmp     r8, rdi            ; 比較 r8 和 rdi
    setnb   dil                ; 條件設(shè)置:dil = !CF,若進(jìn)位則 dil=0
    or      ecx, edi          ; 對 ecx 和 edi 按位或
    mov     rdi, rdx           ; 將 rdx 寄存器的值移動到 rdi 寄存器
    sub     rdi, rsi           ; rdi 減去 rsi
    cmp     rdi, 8             ; 比較 rdi 和 8
    seta    dil                ; 條件設(shè)置:dil = CF=0 and ZF=0,無符號大于時(shí) dil=1
    test    cl, dil           ; 按位與 cl 和 dil
    je      .L9                ; 若二者都為零,則跳轉(zhuǎn)到 .L9
    mov     rcx, rax           ; 將 rax 寄存器的值移動到 rcx 寄存器
    sub     rcx, rsi           ; rcx 減去 rsi
    cmp     rcx, 8             ; 比較 rcx 和 8
    jbe     .L9                ; 如果小于等于則跳轉(zhuǎn)到 .L9
    mov     ecx, 4             ; 將 4 移動到 ecx 寄存器
.L7:
    movdqu  xmm0, XMMWORD PTR [rsi-4+rcx]  ; 將 rsi-4+rcx 地址處的 128 位值加載到 xmm0
    movdqu  xmm2, XMMWORD PTR [rdx+rcx]    ; 將 rdx+rcx 地址處的 128 位值加載到 xmm2
    paddd   xmm0, xmm2          ; 128 位整數(shù)加法操作
    movups  XMMWORD PTR [rdx+rcx], xmm0    ; 將 xmm0 的值保存到 rdx+rcx 地址處
    movdqu  xmm3, XMMWORD PTR [rax+rcx]    ; 將 rax+rcx 地址處的 128 位值加載到 xmm3
    paddd   xmm0, xmm3          ; xmm0 和 xmm3 相加
    movups  XMMWORD PTR [rax+rcx], xmm0    ; 將 xmm0 的值保存到 rax+rcx 地址處
    add     rcx, 16             ; rcx 加 16
    cmp     rcx, 39988          ; 比較 rcx 和 39988
    jne     .L7                 ; 如果不相等,則跳到標(biāo)簽 .L7
    movq    xmm0, QWORD PTR [rsi+39984]    ; 將 rsi+39984 地址處的 64 位值加載到 xmm0
    movq    xmm1, QWORD PTR [rdx+39988]    ; 將 rdx+39988 地址處的 64 位值加載到 xmm1
    paddd   xmm0, xmm1          ; 64 位整數(shù)加法操作
    movq    QWORD PTR [rdx+39988], xmm0    ; 將 xmm0 的值保存到 rdx+39988 地址處
    movq    xmm1, QWORD PTR [rax+39988]    ; 將 rax+39988 地址處的 64 位值加載到 xmm1
    paddd   xmm1, xmm0          ; xmm1 和 xmm0 相加
    movq    QWORD PTR [rax+39988], xmm1    ; 將 xmm1 的值保存到 rax+39988 地址處
    mov     ecx, DWORD PTR [rdx+39996]    ; 將 rdx+39996 地址處的 32 位值加載到 ecx
    add     ecx, DWORD PTR [rsi+39992]   ; 將 rsi+39992 地址處的 32 位值加到 ecx 寄存器中
    mov     DWORD PTR [rdx+39996], ecx   ; 將 ecx 的值保存到 rdx+39996 地址處
    add     DWORD PTR [rax+39996], ecx   ; 將 ecx 的值加到 rax+39996 地址處的值中
    ret                                 ; 函數(shù)返回
.L9:
    mov     ecx, 4             ; 將 4 移動到 ecx 寄存器
.L6:
    mov     edi, DWORD PTR [rdx+rcx]    ; 將 rdx+rcx 地址處的 32 位值加載到 edi
    add     edi, DWORD PTR [rsi-4+rcx]  ; 將 rsi-4+rcx 地址處的 32 位值加到 edi 寄存器中
    mov     DWORD PTR [rdx+rcx], edi    ; 將 edi 的值保存到 rdx+rcx 地址處
    add     DWORD PTR [rax+rcx], edi    ; 將 edi 的值加到 rax+rcx 地址處的值中
    add     rcx, 4           ; rcx 加 4
    cmp     rcx, 40000       ; 比較 rcx 和 40000
    jne     .L6              ; 如果不相等,則跳到標(biāo)簽 .L6

這些注釋將有助于更好地理解這段匯編代碼的工作原理。

?????????????????????

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容