淺談循環之硬件級實現

現代編程語言中循環是十分常見的功能,幾乎任何編程語言都有相似forwhile這樣的循環語句,不過在計算機底層就沒有那麼幸福了,許多的硬件其實並無提供硬件級別的循環。不過硬件級別的限制,彷佛並無影響到咱們平常的工做,今天就主要來看看循環的本質是什麼。編程

指令集

現在使用的編程語言,以及各種不一樣的軟件,其實到最後都會轉換成二進制的形式,用以控制底層硬件的運行。這些上層軟件實際上是底層功能的抽象,無論上層業務多麼複雜,在底層幾乎都是經過有限的寄存器,指令集,還有內存來實現相關的功能。咱們所編寫的應用程序,與CPU的指令集息息相關。其實所謂的指令集,就是CPU提供的一系列用於控制硬件的指令的集合。不一樣的硬件廠家,所生產的CPU指令集肯能會有所不一樣。目前主要分紅了兩大陣營,分別是CISC-複雜指令集計算機和RISC-精簡指令集計算機。AMD以及Intel這些廠家生產的CPU(x86指令集)基本上都屬於CISC,他們所包含的指令至關多也比較複雜,不過彷佛不打算支持硬件級別的循環。而一些的手機CPU,ARM架構的開發版都屬於RISC的範疇,它們的特色是指令相對較少,也比較簡單,而且部分RISC的CPU甚至支持硬件級別的循環。bash

循環的「變態」

FRANKY

這裏的「變態」並無罵人的意思。根據生物學中的描述,變態其實指代了形態的變化。在某種意義上,循環也存在着變態。架構

在C語言中循環廣泛有3中表達方式,分別是for循環,while循環以及do-while循環編程語言

// 1. for循環
for (init-expr; test-expr; update-expr) {
    ....
}

// 2. while循環
init-expr;
while (test-expr) {
    .....
    update-expr;
}

// 3. do-while循環

init-expr;
do {
    ....
    update-expr;
} while (test-expr)
複製代碼

不過問題是計算機底層並無那麼多表達循環的方式,爲了在底層實現循環功能,必需要以另外一種方式來表達循環。實際上循環在底層都會經過指令跳轉配合狀態更改的方式來實現。至關於用goto這類語句來實現循環模式。goto語句在業界是很讓人詬病的,許多的語言都不支持goto這類語法,不過好在C語言仍是支持的。接下來來看看要如何進行這種「變態」。函數

fact_while是一個用while循環實現的階乘函數,固然咱們也能夠用for循環來實現等價的功能,這裏不一一舉例。oop

long fact_while(long n) {
  long result = 1;
  while (n > 1) {
    result *= n;
    n -= 1;
  }
  return result;
}
複製代碼

咱們的任務就是不使用whilefor這些循環語句,只用goto語句來實現上述循環。大致上有兩種翻譯方式,分別是jump to middle以及gurade-do測試

1. Jump To Middle

jump to middle直接翻譯過來就是跳轉到中間,它的原理其實就是**把條件測試寫在中間部分,在首次迭代開始以前先行跳轉並執行條件測試語句。**翻譯過來大概就是優化

long fact_jump_to_middle(long n) {
  long result = 1;
  goto test;
 loop:
  result *= n;
  n --;
 test:
  if (n > 1) goto loop;
  return result;
}
複製代碼

這種翻譯方式最爲關鍵的是goto test;語句,在進入循環區域以前便直接跳轉到條件測試語句,測試是否符合n > 1這個條件。若是符合條件則進入循環體並執行循環體中的邏輯,不然繼續往下執行程序,返回結果。這種翻譯方式還有個特色,當你嘗試把goto test;這條語句去掉以後會發生什麼事情呢?spa

long fact_jump_to_middle_without_first_jump(long n) {
  long result = 1;
 loop:
  result *= n;
  n --;
 test:
  if (n > 1) goto loop;
  return result;
}
複製代碼

從邏輯上講它其實就是一個測試條件相同的do-while循環實現,while語句與do-while語句最大的不一樣就在於,while語句是先進行條件測試,當符合條件的時候纔會進入到循環體中,而do-while則是執行了一次循環體中的語句以後才進行循環相關的條件測試。這麼看來do-while循環本質上就是少了初始條件檢測的while循環。翻譯

2. guarded-do

另外一種翻譯方式被稱爲guarded-do,它的原理是在迭代以前設置一個「門衛」條件。若是不符合條件的話,則直接跳到循環邏輯以後,不然就進入循環邏輯中,此處的循環邏輯依舊用do-while循環來實現。按照這種翻譯方式所翻譯的goto版本以下

long fact_guarded_do(long n) {
  long result = 1;
  if (n <= 1) goto done;
 loop:
  result *= n;
  n --;
  if (n > 1) goto loop;
 done:
  return result;
}
複製代碼

可見最關鍵的地方是設置的「門衛」條件,該條件應該設置成循環條件的補集。只要知足這個「門衛」條件則跳過整個循環邏輯,不然就進入循環區域中。有些書還會把上面的過程寫成

long fact_guarded_do(long n) {
  long result = 1;
  if (n <= 1) goto done;
 loop:
  result *= n;
  n --;
  if (n != 1) goto loop;
 done:
  return result;
}
複製代碼

其實兩種方式是等價的。只要符合條件n > 1便可以進入到循環區域中,在循環中每次迭代都會進行減一操做,那麼只要知足條件n != 1即可持續進行迭代。

真實場景

前面部分簡單地介紹了循環,以及如何對循環進行變形,用goto語句來取代whilefordo-while這類循環語句。然而正常狀況下咱們並不會去把一個C語言的循環版本,轉換成與之等價的C語言的goto版本,這麼作其實只是爲了方便原理的解釋。真實場景下,在語言進行編譯的時候,其實會先轉換成彙編代碼。

經過命令

gcc -Og -S while.c
複製代碼

把最開始的fact_while階乘函數編譯成彙編語言版本,生成的彙編程序會存儲在文件while.s中,丟掉一些雜七雜八的東西以後大概結果以下

movl	$1, %eax
    cmpq	$2, %rdi
    jl	LBB0_2
LBB0_1:
	imulq	%rdi, %rax
	cmpq	$2, %rdi
	leaq	-1(%rdi), %rdi
	jg	LBB0_1
LBB0_2:
    retq
複製代碼

簡單起見,我把一些方法調用相關的寄存器行爲給去掉了,只保留了循環邏輯的部分。閱讀彙編代碼的關鍵點在於瞭解不一樣寄存器的做用,其中寄存器%rax用於存放返回值,寄存器%rdi用於存放函數第一個參數的值。把上面的彙編程序轉換成更加親民的版本,並加上註釋可得

movl	$1, %eax                 ## 把數值1放進寄存器%eax中
    cmpq	$2, %rdi                 ## 把參數n的值與數值2進行比較
    jl	done                         ## 若是n < 2則跳到標籤done處
loop:                                ## 標識着即將進入循環區域
	imulq	%rdi, %rax               ## 把%rax (就是%eax中的數值0擴展到64位)的數值與%rdi(數值n)相乘,並把結果存儲到%rax中
	cmpq	$2, %rdi                 ## 把n的值與數值2進行比較,比較結果會記錄在其餘地方 (1)
	leaq	-1(%rdi), %rdi           ## 改變n的值,n = n - 1
	jg	loop                         ## 獲取(1)處的比較結果,若是在遞減以前n是大於2的則跳轉到循環區域開始的地方
done:                                ## 標識着已經離開循環區域
    retq                             ## 函數返回,返回值存放在寄存器%rax中
複製代碼

整體上看來這裏是採用了guarded-do的翻譯方式。不過它的具體邏輯看起來跟咱們前面用C語言的goto語句描述的過程稍微有些不一樣,可是隻要仔細琢磨,其實它們所作的東西是等價的,爲了少執行一些指令,編譯器會進行了一些優化,不過在本例中所採用的優化等級還算是比較低的了。

結尾

這篇文章主要簡單地總結了一下在計算機底層循環的實現方式,即使是現代最流行的x86指令集都沒有硬件級循環的支持,常見的作法是利用硬件的條件跳轉指令來實現循環的相關邏輯。爲了更直觀地看到這個過程,咱們利用C語言的goto語句模擬了底層的循環實現。最後還提供了一個優化等級較低的彙編語言版本,能進一步體現出底層硬件的工做方式。

相關文章
相關標籤/搜索