劉亮谷
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2016q3 Homework5 (jit-compiler) contributed by <`LanKuDot`>, <`ktvexe`> ## 組員 - [ ] 李昆憶 - [ ] 劉亮谷 ## 作業要求 [A09: jit-compiler](https://hackmd.io/s/SJ7AZJv1e) * 學習設計 JIT 編譯器 * 運用計算機組織的背景知識,進行深入效能評估和提出改善方案 * 延展程式語言,提供 concurrency 能力 ## 基礎知識 ### AOT (Ahead-Of-Time) compilation * 在執行程式之前,執行一連串的指令將 High-level programming language 轉換成可以在該機器上執行的機械碼,並產生可執行檔。 * 而執行檔可以不用重新編譯就在同一個機器上重複執行 * 相比 JIT compiler 可以對程式碼作諸多優化。 ### JIT (Just-In-Time) compilation * 與執行前編譯好執行檔不一樣,JIT 是~~一邊執行程式一邊編譯~~**在真正執行特定功能之際,才編譯程式碼**。可以直接將程式碼轉成機械碼或是其他形式執行。常常使用在 dynamic programming language。 * 只有在執行程式時,才會編譯。每一次執行,就需要重新編譯。 > 這邊我不是很懂,需要再閱讀些資料,研究其實作方式與相關應用[name=劉亮谷][color=blue] > [我參考這個](http://scholarexchange.furman.edu/cgi/viewcontent.cgi?article=1879&context=furmanengaged)[name=LanKuDot] >> "in time" 的意義要先搞懂,引述 dictionary.com 的解說: >>> Before a time limit expires, early enough, as in His speech begins at eight, so we've arrived in time. It is often put as in time for, as in Please come in time for dinner. >> 所以 just-in-time compiler 的意思是,在真正執行特定功能之際或期限超過前,才去編譯程式碼 [name=jserv] ### [BrainF\*ck](http://www.slideshare.net/jserv/vm-construct) * 圖靈實現語言(Turing complete):可以用來模擬單磁帶圖靈機的程式語言,而且只要懂一種圖靈實驗語言,就可以理解其他同一類型的語言。「磁帶」的每一個符號都有對應的動作,透過有限的 table 查表運作。而 brainf*ck 只有八個指令: `>`:移動 data pointer 到下一個 cell `<`:移動 data pointer 到上一個 cell `+`:加一 data pointer 指向的 byte `-`:減一 data pointer 指向的 byte `.`:輸出 data pointer 所指向的 byte `,`:輸入 1 byte 的資料到 data pointer 所指向的 cell `[`:如果 data pointer 所指向的資料為 0,就跳到 `]`之後繼續執行,否則往下執行 `]`:跳回對應的`[`繼續執行 >上課中問到BrainF*ck可否實現if-else,不知道如果回答Turing machine的定義有沒有切重要害[name=劉亮谷][color=blue] * 需要一個磁帶的空間,另一個是存資料的空間 (cell),例如給變數的空間。 >instruction pointer and data pointer[name=李昆憶][color=red] * [P11]:遇到迴圈起點時,先找尋對應的迴圈終點。可以偵測巢狀迴圈: * 因為是先遇到`[` ,所以將 `b`初始化為 1,如果遇到 `]`就減一,遇到`[`就加一。當`b`為 0 時,就代表找到成對的中括號。 * 找到之後,先將對應的`]`,改成`0`,為了就是讓 data pointer 偵測迴圈終點。recursive call,進入迴圈執行。等迴圈結束後,再將`0`改回`]`。 ```c case '[': // 每次進到 switch 是 *c++ 所以此時 c 是指向 [ 的下一個 for (b = 1, d = c; b && *c; c++) // d 紀錄迴圈起點 b += *c == '['; b -= *c == ']'; if (!b) { c[-1] = 0; // ] became \0 while (a[p]) interpert(d); c[-1] = ']'; // Resume ] break; } ``` >`==`的 precedence 比`+=`、`-=`高。[name=李昆憶][color=red] * [P16]:**copy**:`x=y`,`$y[-$x+$y]`。假設 x 存在 cell 0,y 存在 cell 1,data pointer 目前指向 x:那就會是 `>[-<+>]`: * 將 y 減 1,再將 x 加 1,直到 y 為 0。 * `$y[-$t+$y]$t[-$x+$y+$t]`,另一個方法是將 y 的值存到 t 中,然後減掉 t,同時將 x y 加回來。假設 t 在 cell 2,data pointer 目前指向 x:`>[->+<]>[-<<+>+>]` * **需要注意迴圈結束時,data pointer 指向哪個 cell** * [P17]:**if**:`if (x) { <foobar> };` ```clike= Move x to t; if ( t != 0 ) recharge x; foobar; ``` * [P18]:**clean**,`[-]`。用一個 while loop 把那個 cell 減到 0。 * [P19]:**cat**,`+[,.]`。先將目前的 cell 加一,確保他非 0,然後用一個 while loop,一直讀輸入,再輸出,直到遇到`\0`。 * [P20]:**if-endif** ``` $f + // 確保可以作一次跳出迴圈 $A [ // if ($A) 這裡有可能就是移到要判定的 data cell <foo bar> $f [-] // Clear $f -> end if ] ``` * [P21]:**if-else-endif** ``` $f + // 確保可以作一次跳出迴圈 $A [ // if ($A) <foo bar> $f [-] // clear $f ] $f [ // else,如果 if 成立 $f 會被清空,避免下個迴圈被執行 <foo2 bar2> $f [-] ] ``` * [P22]:**Multiply**,就是連續加法,用一個 cell 存要加幾次,在重複加另一個 cell * [P23]:**Divide**,連續減法,用一個 cell 存被除數,一個 cell 存商,每次 loop 減被除數除數 ```clike= // a / b = c; cell 0 store the value of a. while ( a > 0 ) { ++c; a -= b; } ``` * [P26]:bubble sort ```clike= >>>>>,.[>>>,.] // 從第六個 cell 開始,讀取輸入,每隔兩個存一個值 <<< // 當輸入結束 (讀到0時),移回最後一個資料 [<<< // 迴圈A:移到上一個資料 [>>> // 迴圈B:移到下一個資料 [-<<<-<+>[>]>>] // 迴圈C1:每次將這個資料與上一個資料減一,每減一次就在上個資料的前一個 cell 加一,然後移回上一個資料。如果上一個資料還沒歸零的話,就會重複移回這個資料,持續上述動作。所以說,較小的值會被存到上一個資料的前面的 cell。如果上一個比這個大,data pointer 最終會指向這個資料(此時這個為0)。否則,會指向這個資料的前面一個 cell(此時上一個為0) <<<[<]>> // 找到上一個資料的位置 [>>>+<<<-]< // 迴圈C2:如果上一個值比較大,就會先把剩下的值搬到"這個"去,然後指回暫時存起來的最小值 [>+>>>+<<<<-] // 迴圈C3:然後把暫存的最小值都搬到兩個資料中。搬移完成後,data pointer 會在這個暫存 cell 中。這樣比較大的值,就會跑到後面去了。 <<] // 迴圈B:往上一個值移動,這樣比較小的值就會一直往前,直到遇到0 >>>[.[-]] // 往後移動到最近的資料,印出這個值後,把他 clear 掉(所以最小值會先被印出來) >>>[>>>]<<<] // 迴圈A:往下一個資料移動,直到遇到0後,將指標轉回最後一個,直到所有資料都被印出來(all 0) ``` * 優化:對於重複執行的動作,可以進行優化 * 連續加法,或連續減法,連續移動:都可以被一條指令給取代,而非一個一個重複執行 ## 作業 ### 環境建置 原本想直接跑 `make bench-jit-x64` 但出現缺少檔案的訊息 (我環境是 64 bit OS) ```shell $ make bench-jit-x64 lua dynasm/dynasm.lua -o jit-x64.h jit-x64.dasc make: lua: Command not found ``` * 安裝 `lua`: ```shell $ sudo apt-get install lua<ver>` (我是用5.2版) ``` 再跑 `make` 會出現 `bit` module not found 的訊息 * 安裝 lua development package: ```shell $ sudo apt-get install liblua<ver>-dev` ``` * 到[官網](http://bitop.luajit.org/index.html)下載 `bitop` module 的 source code * 解壓縮後執行 `make`,可能會出現 include 的 header file 不在的訊息。檢查它所需的 header file 是否在 `/usr/include/lua<ver>`,通常安裝 library 的 header file 會在 `/usr/include` 或 `/usr/local/include`。找到後,修改 Makefile 中的 include 路徑。編完會產生一個 .so 檔。 ```shell $ make gcc -fPIC -O2 -fomit-frame-pointer -Wall -I/usr/include -c -o bit.o bit.c bit.c:32:17: fatal error: lua.h: 沒有此一檔案或目錄 ``` * 執行 `sudo make install`,可能會出現 `install: 無法建立普通檔案` 這樣的訊息,可以直接建立對應的資料夾解決。 * 也可以先確認 `lua` 的預設 module 路徑,然後修改 `make` 中對應的安裝路徑 ```lua [luaPackagePath.lua] print(package.path) print(package.cpath) $ lua luaPackagePath.lua /usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;/usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;/usr/share/lua/5.2/?.lua;/usr/share/lua/5.2/?/init.lua;./?.lua /usr/local/lib/lua/5.2/?.so;/usr/lib/x86_64-linux-gnu/lua/5.2/?.so;/usr/lib/lua/5.2/?.so;/usr/local/lib/lua/5.2/loadall.so;./?.so ``` ### jit-x64 * source code:`*.dasc` * preprocessor:`dynasm.lua`,經過處理的 `*.dasc` 會輸出為 `*.h` * 包裝:將輸出的 `*.h` 與 `dynasm-driver.c` 一起編譯成 `jit-x64` `dynasm-driver.c` 包含 JIT 初始化,配置記憶體空間,及釋放記憶體空間 [Intel x86_64 instruction set ](https://msdn.microsoft.com/en-us/library/windows/hardware/ff553442%28v=vs.85%29.aspx) ### Benchmark-Baseline 專案中預設執行的 benchmark 有三:`awib.b`、`mandelbort.b`、`hanoi.b`,執行的 compiler 為 `jit-x64` ### Contraction 將連續的 pointer 移動事件及連續對同一個 byte 的計算,整理起來計算有多少次,直接使用`add`及`sub`。 > 之前修課有寫過這個作業,jserv 老師提過增加新功能要以模組化的方式設計,提高可攜性及修改彈性[name=LanKuDot][color=red] ```c int contraction(char **tape) { unsigned int count = 0; char ref_ch = **tape; // Use first char as the reference char do { ++count; // 'count' will be at least 1. } while(*(++(*tape)) == ref_ch); --(*tape); return count; } ``` ### Clear, Multiply, Divide, Move, and Copy 因為這些都牽涉到迴圈的運作,所以可以一併分析。觀察 bf 的原始碼,歸類一些特性 (`$x` 為 move to cell `x`): #### Clear * `[-]`:清空目前的 data cell * `[-{move y cell}]`:包含目前的 cell,每隔 y cells 減 1,直到遇到值為 0 的 cell #### Move or Copy * `[- $1- $2]`:$1 = $1 - $2, than $2 = 0 (cell data 沒有負數,推測 $1 > $2) * `[- $1+ $2]`:$1 = $1 + $2, than $2 = 0 * `[- $1- $2+ $victim]`:要注意 $1, $2, $victim 的移動量加總為 0 * $1 = $1 - $victim * $2 = $2 + $victim * than $victim = 0 * 最後一個一定是 victim,因為這樣回到迴圈起頭時才能跳出迴圈。 #### Multiply * 可以將 Move 或 copy 視為 1 倍的 multiply * 注意:裡面的移動量一樣總合要為 0 * `[- $1++++++ $2]`:$1 = $1 + 6 * $2, than $2 = 0 * `[- $1------ $2]`:$1 = $1 - 6 * $2, than $2 = 0 * `[- $1+++ $2---- $victim]`: * $1 = $1 + 3 * $victim * $2 = $2 - 4 * $victim * than $victim = 0 ### Detect single loop 為了實現上面 section 的想法,必須處理 single loop: * 第一版 ```c int isSingleLoop(char *tape) { while (*++tape != ']') { // Usually true if (*tape == '[') // Usually false return 0; } return 1; } ``` * 第二版:branch predictor friendly,並紀錄 loop end ```c int isSingleLoop(char *tape, char **loop_end) { while (*++tape != ']' && *tape != '[') // Usually true ; if (*tape == ']') { *loop_end = tape; return 1; } else { *loop_end = NULL; return 0; } } ``` 紀錄 `loop_end` 可以在做完 single loop 簡化操作後,直接讓 `p` 指向 `loop_end`。 ### Handle clear, copy, move, and multiply 這些都有幾個共通點: * `[(減)+ (移動 (加)+/(減)+)+ 移動]`:`()+` 為出現1個或多個的意思 * loop 內的總移動量為 0 設計一個 structure 將分析到的資訊包起來: ```c typedef struct __CELL_ACTIONS_ { int valid_length; int victim_divisor; int cell_move[MAX_ACTION]; int victim_multiplier[MAX_ACTION]; } Cell_Actions; ``` * `vaild_length`:紀錄 `cell_move` 與 `victim_multiplier` 的有效長度,或是其他類型的 single loop * -1:不是可以簡化的 single loop * 總移動量不為 0 * 裡面有 `.` `,` * 0:Clear loop * 正值:可簡化的 single loop * `victim_divisor`:victim 的除數。因為可能會出現 `[---->+<]` 這種情況,如果 victim 原為 12,則每次減 4 的話,下一個 cell 只會被加 3 (=12/4) 次。 * `cell_move`:紀錄到下個 cell 的距離 (不是相對於 victim 的距離) * `victim_multiplier`:針對這個 cell 的操作,victim 要乘上的值 呼叫 `handle_single_loop()` 會得到分析結果,接下來是使用流程: 1. 判定是可以簡化的 single loop 2. 將目前 cell (也就是 victim) 的值除以 `victim_divisor` 3. 移動到下個 cell 4. 對要操作的 cell 的值設為 `$cur_cell` + `victim_multiplier` * `$victim` 5. 回到 3.,直到所有有效 cell 都被操作過 6. 將目前 tape 的 pointer 指向 `loop_end` 7. data pointer 指向 victim ### 問題 - [ ] 我該怎麼修改 `jit-x64` 讓我可以得到 data cell 的值並存到變數中? * 嘗試下列 code,但總是會 segmenation fault ```clike uint32_t cur_cell_value = 0; | push r9 | mov r9, byte [PTR] | movzx [cur_cell_value], r9 | pop r9 ``` ### Multiply and Copy * 將 copy 視為 multiply 1 的操作 * 預期格式為:開頭為 `-` → 連續移動到第一個位址 `<`或`>`→ `+` → 連續移動到第二個位址 `<`或`>` → `+` → 移動回原本的位址。 * `dst_1`儲存第一個位址要被歸零的位址差距:`<`為 -1,`>`為 +1 * `dst_2`儲存第二個位址要被歸零的位址差距 * `mul_1`、`mul_2`:儲存第一二個乘數的值 * 最後將原本位址的值歸零 ```clike= | push r8 | push r9 | movzx r8, byte [PTR] | imul r8, mul_1 | mov r9, PTR | add r9, dst_1 | add byte [r9], r8 // 出現 mixed operand size in `add xb,rq' // byte [r9] 為 8 bits, 而 r8 為 64 bit,使用 r8b 解決問題 | movzx r8, byte [PTR] | imul r8, mul_2 | mov r9, PTR | add r9, dst_2 | add byte [r9], r8 // 這一行也是 | mov byte [PTR], 0 | pop r9 | pop r8 ``` ### Parser * 跟昆憶哥討論過後,他給我提示說,讀取 brainfunck 原始碼是在 `util.h` 的 `read_file`,不過他就是整坨讀近來,沒有處理換行或是非有效字元等等問題。 * 所以開始處理在讀檔時所遇到的非有效 brainfuck 程式碼,不過我思考許久,我想不到確定可以比 `jit-x64.dasc` 中的 switch case 效能還要快的 parser 方式,因為原先 `util.h` 中是使用 `fread` ,只進行一次讀取,不過如果要在讀檔階段做 parse 那就必須一個字元一個字元判斷,我認為這會比起原先的讀檔還費時,所以我最後的作法有點像是把`jit-x64.dasc` 中的 switch case 搬到讀檔時處理,感覺不是很好的方式,所以我預測這樣的改寫效能可能不會提升。 ```clike= char *read_file(const char * const filename) { ...... size_t i=0; while(fread(code+i,1,1,fp)){ switch(*(code+i)){ case '>': case '<': case '+': case '-': case '.': case ',': case '[': case ']': i++; break; } } char *code_dup = malloc(sizeof(char) * i); memcpy(code_dup,code,i); free(code); ``` 但是實驗結果非常奇怪,其實我認為這組實驗數據應該是有問題的。 * 首先是我一開始做完parse後並未回傳正確大小之空間,這會造成的結果不只是在進行 JIT compilation 時,原先較大的空間中的 garbage value 會造成錯誤(雖然這是範例剛好沒有遇到),而且還會變成沒有替 compilation 省下處理的時間(不過 prediction 有可能會比較順利,因為多出來的空間中大部份可能是 garbage value )。 * 再者,實驗結果中 `awib.b` 的效能提升相當有限,這也是蠻奇怪的,如果說事前的 parser 真的能夠對效能提升有所幫助,那其提升的比例應該會與其 code size 的改變有關,見下表比較 code size 的改變再對照效能分析,發現 code size 減少最多的 `awib.b` 效能提升的比例卻不如 `hanoi.b` 。 | |Origin Size|Parsed Size| |-------------|-----------|-----------| |awib.b |44463 |31514 | |mandelbrot.b |11669 |11451 | |hanoi.b |54593 |53884 | * 最後我認為上面那段 `read_file` 程式的 parse 應該與 `jit-x64.dasc` 中的 switch case 合併,這樣效能會比現在這個方式還要更佳。 > 但因為我處理優化都是透過 function call,所以一開始就傳入處理好的字串會比較簡單。讓優化函式可以專注在目標 pattern 的判斷,而不用再處理非有效字元。[name=LanKuDot][color=red] `jit-x64.dasc`: ```clike= tape_head = read_file(argv[1]); for (char *p = tape_head; *p; p++) { switch (*p) { case '>': repeat = contraction(&p); | add PTR, repeat break; case '<': repeat = contraction(&p); | sub PTR, repeat break; case '+': repeat = contraction(&p); | add byte [PTR], repeat break; case '-': repeat = contraction(&p); | sub byte [PTR], repeat break; case '.': | movzx edi, byte [PTR] | callp putchar break; case ',': | callp getchar | mov byte [PTR], al ....... ``` ## 效能 ### Baseline ```shell progs/awib.b GOOD 177.3ms progs/mandelbrot.b GOOD 5342.7ms progs/hanoi.b GOOD 14622.7ms ``` ### Contraction ```shell progs/awib.b GOOD 75.9ms progs/mandelbrot.b GOOD 1960.0ms progs/hanoi.b GOOD 8193.4ms ``` ### Clear loop 可以發現 `hanoi.b` 有很多 clear loop ```shell progs/awib.b GOOD 78.8ms progs/mandelbrot.b GOOD 1881.9ms progs/hanoi.b GOOD 207.6ms ``` ### Parser withoout clear loop > 注意這邊的測試是使用不同的電腦 Only Contraction: ```shell Executing Brainf*ck benchmark suite. Be patient. progs/awib.b GOOD 62.0ms progs/mandelbrot.b GOOD 1390.6ms progs/hanoi.b GOOD 5036.3ms ``` Parse but don't duplicate memory: ```shell Executing Brainf*ck benchmark suite. Be patient. progs/awib.b GOOD 65.8ms progs/mandelbrot.b GOOD 1439.4ms progs/hanoi.b GOOD 4614.0ms ``` Duplicate correct memory: ```shell Executing Brainf*ck benchmark suite. Be patient. progs/awib.b GOOD 58.3ms progs/mandelbrot.b GOOD 1453.8ms progs/hanoi.b GOOD 4683.7ms ``` ## 其他資料 * 輸出 execution map:gprof * [優化 BF Compiler](http://www.wilfred.me.uk/blog/2015/08/29/an-optimising-bf-compiler/) ## Reference * [What does a just-in-time (JIT) compiler do?](http://stackoverflow.com/questions/95635/what-does-a-just-in-time-jit-compiler-do) * [Just-in-time compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation) ###### tags: `sysprog21` `sysprog2016`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully