1、初識計算機底層


相關書籍推薦

讀書的原則:不求甚解,觀其大略

  • 《編碼:隱匿在計算機軟硬件背后的語言》

  • 《深入理解計算機系統》

  • 語言:C JAVA K&R《C程序設計語言》《C Primer Plus》

  • 數據結構與算法: --畢生的學習 leetCode

    • 《Java數據結構與算法》《算法》
    • 《算法導論》《計算機程序設計藝術》//難
  • 操作系統:Linux內核源碼解析 Linux內核設計與實現 《30天自制操作系統》

  • 網絡:機工《TCP/IP詳解》卷一 翻譯一般

  • 編譯原理:機工 龍書 《編譯原理》 《編程語言實現模式》馬語

  • 數據庫:SQLite源碼 Derby - JDK自帶數據庫

硬件基礎知識

CPU的制作過程
Intel cpu的制作過程:https://haokan.baidu.com/v?vid=11928468945249380709&pd=bjh&fr=bjhauthor&type=video

CPU是如何制作的(文字描述)
https://www.sohu.com/a/255397866_468626

CPU的原理
計算機需要解決的最根本問題:如何代表數字

晶體管是如何工作的:https://haokan.baidu.com/v?vid=16026741635006191272&pd=bjh&fr=bjhauthor&type=video

晶體管的工作原理:https://www.bilibili.com/video/av47388949?p=2

匯編語言(機器語言)的執行過程

匯編語言的本質:就算機器語言的助記符,其實它就是機器語言

計算機通電 -> CPU讀取內存中程序(電信號輸入)
->時鍾發生器不斷震盪通斷電:推動CPU內部一步一步執行(執行多少步取決於指令需要的時鍾周期)
->計算完成
->寫回(電信號)
->寫給顯卡輸出(sout,或者圖形)

計算機的組成

image

CPU的基本組成

  • PC: Program Counter 程序計數器 (記錄當前指令地址)
  • Registers: 暫時存儲CPU計算需要用到的數據
  • ALU:Arithmetic & Logic Unit 運算單元
  • CU: Control Unit 控制單元
  • MMU:Memory Management Unit 內存管理單元
  • cache

緩存一致性協議:https://www.cnblogs.com/z00377750/p/9180644.html

緩存行:
緩存行越大,局部性空間效率越高,但讀取時間慢
緩存行越小,局部性空間效率越低,但讀取時間快
取一個折中值,目前多用:64字節

測試緩存:Test1 時間比Test2 時間執行大概慢1秒左右
對比一下

public class Test1 {

    public static volatile long[] arr = new long[2];

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            for (long i = 0; i < 10_0000_0000L; i++) {
                arr[0] = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (long i = 0; i < 10_0000_0000L; i++) {
                arr[1] = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}
public class Test2 {

    public static volatile long[] arr = new long[16];

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            for (long i = 0; i < 10_0000_0000L; i++) {
                arr[0] = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (long i = 0; i < 10_0000_0000L; i++) {
                arr[8] = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

經過測試,Test1類執行時間大概慢於Test2執行時間2秒左右
這是因為緩存行的原因:對於特別敏感的數字,會存在線程競爭的訪問,為了保證不發生偽共享,所以使用緩存行對齊的方式。

JDK7中,很多采用long padding提高效率
JDK8,加入了@Contended注解(實驗)需要加上:JVM配置項 -XX:-RestrictContended
可以試一下

public class Test1 {

    @Contended
    public static volatile long[] arr = new long[2];

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            for (long i = 0; i < 10_0000_0000L; i++) {
                arr[0] = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (long i = 0; i < 10_0000_0000L; i++) {
                arr[1] = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

亂序執行

參考:https://preshing.com/20120515/memory-reordering-caught-in-the-act/

指令重排序的樣例:

public class 指令重排序Demo {
    private static int x = 0, y = 0;
    private static int a = 0, b =0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    //由於線程one先啟動,下面這句話讓它等一等線程two. 讀着可根據自己電腦的實際性能適當調整等待時間.
                    //shortWait(100000);
                    a = 1;
                    x = b;
                }
            });

            Thread other = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            one.start();other.start();
            one.join();other.join();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);     // 肯定會有x 和 y 都為0 的時候
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

這個測試要花好久時間:

DCL單例為什么要加volatile(Double Check Lock)

如下一個類:

public class T {
    int m = 8;
}

如果要new一個對象:T t = new T();
它是會有一個中間態的,在new這個對象剛開始的時候,會在內存分配一塊空間,默認給這個int類型的m 賦值是0,
但如果是多線程情況在這個時候不斷地訪問,可能會得到m=0 這個值,是不對的,這是因為匯編碼的指令是亂序的,所以要禁止指令亂序

CPU層面怎么禁止指令重排序?

內存屏障。

就是對某部分內存做操作時,前后添加屏障,屏障前后的操作不可以亂序執行。
Intel 使用的是原語

JVM層面怎么禁止指令重排序?

上面所說的是CPU層面的避免指令重排實現,也就是具體的實現,但java程序中是通過JVM的指令去調用CPU底層的

JVM層級:8個hanppens-before原則 4個內存屏障 (LL LS SL SS)
jvm 里面對於內存屏障的實現有4 種

  • LoadLoad:對於這樣的語句Load1;LoadLoad;Load2,在Load2及后續的讀操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢;
  • StoreStore:對於這樣的語句Store1;StoreStore;Store2,在Store2及后續的寫操作執行前,保證Store1的寫入操作對其他處理器可見;
  • LoadStore:對於這樣的語句Load1;LoadStore;Store2,在Store2及后續的寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢;
  • StoreLoad:對於這樣的語句Store1;StoreLoad;Load2,在Load2及后續的讀操作要讀取的數據被訪問前,保證Store1的寫入操作對其他處理器可見;

StoreStoreBarrier
volatile 寫操作,上下不會亂排
StoreLoadBarrier

LoadLoadBarrier
volatile 讀操作,上下不會亂排
LoadStoreBarrier

合並寫技術的了解

Write Combining Buffer

一般是4個字節
由於ALU速度太快,所以在寫入L1的同時,寫入一個WC Buffer,滿了之后,再直接更新到L2

NUMA(Non Uniform Memory Access)

在ZGC - NUMA aware ,意思就是分配內存會優先分配該線程所在CPU的最近內存

先了解UMA:說白了多個cpu訪問一塊內存

再來看NUMA:每個cpu附近有個內存,訪問就近的內存比訪問別家的內存要快得多


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM