function is an object

python 的 function 是一個物件, 所以可以放進 collection 中等以後被呼叫.
>>> def a():
 print 'a';

 
>>> def b():
 print 'b';

 
>>> def c():
 print 'c';

 
>>> list = [a,b,c];
>>> for f in list:
 f();

 
a
b
c

split large file

I use Notepad++ to check log and debug.
When log file is large, I need to split large file to smaller ones.
I can't find tool so write codes to split file in Java every time.
I use Python to write a tool to split file when studying python.
Welcome to use this tool if you also need to split large log file.

Shell 筆記


  1. cd /d %~dp0
    不管執行 script 時候路徑在哪, cd /d %~dp0 後 script 的路徑就到該 script 路徑下
    script, 放在 f:/test/test.bat
    cd /d %~dp0
    dir
    
    execute: f:/test/test.bat
    f:\>test\test.bat
    
    f:\>cd /d f:\test\
    
    f:\test>dir
     磁碟區 F 中的磁碟是 新增磁碟區
     磁碟區序號:  B0DF-F1C2
    
     f:\test 的目錄
    
    2015/07/14  上午 11:41    <DIR>          .
    2015/07/14  上午 11:41    <DIR>          ..
    2015/07/14  上午 11:42                16 test.bat
                   1 個檔案               16 位元組
                   2 個目錄  224,189,362,176 位元組可用
    

Git 筆記


  1. 從 branch merge 回 master, 導致 pom.xml conflict, 想 reset pom.xml 因為這不是我要 merge 的內容. (stackoverflow)
     git reset pom.xml
     git checkout pom.xml
    
  2. 想清掉 untrack file
    git clean -f
    
  3. 想清掉 untrack folder
    git clean -f -d
    
  4. 有次一個 branch 太久沒 pull 了, 後來不知道誰改了甚麼, 要再 pull 都 conflict 一堆.
    反正我也沒有要保留 local 的東西, 同事就教我強制把 local 的檔案 reset 到某個版本
    git reset comm_id --hard
    
  5. 在 pull 之前就 commit, git status 出現 "Your branch is ahead of 'origin/master' by 25 commits" 的訊息, 用 reset 還原
    git reset --hard origin/master
    
  6. 要把一個 branch 傳送到另一個 repository
    # new remote
    git remote add remotename git@git.abc.com:test/test.git
    
    # push branch to remote
    git push -u remotename branchname
    

Basic Perl 筆記


  1. 單引號的 \n 不會換行, 雙引號內的 \n 會換行
    print("line1\nline2\n\n\n\n");
    print('line3:abc\nabc');
    
    d:\>test.pl
    input string:abc
    input number:4
    abcabcabcabc
    d:\>test.pl
    equal:1
    d:\>test.pl
    line1
    line2
    
    
    
    line3:abc\nabc
    
  2. 用 . 連字串
    $a = 'abc\nabc';
    $b = "def\ndef";
    $c = $a . $b . "QQQ";
    print("$c");
    
    d:\>test.pl
    abc\nabcdef
    defQQQ
    
  3. 數字 0 是 false, 其他都是 true.
    空字串是 false, 其他都是 true.
    undef 是 false.
    print($abc == 0); # print 1, $abc is undef and is false, false is 0
    $abc = '';
    print($abc == 0); # print 1, $abc is empty string and is false, false is 0
    $abc = "0";
    print($abc == 0); # print 1, $abc is string 0, Perl transfer to number 0
    
    d:\>test.pl
    111
    
  4. 使用 lt,le,eq,ge,gt 作字串的比較, perl 會用 ASCII 或 Unicode 作為順序參考排大小.
    $t1 = "a";
    $t2 = "a";
    if ($t1 eq $t2) {
        print("same");
    } else {
        print("different");
    }
    
    d:\>test2.pl
    same
    
    $t1 = "a";
    $t2 = "b";
    if ($t1 eq $t2) {
        print("same");
    } else {
        print("different");
    }
    
    d:\>test2.pl
    different
    
  5. 取得使用者輸入: <STDIN>
    print("What's your name?\n");
    $name = <STDIN>;
    print("$name, how are you?");
    
  6. d:\>test.pl
    What's your name?
    isaac
    isaac
    , how are you?
    



  7. 這時候就發現 <STDIN> 取得的資料會包含換行, 換行字元是不需要的, 就用 chomp 去掉
    print("What's your name?\n");
    $name = <STDIN>;
    chomp($name);
    print("$name, how are you?");
    

  8. d:\>test.pl
    What's your name?
    isaac
    isaac, how are you?
    



  9. 如果輸入的時候按 Ctrl + C, 沒有輸入, <STDIN> 會回傳 undef. 這時候可以用 defined 來判斷是否為 undef
    $name = <STDIN>;
    if (defined($name)) {
     chomp($name);
     print("input:$name");
    } else {
     print("no input\n");
    }
    

  10. d:\>test.pl
    no input
    Terminating on signal SIGINT(2)
    

  11. 使用陣列
    while ($i < 10) {
     $i += 1;
     $names[$i] = "p$i";
     print("$names[$i]\n");
    }
    $names[100] = "qq";
    print("$names[100]");
    
    d:\>test.pl
    p1
    p2
    p3
    p4
    p5
    p6
    p7
    p8
    p9
    p10
    qq
    
  12. 取陣列最後一個值的 index: $#names
    $names[100] = "aaa";
    print("$#names\n"); 
    print($names[$#names]); 
    
    d:\>test.pl
    100
    aaa
    
  13. 列出陣列全部值 (這裡是要注意 $#names 的值是最後一個 index 而不是長度. )
    想得到長度要再加 1, 因為還有第 0 個.
    $i = 0;
    while ($i < 10) {
     $i += 1;
     $names[$i] = $i;
    }
    $i = 0;
    while ($i <= $#names) {
     $i += 1;
     print("$names[$i]");
    }
    
    d:\>test.pl
    12345678910
    
  14. index 可以指定負數, -1 就是最後一個值
    $names[0]="0";
    $names[1]="1";
    $names[2]="2";
    $names[3]="3";
    print($names[0]); #0
    print($names[-1]); #3
    print($names[-2]); #2
    print($names[-3]); #1
    print($names[-4]); #0
    
  15. 用 @串列變數 = (用逗號分隔的串列值) 宣告串列
    會置換變數, \n 會換行, 就跟雙引號宣告的變數一樣
    @a = (1,2,3); 
    print("\@a:@a\n");
    
    @b = (1..3); # 使用 .. 會 +1
    print("\@b:@b\n");
    
    @c = (1.4...5.6); # 使用 .. 會無條件捨去小數
    print("\@c:@c\n");
    
    @d = (2,6...10,43); 
    print("\@d:@d\n");
    
    $e = 10;
    $f = 20;
    @g = ($e...$f); 
    print("\@g:@g\n");
    
    @h = ("a", "b\n", "c");  #有換行效果
    print("\@h:@h\n");
    
    d:\>test.pl
    @a:1 2 3
    @b:1 2 3
    @c:1 2 3 4 5
    @d:2 6 7 8 9 10 43
    @g:10 11 12 13 14 15 16 17 18 19 20
    @h:a b
     c
    
  16. 用 @串列變數=qw(用空白分隔的串列值) 宣告串列,
    不會置換變數, \n 不會換行, 跟單引號宣告的變數一樣
    @a = qw(1 2 3); #可以用 qw()
    print("\@a:@a\n"); 
    
    @b = qw<1 data-blogger-escaped-..3="">; #也可以 qw<>, 但 1..3 會直接印出來
    print("\@b:@b\n");
    
    @c = qw/1.4...5.6/; #也可以 qw//
    print("\@c:@c\n");
    
    @d = qw!2 6...10 43!; 
    print("\@d:@d\n");
    
    $e = 10;
    $f = 20;
    @g = qw($e...$f); #沒有換變數的效果
    print("\@g:@g\n");
    
    @h = qw("a" "b\n" "c");  #無換行效果, 雙引號也會被印出來
    print("\@h:@h\n");
    
    d:\>test.pl
    @a:1 2 3
    @b:1..3
    @c:1.4...5.6
    @d:2 6...10 43
    @g:$e...$f
    @h:"a" "b\n" "c"
    
  17. 一次 assign 值給多個變數
    ($a,$b,$c) = (1,2,3);
    print("a:$a\n");
    print("b:$b\n");
    print("c:$c\n");
    
    d:\>test.pl
    a:1
    b:2
    c:3
    
  18. 換值
    $a[0] = 0;
    $a[1] = 1;
    ($a[0],$a[1]) = ($a[1],$a[0]);
    print("a[0]:$a[0]\n");
    print("a[1]:$a[1]\n");
    
    d:\>test.pl
    a[0]:1
    a[1]:0
    
  19. 如果移除括號也不改變原本意思, 就可以移除括號
    @a = 1...3;
    print(@a);
    
    d:\>test.pl
    123
    
  20. 如果一個串列值是另一個串列, 被包含在串列裡的會被展開
    @a = 1..3;
    $b = 4;
    @c = ();
    #d is undefined
    @e = (@a,$b,@c,@d);
    print(@e);
    
    d:\>test.pl
    1234
    
  21. 用 pop 可以從串列取值出來, 沒有值的話會取出 undef
    @a = 1..3;
    while (defined($val = pop(@a))) {
     print("$val\n");
    }
    
    d:\>test.pl
    3
    2
    1
    
  22. 用 push 可以把值放進串列
    @a = 1..3;
    push(@a,4);
    while (defined($val = pop(@a))) {
     print("$val\n");
    }
    
    d:\>test.pl
    4
    3
    2
    1
    
  23. 串列可以複製全部的值 (不是 reference, 所以複製後對串列的修改不會互相影響)
    @a = 1...3;
    @b = @a;
    print("a:\n");
    while (defined($val = pop(@a))) {
     print("$val\n");
    }
    print("b:\n");
    while (defined($val = pop(@b))) {
     print("$val\n");
    }
    
    d:\>test.pl
    a:
    3
    2
    1
    b:
    3
    2
    1
    
  24. 一次 assign 值給多個變數
    ($a,$b,$c) = (1,2,3);
    print("a:$a\n");
    print("b:$b\n");
    print("c:$c\n");
    
    d:\>test.pl
    a:1
    b:2
    c:3
    
  25. qw 透過空白來區分值, 也可以 assign 值給多個變數
    ($google,$yahoo,$linkedin) = qw {
     http://www.google.com
     http://www.yahoo.com
     http://www.linkedin.com
    };
    print("google:$google\n");
    print("yahoo:$yahoo\n");
    print("linkedin:$linkedin\n");
    
    ($google,$yahoo,$linkedin) = qw !
     http://www.google.com
     http://www.yahoo.com
     http://www.linkedin.com
    !;
    print("google:$google\n");
    print("yahoo:$yahoo\n");
    print("linkedin:$linkedin\n");
    
    d:\>test.pl
    google:http://www.google.com
    yahoo:http://www.yahoo.com
    linkedin:http://www.linkedin.com
    google:http://www.google.com
    yahoo:http://www.yahoo.com
    linkedin:http://www.linkedin.com
    
  26. shift 從 index 0 取值, unshift 從 index 0 放值
    @a = ();
    unshift(@a,"1");
    unshift(@a,"2");
    unshift(@a,"3");
    print("@a\n"); #321
    print(shift(@a)); #3
    print(shift(@a)); #2
    print(shift(@a)); #1
    
  27. pop 從 index 最後取值, push 從 index 最後放值
    @a = ();
    unshift(@a,"1");
    unshift(@a,"2");
    unshift(@a,"3");
    print("@a\n"); #321
    push(@a,"4"); #3214
    push(@a,"5"); #32145
    push(@a,"6"); #321456
    print(shift(@a)); #3
    print(shift(@a)); #2
    print(shift(@a)); #1
    print(pop(@a)); #6
    print(pop(@a)); #5
    print(pop(@a)); #4
    
    d:\>test.pl
    3 2 1
    321654
    
  28. pop,push,shift,unshift 可以一次處理整個串列
    @a = ();
    unshift(@a, qw/ 1 2 3 /); #123
    unshift(@a, qw/ 4 5 6 /); #456123
    push(@a, qw/ 7 8 9 /); #456123789
    push(@a, qw/ 10 11 12 /); #456123789101112
    print(shift(@a)); #4
    print(shift(@a)); #5
    print(shift(@a)); #6
    print(shift(@a)); #1
    print(shift(@a)); #2
    print(shift(@a)); #3
    print(pop(@a)); #12
    print(pop(@a)); #11
    print(pop(@a)); #10
    print(pop(@a)); #9
    print(pop(@a)); #8
    print(pop(@a)); #7
    defined(pop(@a)) ? print("value") : print(" no val"); # no val
    
    d:\>test.pl
    456123121110987 no val
    
  29. 切串列: splice
    @a = 1..9;
    splice(@a,1); # 從 index 1 之後全切掉
    print("@a\n"); #1
    @a = 1..9;
    @removed = splice(@a,1,3); # 從 index 1 切掉三個
    print("@a\n"); #156789
    print("@removed\n"); #234
    @a = 1..9;
    @b = qw (- 9 8 7 6 5 4 3 2 1 -);
    splice(@a,1,3,@b); # 從 index 1 切掉三個之後加上 b 串列
    print("@a\n"); #1-987654321-56789
    @a = 1..9;
    splice(@a,1,0,@b); # 從 index 1 加上 b 串列, 完全不切掉任何值
    print("@a\n"); #1-987654321-23456789
    
    d:\>test.pl
    1
    1 5 6 7 8 9
    2 3 4
    1 - 9 8 7 6 5 4 3 2 1 - 5 6 7 8 9
    1 - 9 8 7 6 5 4 3 2 1 - 2 3 4 5 6 7 8 9
    
  30. print 的時候用 \@ 來跳脫串列的 @
    @yahoo = qw { yahoo hohoho };
    print("yahoo:@yahoo\n");
    print("mail:test@yahoo.com\n"); #@沒跳脫, 會換成串列內容
    print("mail:test\@yahoo.com\n"); #@跳脫了, 不會換成串列內容
    
    d:\>test.pl
    yahoo:yahoo hohoho
    mail:testyahoo hohoho.com
    mail:test@yahoo.com
    
  31. 串列可以當成陣列用
    @names = qw (a b c);
    print("index 0:$names[0]\n"); #a
    print("index 1:$names[1]\n"); #b
    print("index 2:$names[2]\n"); #c
    
    d:\>test.pl
    index 0:a
    index 1:b
    index 2:c
    
  32. 如果緊接著串列變數要印[index]的字串, 串列變數就要別處理
    @names = qw (a b c);
    print("index 0:${names[0]}[0]\n"); #用 {} 把變數圈起來
    print("index 1:$names[1]"."[1]\n"); #用 . 把字串分開
    print("index 2:$names[2]\[2\]\n"); #用 \ 跳脫 [ 與 ]
    
    d:\>test.pl
    index 0:a[0]
    index 1:b[1]
    index 2:c[2]
    
  33. foreach iterate 串列
    @names = qw (a b c);
    foreach $name (@names) {
     print("$name\n");
    }
    
    d:\>test.pl
    a
    b
    c
    
  34. foreach 裡面宣告的變數不會影響外部的變數
    $name = "hello";
    @names = qw (a b c);
    foreach $name (@names) {
     print("$name\n");
    }
    print("$name\n"); #hello
    
    d:\>test.pl
    a
    b
    c
    hello
    
  35. 預設變數 $_, 比方說在 foreach 的時候沒宣告變數就可以使用 $_
    foreach (qw / a b c /) {
     print("$_\n");
    }
    
    d:\>test.pl
    a
    b
    c
    
  36. reverse 把串列反過來
    @a = (1,2,3,4,5);
    print("a:@a\n");
    print("reverse a:".reverse(@a)."\n");
    
    d:\>test.pl
    a:1 2 3 4 5
    reverse a:54321
    
  37. 用 each iterate 串列, each 會一次回傳 index 與 value.
    @a = (1,2,3,4,5);
    while (($index,$value) = each(@a)) {
     print("index:$index, value:$value\n");
    }
    
    d:\>test.pl
    index:0, value:1
    index:1, value:2
    index:2, value:3
    index:3, value:4
    index:4, value:5
    
  38. 當進行字串的運算時, 就得到字串的結果. 當執行數字的計算時, 就得到數字的結果. 是字串還是數字是由運算符號決定.
    print(3*3 ."\n");
    print(3x3 ."\n");
    @a = qw{1 100 3 4 5}; #長度5
    print(3*@a ."\n"); #3*5=15
    print(3x@a ."\n");
    
    C:\Users\isaac>test.pl
    9
    333
    15
    33333
    
  39. 在字串的運算時, 串列會印出字串. 在數字的運算時, 串列會印出個數.
    @a = qw {e f d c b a};
    print(2*@a."\n"); #@a 是數字5, 印出 10 (2*5=10)
    print(2x@a."\n"); #@a 是數字5, 印出 22222
    print(sort(@a)); #印出排序過的字串
    
    C:\Users\isaac>test.pl
    12
    222222
    abcdef
    
  40. 運算串列的時候會印出串列, 但有時候運算串列的時候需要印出串列的 size. 這時候要用 scalar 這個假函式讓它變串列的 size
    @list = qw /a b c/;
    print("list:",@list,", size:",scalar @list);
    
    d:\>test.pl
    list:abc, size:3
    
  41. 可以在 console 多行資料給串列, 在 windows 下按 Ctrl+Z 結束, 在 Linux 下按 Ctrl+D 結束
    @commands = <STDIN>;
    print("commands:",@commands);
    
  42. d:\>test.pl
    a
    b
    c
    d
    e
    ^Z
    commands:a
    b
    c
    d
    e
    




  43. STDIN 輸入資料進串列, 每一行都會加上換行符號, 這不一定是我們要的, 可以用 chomp 去掉換行符號
    @commands = <STDIN>;
    chomp(@commands);
    print("commands:",@commands);
    

  44. d:\>test.pl
    a
    b
    c
    d
    e
    ^Z
    commands:abcde
    




  45. 可以簡化寫法
    chomp(@commands = <STDIN>);
    print("commands:",@commands);
    

  46. d:\>test.pl
    a
    b
    c
    d
    e
    ^Z
    commands:abcde
    




  47. 定義副常式 subroutine, 呼叫的方式是用 &副常式名稱 來呼叫.
    &hellosubroutine;
    
    sub hellosubroutine {
     print("hello subroutine");
    }
    
    d:\>test.pl
    hello subroutine
    
  48. subroutine 存取的變數都是全域變數
    &changeto5;
    print($n,"\n");
    &changeto10;
    print($n,"\n");
    
    sub changeto5 {
     $n = 5;
    }
    
    sub changeto10 {
     $n = 10;
    }
    
    d:\>test.pl
    5
    10
    
  49. subroutine 的最後一行計算就是回傳值
    print(&changeto5,"\n");
    print(&print,"\n");
    print(&add1ToN,"\n");
    print($n,"\n");
    
    sub add1ToN {
     $n + 1;
    }
    
    sub changeto5 {
     $n = 5;
    }
    
    sub print {
     print("");
    }
    
    d:\>test.pl
    5
    1
    6
    5
    
  50. subroutine 加參數
    sub test {
     print("arg[0]:$_[0]\n");
     print("arg[1]:$_[1]\n");
     print("arg[2]:$_[2]\n");
     print("arg[3]:$_[3]\n");
    }
    
    print("======3 args============\n");
    &test(1,2,3);
    print("======4 args============\n");
    &test(1,2,3,4);
    
    d:\>test.pl
    ======3 args============
    arg[0]:1
    arg[1]:2
    arg[2]:3
    arg[3]:
    ======4 args============
    arg[0]:1
    arg[1]:2
    arg[2]:3
    arg[3]:4
    
  51. 參數傳入 subroutine 後會存在 @_ 這個預設串列
    sub test {
     print("@_");
    }
    
    &test(1,2,3,4,5);
    
    d:\>test.pl
    1 2 3 4 5
    
  52. 用 my 可以宣告 subroutine 裡的區域變數
    sub test {
     $a = "a";
     my $b = "qq";
    }
    
    &test;
    print("a:$a\n");
    if (!defined($b)) {
     print("b is undef");
    }
    
    d:\>test.pl
    a:a
    b is undef
    
    sub max {
     my $max = shift @_;
     for (@_) {
      if ($max < $_) {
       $max = $_;  
      }
     }
     $max; #return
    }
    
    print(&max(1,2,3,4,5),"\n");
    if (!defined($max)) {
     print("\$max is undef");
    }
    
    d:\>test.pl
    5
    $max is undef
    
  53. 一個 subroutine 中本來就有一個變數, 又透過 my 宣告區域變數, subroutine 在 my 宣告後, 會以 my 宣告的變數值為主, 但又不影響原本的全域變數值
    sub max {
     $max = 333;
     my $max = shift @_;
     for (@_) {
      if ($max < $_) {
       $max = $_;  
      }
     }
     print($max,"\n"); #max=5
     $max; #return 5
    }
    
    print(&max(1,2,3,4,5),"\n");
    print($max); #max=333
    
    d:\>test.pl
    5
    5
    333
    
  54. 用 my 一次宣告多個變數來接外來的參數
    sub max {
     my($a,$b,$c,$d) = @_;
     print("a:$a,b:$b,c:$c,d:$d\n");
    }
    
    &max(1,2,3); 
    &max(1,2); 
    
    d:\>test.pl
    a:1,b:2,c:3,d:
    a:1,b:2,c:,d:
    
    
  55. 檢查陣列長度是否符合預期
    sub max {
     if (@_ != 2) {
      print("argument size should be 2\n");
     }
     my($a,$b) = @_;
     if ($a > $b) { 
      $a;
     } else {
      $b;
     }
    }
    
    print("max:",&max(1,2,3));
    
    D:\>test.pl
    argument size should be 2
    max:2
    
  56. use strict 強迫程式碼用比較好的方式撰寫 原本的範例
    sub test {
     foreach $qq (qw /a b c/) {
      print("$qq\n");
     }
    }
    
    $qq = 5;
    &test;
    print("qq:",$qq);
    
    d:\>test.pl
    a
    b
    c
    qq:5
    
    加上 use strict 之後
    use strict;
    sub test {
     foreach $qq (qw /a b c/) {
      print("$qq\n");
     }
    }
    
    $qq = 5;
    &test;
    print("qq:",$qq);
    
    d:\>test.pl
    Global symbol "$qq" requires explicit package name at D:\test.pl line 3.
    Global symbol "$qq" requires explicit package name at D:\test.pl line 4.
    Global symbol "$qq" requires explicit package name at D:\test.pl line 8.
    Global symbol "$qq" requires explicit package name at D:\test.pl line 10.
    Execution of D:\test.pl aborted due to compilation errors.
    
  57. return 回傳值
    原本 subroutine 的最後一行程式就是該 subroutine 的回傳值, 不過使用 return 就可以在最後一行之前回傳
    sub indexOf {
        my($keyword,@texts) = @_;
        foreach (0...$#texts) {
            if ($keyword eq $texts[$_]) {
                return $_;
            }
        }
        -1;
    }
    
    print(&indexOf("test",qw/ ab r ewr /),"\n");
    print(&indexOf("test",qw/ ab r ewr test/),"\n");
    
    d:\>test.pl
    -1
    3
    
  58. 當呼叫 subroutine 時需要用 & 來呼叫, 這是透過 & 來告訴 perl 這是一個 subroutine. 不過如果呼叫的時候有加參數,讓 perl 知道這是個 subroutine, 就不需要 & 了.
    sub say {
        print("say:",@_);
    }
    
    say("hello");
    
    d:\>test.pl
    say:hello
    
  59. 但是如果 subroutine 的名稱跟 perl 預設的 function 同名, 那還是需要透過 & 來告訴 perl 這是 subroutine 而不是預設的 function.
    sub print {
        print("print:",@_);
    }
    print("hello\n");
    &print("hello");
    
    d:\>test.pl
    hello
    print:hello
    
  60. 使用 my 宣告的區域變數在 subroutine 結束後值就不在了, 使用 state 宣告的話, 變數的狀態會記在 subroutine 中. 不過要宣告 use 5.010 才可以使用這個功能.
    use 5.010;
    
    sub test {
        my $localn = 0;
        $localn = $localn+1;
        print("test.localn:",$localn,"\n");
        
        state $n = 0;
        $n = $n+1;
        print("test.n:",$n,"\n");
    }
    
    sub test2 {
        state $n = 0;
        $n = $n+1;
        print("test2.n:",$n,"\n");
    }
    
    &test;
    &test;
    &test2;
    
    d:\>test.pl
    test.localn:1
    test.n:1
    test.localn:1
    test.n:2
    test2.n:1
    
    use 5.010;
    
    sub append {
        state @list;
        foreach (@_) {
            push(@list,$_);
        }
        print("list:",@list,"\n");
    }
    
    &append(qw/a b c/);
    &append(qw/1 2 3/);
    &append(qw/Q R T/);
    
    d:\>test.pl
    list:abc
    list:abc123
    list:abc123QRT
    
  61. console 輸入
    $line = <STDIN>;
    chomp($line);
    print($line);
    
    d:\>test.pl
    test
    test
    
    while (defined($line = <STDIN>)) {
        print($line);
    }
    
    d:\>test.pl
    test
    test
    qq
    qq
    BB
    BB
    ^Z
    
    d:\>
    
    while(<STDIN>) {
        print($_);
    }
    
    d:\>test.pl
    test
    test
    qq
    qq
    bb
    bb
    QQ
    QQ
    ^Z
    
    
    foreach (<STDIN>) {
        print($_,"\n");
    }
    
    d:\>test.pl
    a
    b
    c
    ^Z
    a
    
    b
    
    c
    
    這裡值得說明的是: perl 在 while 迴圈中使用 <STDIN> 做了特別處理, 使用 while (<STDIN>) 的效果會變這樣
    while (defined($_ = <STDIN>)) {
        print($_);
    }
    
    d:\>test.pl
    test
    test
    bb
    bb
    ^Z
    
    不過使用 foreach 則會把 STDIN 的結果全都讀進來才用 foreach iterate.
    這代表著如果 STDIN 的 input 量很大, 使用 while 沒關係因為每次換行都會輸出一次.
    使用 foreach 來讀大資料的話有可能一次佔用很多記憶體.
  62. 在程式中使用 while (<>) 可以讀取開啟程式時參數指定的檔案, 或者用 - 來當成標準輸入
    test2.txt
    {"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    
    julie.txt
    2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    test.pl
    while (<>) {
        print("print:$_\n");
    }
    
    d:\>test.pl test2.txt julie.txt
    print:{"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    print:2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    在參數指定 - 可以加上 STDIN 的效果
    test.pl
    while (<>) {
        print("print:$_\n");
    }
    
    d:\>test.pl test2.txt - julie.txt
    print:{"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    qq
    print:qq
    
    bb
    print:bb
    
    ^Z
    print:2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    看到 - 的處理都會多一個換行, 可以用 chomp 去掉.
    另外 perl 鼓勵我們少打字, 呼叫 function 的時候不用加括號也可以
    while (<>) {
        chomp;
        print "print:$_\n";
    }
    
    d:\>test.pl test2.txt - julie.txt
    print:{"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    testt
    print:testt
    ^Z
    print:2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    
    如果沒指定參數, <> 就會從 STDIN 讀取輸入
    while (<>) {
        chomp;
        print "print:$_\n";
    }
    
    d:\>test.pl
    a
    print:a
    b
    print:b
    c
    print:c
    ^Z
    
    
  63. while(<>) 其實是處理 @ARGV, @ARGV 是 perl 的特殊陣列, 裡面會放起動程式的參數, 進入程式後可以像一般陣列一樣使用
    foreach (@ARGV) {
        print("arg:$_\n");
    }
    
    d:\>test.pl a b c
    arg:a
    arg:b
    arg:c
    
    
    @ARGV = qw/a b c/;
    foreach (@ARGV) {
        print("arg:$_\n");
    }
    
    d:\>test.pl d d d
    arg:a
    arg:b
    arg:c
    
  64. print <> 作出 linux 下 cat 的效果 data1.txt
    a
    b
    c
    d
    e
    
    data2.txt
    d
    d
    c
    b
    a
    e
    
    執行 cat
    [root@Platform-151-ninja Isaac]# cat data1.txt data2.txt 
    a
    b
    c
    d
    e
    d
    d
    c
    b
    a
    e
    
    執行 perl
    print <>;
    
    在 linux 執行
    [root@Isaac]# perl test.pl data1.txt data2.txt 
    a
    b
    c
    d
    e
    d
    d
    c
    b
    a
    e
    
    在 windows 執行結果跟在 Linux 執行不太一樣
    d:\>test.pl data1.txt data2.txt
    a
    b
    c
    d
    ed
    d
    c
    b
    a
    e
    
  65. 待續...

Scrum Q&A


Q: 我們用用 SCRUM 想幹嘛? 
A: 我們都曾經遇過在某個專案已經花費很多時間跟人力做得一團亂. 但在某個機緣下少少幾個人展現強大的戰鬥力把待辦清單做完, 成為膾炙人口的佳話. 這個佳話也成為團隊員日後美好的回憶. 會有佳話是因為: 
  1. 待辦事項清單清楚
  2. 有人排出優先順序, 先做重要的, 其他丟掉
  3. 隊員各有擅長的事情, 壓力下大家挑出擅長的事情互相合作完成
  4. 跟使用者端緊密合作, 確保大家做的事情可以上線
就好像玩美式足球. 賽前規劃戰略, 比賽中一球一球緊密配合, 賽後檢討準備下場比賽.
使用 SCRUM, 最終希望在這個組織工作的人都能擁有美好的回憶, 達到卓越的狀態.

Q: 為甚麼要 Daily Scrum? 
A: 因為我 (Scrum Master) 需要知道每個人的狀況, 是否有人被卡住需要我去協調.
此外當隊員都很好溝通, 彼此熟悉, 那當然很好. 如果隊員溝通情況沒那麼好, 或是個性沒那麼搭, Daily Scrum 是個管道強迫大家互相溝通進度或向外求援

Q: 為甚麼要 retrospective meeting?
A: 大家在執行上有甚麼問題, Scrum Master 需要知道, 然後趕快改善, 這是很重要的部分

Q: 為甚麼要 demo?
A: 做出來的東西要快點讓使用者看看是否可用, 還是說應該要快點修正. 
不過 demo 的時候, 使用者的回饋不用照單全收, 比方說他可能會說: 請給我全部欄位都可以排序.
我們可以在 demo 的時候吸收 feedback, 然後分析 feedback 再改善, 而不是把使用者的話當 spec.

Q: 為甚麼要 Story?
A: Story 會描述情境, 別人看了比較知道要滿足甚麼. 你都短短的寫技術或 component 名詞, 只有你現在知道要做甚麼, 以後看 ticket 都需要去爬程式回想才知道想幹嘛. 更糟的是程式不一定能反應當下的情境.

Q: 為甚麼要 Story Point?
A: 為了評估團隊的 througput, 看進步或退步了, 看是否可持續改善.
另外用來跟公司收到的利益結合, 計算 Story 替公司爭取多少利益. 這當成 product owner 的 KPI. 亂開 Story 的 product owner 績效會很差.

Q: Plan Meeting 怎麼進行?
A: 讓每個 member 知道每個 Story 就好了.
"千萬"不要討論實做細節, 那不是 plan meeting 該做的事. 就算實做的"方向"也不該討論.
那是 member 自行決定的東西.
plan meeting 後, member 可以留下來繼續討論怎麼在這個 sprint 內"攻下"這些 story.
照戰略攻克每個 story 很爽...

User Story Template

Reference


User story format:

“As a (user type), I want to (goal), so that (reason).”
Attributes of good user story: INVEST
Independent
Negotiable
Valuable
Estimable
Sized Appropriately
Testable

Best Practices

  • Readability:
    • "One sentence" part is for reader to catch up in 5 seconds
    • The entire user story is for reader to understand in 30 seconds
    • The extra information can be added in notes section. For example, detail requirement, special conditions, and key decisions.
    • Use your best way to communication. For example, for functional requirement, please put it in UI prototype which is more clear than thousand words.
  • Prioritization:
    • Always need to have priority
    • Force ranking from 1 (the top priority) in each category
  • How to improve user story quality:
    • Write often and review with colleagues
    • Share with different stakeholders like customers, NOC, BD, or CEO. Try to get their feedbacks.

[Study Group] Java Performance - JVM Overview

Reference

Notes

  1. HotSpot VM 三個主要元件: VM Runtime, JIT Compiler, memory manager
  2. JIT Compiler 與 Garbage Collector 是 pluggable.
  3. HotSpot VM Runtime 提供 service 與 Common API 給 JIT Compiler 與 Garbage Collector 使用
  4. HotSpot VM Runtime 還提供了諸如啟動程序, thread management, JNI等功能
  5. 32位元的 HotSpot VM 最多只能使用4GB Ram, 而隨著 OS 不同實際上只能使用 1.5-3.3GB Ram
  6. 64位元的系統引進後, HotSpot VM可以使用更大的記憶體, 但隨之而來的是由於java object 的 representation (稱為 ordinary object pointers, or oops)的 size 變大產生的效能問題. 換句話說就是其寬度從32位元變為64位元時跟著一起變大了.
  7. oops 寬度變大導致能放進 CPU cache line 的 oops 減少也使 CPU 效能跟著減少 (跟32位元比起來約減少約8%-15%).
  8. 在 Java6 HotSpot VM, OpenJDK 中增加新功能: compressed oops, 透過 -XX:+UseCompressedOops 就能起動, 使能擁有64位元的 heap 與32位元相同甚至更好的效能 

HotSpot VM Runtime

Command Line Options

Command Line Option 有三種: standard, nonstandard, developer.
  1. Standard: JVM Spec 定的 option, 所以每個 JVM 實作都會支援. 穩定, 有可能在之後的 release 被 deprecated.
  2. Nonstandard: -X 開頭的 option. 不保證每個 JVM 實作都會支援. 也可能在沒告知的情況下被移除. 
  3. Developer: -XX 開頭的 option. 需在特定的系統需求與權限下才能被正確使用. 也可能在沒告知的情況下被移除.
Command Line Option 控制著 HotSpot VM 內部變數的值.
  1. + 或 - 可指定一個 boolean 變數為 true 或 false.
    Ex. -XX:+AggressiveOpts 設定 AggressiveOpts 為 true 去 enable 額外的效能優化
    Ex. -XX:-AggressiveOpts 設定 AggressiveOpts 為 false 去 disable 額外的效能優化
  2. Developer command line 也可以指定nonboolean value.
    Ex. -XX:OptionName=<N>
  3. 幾乎所有 value 為數值的 command line option 都可以使用 k,m,g 結尾來代表 kilo-, mega-, giga-

VM Life Cycle

HotSpot VM Runtime負責開啟與關閉 HotSpot VM
  1. launcher: 開啟 HotSpot VM 的 component.
    Ex. 
    1. java (Linux)
    2. javaw (Windows)
    3. JNI_CreateJavaVM (Embedded JVM through JNI interface)
    4. javaws (Java Web Start)
  2. Launcher 開啟 HotSpot VM 的流程
    1. Parse command line option: 有些 option  會拿來決定如何開啟 HotSpot VM 如
      -server or -client. 有些則會丟給開啟後的 HotSpot VM.
    2. 如果 java heap size 與 JIT compiler type 沒透過 option 指定, 則 Launcher 會在這時候根據環境計算 & 決定
    3. 建立環境變數如 CLASSPATH
    4. 如果 option 沒指定 Main-Class, Launcher 在這時候會去找 Jar file 的 manifest 指定的 Main-Class
    5. 在新建立的非原生(nonprimordial)的 thread 使用 JNI_CreateJavaVM 啟動 HotSpot VM. (原生的 thread 是作業系統建立的 thread)
    6. HotSpot VM 開啟後, 載入 Main-Class, 取得要丟給 Main-Class 的參數
    7. 解析參數後, 透過 JNI method CallStaticVoidMethod 傳入被呼叫的 Main-Class 的 main method.
  3. Java program 或 Java main method 執行完, HotSpot VM 要檢查與清除所有在程式執行中產生的 exception 然後回傳 exit status 給 caller.
  4. 呼叫 JNI method DetachCurrentThread 去 detach Java Main method.
  5. 當 DetachCurrentThread 被呼叫, thread count 會減少, 使 JNI 知道何時可以安全的關閉 HotSpot VM 並確保沒有 thread 在沒有 java frames or stacks 的時候還在 thread 裡面作業.

Class Loader Delegation

一個 class loader 可以叫另一個 class loader 去 load class 就叫做 Class Loader Delegation.
Class Loaders 被階層式的定義, 每個 class loader 有個 delegation parent. 這個 delegation 定義了呈現 class 的方法. Java SE class loader 會 search bootstrap classloader => extention class loader => system class loader (system class loader 就是預設的 application class loader, 也就是從 classpath 載入 class 與 Java main method 的 class loader). application class loader 可以是 Java SE 的 class loader, 也可以是 developer 自訂的 class loader. Java SE 的 class loader 實作了會從 JRE 的 lib/ext 載 class 的 extenstion class loader.

Bootstrap Class Loader

HotSpot VM 實作了 Bootstrap Class Loader, 會從 HotSpot VM BOOTCLASSPATH 載入 class 例如 rt.jar (rt.jar 包含了 Java SE class library).

Type Safety

Java class 或 Java interface 包含 package name 在 class loader 中必須是 unique. 這表示兩個不同的 class loader 內的同樣名字的 class 代表著不同的 class. HotSpot VM 要負責保證 extension customer class loader 不會破壞這個 type safety. => HotSpot 要確保當 class A 呼叫 B.someMethod() 時, A 的 class loader 與 B 的 class loader 透過 class loader 的 tracking constraint 都同意 someMethod 的 parameter 與 return type.

Byte Code Verification

在對於 Java 6 之前 compile 的 class (version number 50), HotSpot VM 使用 type inference 檢查 class file. Java 6 之後, HotSpot VM 使用新的, 效率較好的 type verification 機制.

Class Data Sharing

在 Java 5 之後為了優化啟動速度並減少記憶體使用 & 增加能同時開啟的 JVM 引進的功能. 原理是有些 class 是能夠跨 JVM 共用的, 放在 read-only memory mapped space 可以分享給不同的 JVM, 使不用重新載入 class.

HotSpot VM Garbage Collector

Generational Garbage Collection

HotSpot VM 使用 Generational Garbage Collection, 這個方式依賴兩種觀察:
  1. 大部分物件會很快變 unreachable
  2. 很少從老物件到新物件的關連
這兩個觀察就是 weak generational hypothesis. 根據這個假說, HotSpot VM 將 heap 分成幾塊: 
  • The young generation: 大部分新物件都會 allocate 在 young generation, 在 java heap 中這一塊相對小且很快會被回收, 因為大部分物件被預期會很快變成 unreachable, 存在 young generation 的然後被 minor garbage collection 的物件被期望要很少. 通常 minor garbage collection 會比較有效率因為它處理的空間比較小且包含很多要回收的物件.
  • The old generation: 活比較久的物件會被 promote 到 old generation. 這個區塊比 young generation 大, 成長的也比較慢. GC 較不頻繁, 但時間較久.
  • The permanent generation: 雖說這是個 generation, 但 user allocate 的物件不會被移到這裡. 這個區塊是給 HotSpot VM 使用的. 例如 metadata, internal string..etc
為了確保 GC 時間短, garbage collector 必須不用 scan 整個 old generation 就能從 young generation 指出 live object. 為此 HotSpot VM 使用 card table 來完成這件事. old generation 每 512 bytes 被分成一個 card. card table 是一個 array, 每個 card 有一個 byte 做為 entry. 每個 reference 欄位的更新必須確保包含該 reference 欄位的 card 被註記成 dirty. (透過設定 card table 的 entry). 在 minor GC 的時候只有被註記成 dirty 的 card 會被 scan 來發現從 old generation 連到 young generation 的 reference.

HotSpot VM 與 bytecode interpreter, JIT compiler 互動的時候使用 write barrier 的技巧維護 card table. 這個 barrier 就是一段去改寫 card table entry 為 dirty 的程式. 當 interpreter 執行 bytecode 修改 reference 的時候會跑一次 write barrier. JIT Compiler 也會在更新 reference 的時候去跑 write barrier.

generational garbage collection 的好處是每個 generation 可以依照特性使用不同的 GC algorithm. base 在 young generation 的特性 (空間小, 物件很快被回收), 比較快速的 garbage collector 做為 minor GC 可以浪費一些空間以較快速度處理 young generation. 而空間使用上較有效率的 garbage collector 則拿來處理佔了大部分  java heap 的 old generation. 這個 GC 比較慢但由於 full GC 的頻率比較少所以影響有限.

The Young Generation

Young Generation 分為三塊: The eden & two survivor spaces

  • The eden. 大部分新物件都放這, 在 minor GC 後 eden space 通常是空的
  • The two survivor spaces. 活過至少一次 minor GC 的物件就會進入 survivor space. 但還有機會在進入 old generation 前變成 unreachable.
在 minor GC 的時候, 不保證 survivor space 有足夠的空間放物件, 如果 overflow 了, 物件會直接放到 old generation. 這會造成 old generation 因為存放短暫存活的物件而變大造成效能問題. 當這現象持續發生導致 old generation 滿了, 就會導致 minor GC 之後就開始 full GC. 

Fast Allocation

object allocator 與 garbage collector 的操做緊密相關, garbage collector 必須記錄回收過的 free space 在哪裡, allocator 必須去找 heap 裡面能滿足 allocation request 的 free space 在哪. 回收 eden space 的 garbage collector 有個優勢就是每次回收之後 eden space 就是空的. 這讓 eden space 的 allocation 透過 bump-the-pointer 的技巧能很有效率. bump-the-pointer 這個技巧是去追蹤最後一個 allocated object, 把位置放在 top, 當新的 allocation request 來的時候, allocator 只需要檢查 eden 的 top 與 end 是否能滿足該 allocation request, 滿足的話, top 就會被調整到新 allocate object 的尾巴.

在 multi-threaded 的環境中, bump-the-pointer 為了做對需要 lock, 如果只有一個 lock (global lock) 會有效能問題, HotSpot VM 使用 Thread-Local Allocation Buffers (TLABs) 的技巧, 在 eden space 中切一小塊給每個 thread 專屬的能夠 allocate 的 buffer 來提升 multi-thread 時候 allocation 的效能. 由於每個 TLAB 都只會有一個 thread 在使用, 就不需要 lock 的機制就能執行 bump-the-pointer 的動作. 然而當 TLAB 滿了, 一個 thread 需要新的 TLAB 就需要取得 lock 取得 TLAB 才安全. 在 HotSpot VM 中 new Object() 通常會有大約 10 行 assembly code, 就是為了清空 eden space 並啟動 fast allocation.

Garbage Collectors: Spoiled for Choice

HotSpot VM 有四種 garbage collector, 不同 garbage collector 針對不同型態的 application 設計.

The Serial GC

old generation 由 sliding compacting mark-sweep 也就是 mark-compact garbage collector 管理,  Serial GC 負責 young generation. minor 與 full GC 會在 stop-the-world 時進行, 整個 application 在 GC 結束前動作都會停下來.
mark-compact collector 會先指出哪些物件還活在 old generation, 把它們轉到 heap 一開始的地方, 在 heap 的尾端騰出連續的空間. 這讓之後的物件使用快速的 bump-the-pointer 的技巧從 young generation promote 到 old generation.
大部分沒有 "暫停時間一定要很短" 需求或是在 client machine 執行的 application 選用 Serial GC. 好處是只有一個 virtual processor 會執行 garbage collection 的工作. 在今天的機器上, Serial GC 可以有效率只要很短的暫停時間處理 100MB 以下的 heap size.
另一個使用 Serial GC 的 case 是在一台機器上跑多個 JVM. (JVM 的數量比 processor 還多) 在這樣的環境上, 即使 garbage collection 會跑更久, 一個 JVM 最好只跑在一個 processor 來減少對其他 JVM 的影響.

The Parallel GC: Throughput Matters!

很多 java application 執行的環境有許多的實體記憶體與 processor. 理想上 garbage collector 會善用這些資源執行 GC 的工作. 為了加強跑在這種 server style 機器上 application 的 throughput, HotSpot VM 提供 Parallel GC, 也叫做 Throughput GC.
Parallel GC 的行為跟 Serial GC 一樣, 將物件從 young generation 搬到 old generation 的時候要 stop-the-world. 但是 minor 與 full GC 可以使用 available processors 同時進行.
有需求 throughput 要好且 pause time 不能低 (stop-the-world 的時間不能久)的 application, 如果跑在多個 processor 的機器上可以使用 Parallel GC. 

The Mostly-Concurrent GC: Latency Matters!

對某些 application 而言, 一段一段 throughput 的重要性比不上快速的 response time. 在 stop-the-world 模式下 application 在 GC 完成前會無法服務. minor GC 的影響不大, 但在 full GC 的時候, 即使頻率不高, 但每次的 stop-the-world 都可能造成下次更久的 stop-the-world. 
為了解決這個問題, HotSpot VM 提供 Mostly-Concurrent GC, 也叫做 Concurrent Mark-Sweep GC (CMS). 這個機制對 minor GC 的處理不變, 但對於 old generation, CMS 用一個演算方式讓大部分 GC 的工作同時進行, 每次 GC 只需要兩次短暫的暫停.
CMS 的流程是: 
  1. 開始於一個短暫的暫停 (整個 JVM stop-the-world), 稱做 initial mark, 指出可立即從 old generation 外部碰觸到的物件. 
  2. 在 concurrent marking phase 的時候標記這些物件是可接觸到的. 
  3. 由於在標記的時候可能 reference fields 就被更新了 (CMS 正在 iterate 的 object tree 會更新), 所以不保證所有的物件在 concurrent marking phase 結束後都會被標記完. 為了解決這個問題, application 會再暫停一下子, 稱為 remark pause, CMS 在這個 phase 中會再訪問一次 concurrent marking phase 中有更動過的物件並給予最終狀態. 為了追蹤物件狀態這時候會重複使用 card table.
  4. 由於 remark pause比 initial mark 更重, 為了增加效率被設計成同步進行 (parallelized)
  5. 為了更進一步減少 remark pause的工作, HotSpot VM 引進 concurrent pre-cleaning phase. 這個 phase 在 concurrent marking phase 後以及 remark pause之前發生. 這時候會做些在 remark pause 的事情, 例如重新拜訪一次被更動的物件, 如此在 remark pause 的時候工作就不會那麼多. (有些需要 finalize mark 的物件在 pre-cleaning 的時候就處理過了)
  6. 在 remark pause 的最後, 保證所有 java heap 裡面的 live object 都會被標記.
    整體來說, 比起 Parallel GC, CMS 的工作量比較多. 這是因為 garbage collector 要做的事情就是那麼多, 為了增加效率與減少 pause time 只好增加工作量.
  7. 登記完 old generation 的所有物件後, 最後一個 phase 就是 concurrent sweeping phase, 也就是把垃圾物件清除. 與 Serial GC & Parallel GC 不同的是, CMS 清除垃圾物件後, free space 並不是連續的. CMS 會去記錄 free space 位置在哪. 這導致在 old generation 定位物件的成本比使用 bump-the-pointer 技巧的 Serial GC or Parallel GC 來的昂貴.
  8. 這也對 minor GC 帶來額外的負擔因為 old generation 的 allocation 會發生在物件在 minor GC 被 promote 的時候.
  9. CMS 另一個 Serial GC 與 Parallel GC 沒有的缺點是: CMS 需要更多的 Java heap.
    1. 這是因為 concurrent marking cycle 延續的時間比 stop-the-world 還長, 而且只有在 sweeping phase 的時候 space 才真正的被回收.
    2. 在 marking phase 的時候 application 還是可以繼續執行, 所以也可以繼續在 old generation 佔用新的空間, 而這些空間又只會在 sweeping phase 的時候減少
    3. 此外, 儘管 GC 保證在 marking phase 的時候 identify 所有的 live object, 但這不保證它會 identify 所有的 garbage 物件. 物件有可能在 marking phase 的時候被偵測到是 garbage 但也可能不會, 如果沒被偵測到就只能等下次的 marking phase.
    4. 在 GC 時候沒被偵測到的 garbage object 稱作 floating garbage (漂浮垃圾)
  10. 最後, 由於缺乏 compaction 造成的碎片問題 (fragmentation issues) 可能使 garbage collector 無法盡可能有效使用 free space. 如果 old generation 在回收有效空間之前就滿了, 如同 Serial GC 與 Parallel GC, CMS 會回到 stop-the-world compackting 的階段.

The Garbage-First GC: CMS Replacement

  1. G1 使用跟其他 GC 機制不同的 Java heap layout.
  2. 它把 Java heap 分成多組同樣大小的 chunk, 稱作 region.
  3. 雖然 G1 是 generational, 但沒有實體上區分 young 與 old generation. 而是每個 generation 有多個 regions. (這些 regions 不一定是連續的). 這讓 G1 能有彈性的調整 young generation 的 size.
  4. G1 是在從 region 中評估 surviving object 的時候回收資源, 大部分時候 G1 回收 young regions (也就是 G1 的 young generation) 就像 minor GC 一樣.
  5. G1 也會周期性的進行 concurrent marking cycles 來 identify 哪些 non-young regions 是空的或大部分是空的 (empty: 看文章應該是說沒空間 promote 物件到此). 這些 region 屬於回收起來 CP 值最高的 regions. 這些 region 會被丟進排程進行回收. 

HotSpot VM JIT Compilers

Compilation Policy

  1. 由於 JIT 沒有足夠的時間 compile 每個 method, 所有的程式最初都從 interpreter 開始執行, 一旦 code 變夠 hot 了就會被 schedule 去 compile.
  2. 這在 HotSpot VM 裡面是由兩個 counter 來控制: invocation counter 與 backedge counter.
    1. invocation counter: 每次 method 被呼叫就 +1
    2. backedge counter: 每次 control flow 從較高的 bytecode index 執行到較低的 bytecode index 就 +1.
      backedge counter 用來偵測有 loop 的 method 來提前 compile.
  3. counter 無論何時增加都會檢查一個 threadhold, 如果超過 threadhold, interpreter 就會要求 compile 這個 method. 
  4. invocation counter 的 threadhold 名字叫 CompileThreadhold.
  5. backedge counter 的 threadhold 公式比較複雜:
    CompileThreadhold * OnStackReplacePercentage / 100
  6. compilation request 會被 enqueue 進一個一或多個 compiler threads 監控著的 list, 當 compiler thread 不忙的時候就會將 compilation request 從 list 移掉然後開始進行 compilation.
  7. 通常 interpreter 不會等 compiler 完成, 而是 reset invocation counter 後繼續執行 interpreter 裡面的 method. 當 compilation 完成後, compile 過的 method 會被關連到那個 method, 讓下一個 method caller 使用那個 compiled code. (當 interpreter 偵測到一個 method 是 compiled 過的, 下次就不會執行 interpreter 裡面的 code 而會 dispatch 到 compiled 的 code 去執行)
  8. 然而, 當一個 java code 是跑一個很長而且只跑一次的 loop, 比方說一個程式會執行無限迴圈直到 process 關掉, 這時候 method 雖然被偵測到要 compile, 但是 compile 結束卻不會被使用, 而是一直使用 interpreter 的 code, 因為正常情況 long-running loop method 要等到下次 method 被呼叫的時候才會使用 compiled 過的程式. 這時候 HotSpot VM 可以執行一個特別的 compiles call "On Stack Replacement" 或稱為 "OSRs" 可以解決這個問題.
  9. 當 backedge counter 溢位 (猜是超過 threadhold 的意思), interpreter 會發出 compile request, 這會開始執行 backedge 的 bytecode 而不是 method 第一次被呼叫的 bytecode. 這導致 compile 完產生出來的程式會把 interpreter frame 當成 input 來用並執行. 這作法使 long-running loop 可以使用 compiled 過的 bytecode. 
  10. On Stack Replacement 因此而得名: compile 完產生的 code 拿一個 interpreter frame 來執行.

Client JIT Compiler Oberview

  1. HotSpot VM Client JIT Compiler 目標是快速啟動與 compilation.
  2. 為了給予 Java 合理的啟動效能避免太多的複雜度, Client JIT Compiler 被當成一個快速簡單的 code generator 程式啟動, 
  3. 這概念上跟 interpreter 有點像因為它會針對不同種類的 bytecode 建立 template 並維護一個像 interpreter frame 的 stack layout.
  4. Java 1.4 之前只支援 field accessor 的 inline.
  5. Java 1.4 之後支援 inline method, 還支援 Class Hieracrchy Analysis 與 deoptimization.
  6. Java 6 後為了提升效能有很多改變, 其中一個是把 intermediate representation 改為 SSA style representation, 且原本 simple local register allocator 改為 linear scan register allocator.
  7. 此外 Java 6 支援數字可以跨 block. 在 x86 支援使用 SSE 增加浮點數的操作, 提升浮點運算效能提升.

Server SIT Compiler Overview

  1. HotSpot VM Server JIT Compiler 目標是好的效能, throughput 與優化
  2. 比起 Client JIT Compiler, Server JIT Compiler 在 compile 的時候需要更多的 space 與時間.
  3. Server JIT Compiler 會更積極作 inline, 通常也帶來更大的 method, 更大的 method 要花更長的時間 compile.

SSA - Program Dependence Graph

program dependence graph

  1. Server JIT Compiler 的 intermediate representation (IR) 是一個 SSA like IR, 但它使用了不同的 representing 流程處理方式, 稱為 "program dependence graph"
  2. 為了能夠積極的重新排列 operation 與 global value 來減少多餘的計算, "program dependence graph" 會試著在 operation 進行中截取最小的 constraint, 
  3. "program dependence graph"擁有 rick type system 而能夠取得所有 java type 執行的細節, 再投入之後的優化中. 

methodDataOop

  1. interpreter 在執行的時候會蒐集 profile information 給 Server JIT Compiler 使用.
  2. 執行 bytecode 的時候, 一個 method 如果執行了足夠的次數, interpreter 就會建立一個 methodDataOop 的物件來裝這個 method 的 profile information, 資料包含哪些 type 被呼叫以及呼叫幾次.
  3. 所有 control flow 的 bytecode 也都會紀錄自己有多常被使用以及後續的動作.
  4. 所有這些資訊都被 Server JIT Compiler 用來基於 common types 與執行頻率找到 inline 的機會, 這會驅動 block layout 與 register allocation.

uncommon trap

  1. 所有 Java bytecode 的 JIT Compiler 都需要面對 unloaded 或 uninitialized classes 的可能性. 
  2. 在 Server JIT compiler 包含 unresolved constant pool entries 時,Server JIT compiler 會把 unloaded 或 uninitialized classes 當成碰不到的路徑 (unrechable path)
  3. 遇到這種情況, Server JIT compiler 會替該 bytecode 送出一個 uncommon trap 並停止透過該 method parse 的 path.
  4. 一個 uncommon trap 是一個給 HotSpot VM 的 request, 這個 request 會要求 HotSpot VM deoptimize 目前的 compiled method, 然後將程式退回到 constant entry pool 能被解析與 process 為止, 退回的部分改在  interpreter 執行. 
  5. HotSpot VM 收到 uncommon trap 後, 原本 compiled 過的 code 會被丟掉, 程式會回到 interpreter 執行, 直到下次 compile 又被觸發為止, 下一次執行時由於 path 都被 resolved 過, 所以下次可以正常的 compile.
  6. uncommon traps 也會用來處理沒碰觸到的 path, 所以 compiler 沒有 generate code 給沒有用過的 method. 這樣 code 會比較小而且更直接的程式通常更好優化.
  7. Server JIT Compiler 還會拿 uncommon trap 來做更樂觀的優化, 當 Server JIT Compiler 認為某些行只會執行一次, 就會放個 dynamic check 去檢查, 如果檢查結果認為是 uncommon 就送出 uncommon trap 然後在 interpreter 執行這段程式. 當 uncommon trap 發生次數夠多, 就會被當成其實不是 uncommon, 就不再懷疑這可能是 uncommon 而直接使用 generated code. 這個情況發生在: 當 call site 從一個 profile information 只會看到一個類別, Server JIT Compiler 就會假設只看到一個類別, 然後加入檢查. 如果大部分情況看到一個類別, 少部分情況看到別的類別, Server JIT Compiler 就不會送出 uncommon trap 而是使用 compiled code.

optimizations on loops

  1. Server JIT Compiler 在 loop 上有很多優化, 透過拆解 iteration 做到如 loop unswitching, loop unrolling, range check elimination 等優化.
  2. 拆解 iteration 的方式是把一個 loop 轉成三個: preloop, main loop, post loop.
  3. 這個 idea 是去計算每個 loop 的邊界使 main loop 不用做任何的 range check, 只有 preloop 與 post loop 需要做 range check.
  4. 大部分情況 preloop 與 post loop 都只需要跑少數幾次, 甚至很多情況下都可以被完全淘汰.
  5. 當 range check 被移除, 就可以被 unroll. Loop unroll 就是讓 loop body 變單純, 讓被 loop 的程式攤開來不要跑 loop, 減少 loop 的次數.
  6. unroll 之後可以減少執行 loop 的成本, 通常可以更簡化 loop 的內容, 使 loop 可以在更短時間做更多事. 有時候幾次的 unroll 後可以讓 loop 完全消失.
  7. Loop unrolling 可以導致開始另一個優化叫做 superword, superword 是 vectorization 的一種形式. 
  8. Unrolling 可以在 loop body 建立一些平行的 operations, 如果那些 operations 照順序放在記憶體, 就可以被蒐集成 vector 裡面的 operations. 使一個指令就能在同樣的一段時間內執行多個 operation. 
一旦所有的高階優化都完成, IR (intermediate representation) 會被轉換成 machine dependent 的格式. 這時候可以享有所有特別的 instructions 與 processor address mode 的好處.

compound interest

Reference

Description

不想工作, 最好投資可以每年幫你賺 200萬, 就是在不工作的情況下還是可以賺200萬.
怎麼做呢? 我先做個網頁, 用複利的方式去替你算每年的目標是多少.
二三十年的規畫下來, 定好目標之後, 要怎麼執行則是另一個 story..

Android APP: GameVolumn

Reference

Description

昨晚才做完 BrightImage, 想說再做一個甚麼東西, 想到上次有朋友提個需求:
由於每次進到不同的 APP 都會需要調整不同的音量, 重點是每次調整的音量都變來變去.
有時是鈴聲, 有時是電話, 有時鬧鐘, 有時音樂...etc.
他希望有個 APP 可以只調整遊戲音量. 所以就做了 GameVolumn.

Development Note

  1. 首先查一下調整音量那個左右滑動的叫甚麼 => 結果叫做 SeekBar, OK.
  2. 希望 Activity 開起之後只出線 SeekBar 就好 => 把 Activity 設定為透明. OK.
  3. 看一下怎麼讀取跟設定音量 => 使用 AudioManager
  4. 設定好想要 beep 一下 => 使用 ToneGenerator
  5. 設定好, 當 user 想要離開, 點 SeekBar 以外的地方就關掉 Activity => 用 onTouchEvent
  6. 結束上傳
有點粗糙, 不過功能算是完成了. 

Fight with Bitmap OOM problem.

Reference

Description

為了買大樂透不想每次都開 APP 顯示 QR Code 很慢, 所以做了 MAX Brightness 這個 Widget.
做好之後, 的確可以方便的把亮度調最大再開 QR Code 的 screenshot, 掃完圖後也可以方便的恢復原本亮度.
朋友聽完這個 Widget 之後就問: 幹嘛不乾脆做一個功能直接把圖秀出來亮度最亮就好?
真是一語驚醒夢中人... 於是就開始做 BrightImage 這個 APP.
需求就很簡單: 選一張圖顯示, 顯示的時候調最亮, 選過一次就不用重選.

Development Note

整個 APP 開發起來很簡單, 比較麻煩的是 Bitmap 這個東西.
由於功能包含可以選圖呈現, 所以測試的時候就也亂選圖, 結果選到一張手機拍的照片後程式就 crash 了... Orz
看 log 發現是 OutOfMemory, 引發 OutOfMemory 的圖有 3264*1826 這麼大.
程式的寫法是

Bitmap bitmap = BitmapFactory.decodeFile(picturePath);
imageView.setImageBitmap(bitmap);

這樣的寫法, 小圖沒問題, 大圖就爆了. 就算沒爆, 只要橫放&直放手機或者重複幾次就會爆.
後來上網查, 發現要 recycle, 所以我就在 onPause 去把 Bitmap recycle 掉, 結果沒那麼簡單. recycle 的時機很重要, 有時候 recycle 了, 底層還沒 recycle 完, 有時是 imageView 預設的圖在佔空間.
試了多種組合都沒用. 由於每天大概只做半小時吧, 加上最近忙家裡有事, 這個問題擺了幾週..

這幾週中有試到一種方式不會 OutOfMemory.

Display display = getWindowManager().getDefaultDisplay();
imageView.setImageBitmap(Bitmap.createScaledBitmap(BitmapFactory.decodeFile(picturePath),display.getWidth(), display.getHeight(), true));

這樣的寫法, 雖然圖片在螢幕翻轉的時候圖片會被 scale 成怪怪的樣子, 但不會 OOM.
當時沒發現原因, 直到今天才突然想到: 啊! 其實存在 imageView 裡面的 Bitmap size 比較小所以不會 OOM.
加 log 去檢查發現果然沒錯 => 雖然很簡單的規則卻過很久才想到 Orz

一開始想說是不是要自己算寬高, 可是這樣程式會比較雜. 想說難道沒有相關的 API 嗎?
看 Bitmap 有個 API: Bitmap#getScaledWidth(targetDensity:int), 就去查 Density 甚麼意思.
然後發現 Bitmap#getScaledWidth(targetDensity:int) 似乎可以達到把圖縮小的目的.
重要的是不用自己去算寬高, 感覺太雜了.

一開始縮小圖片的方式只有

bitmap.getScaledHeight(DisplayMetrics.DENSITY_LOW);

一開始測試沒問題, 結果上傳到 Google Play 後自己測試又遇到 OOM!!
看 log 發現原來原本的 density 是 240, DENSITY_LOW 是 120, 用 DENSITY_LOW 去縮小圖卻沒用, 因為圖片還是太大, 還是會遇到 OOM.
想是不是還是得自己算寬高的時候, 突然想到一個很瞎的方式: catch OOM 然後縮小 targetDensity 來縮小記憶體用量.

for ( int i = 1; i < 10; i++ ) {
  int targetDensity = bitmap.getDensity() / i;
  try {
    int h = bitmap.getScaledHeight(DisplayMetrics.DENSITY_LOW);
    int w = bitmap.getScaledWidth(targetDensity);
    Log.i(getClass().getName(), "reduce density to " + targetDensity);
    imageView.setImageBitmap(Bitmap.createScaledBitmap(bitmap, w, h, true));
    break;
  } catch (OutOfMemoryError e) {
    Log.w(getClass().getName(), "OOM when targetDensity:" + targetDensity);
  }    
}

結果這個方法有用. 想想應該不會有情況是把圖片的精細度縮 1024 倍還看不到, 就設定只要測10次就好了.

PS. 這次有加可以選圖的功能, 上網查詢後發現意外的簡單.

private void selectPicture() {
  Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  startActivityForResult(i, RESULT_LOAD_IMAGE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
    Uri selectedImage = data.getData();
    String[] filePathColumn = { MediaStore.Images.Media.DATA };
  
    Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
    cursor.moveToFirst();
  
    int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
    String picturePath = cursor.getString(columnIndex);
    cursor.close();
                       
    loadImage();
  }
}

First Android APP - MAX Brightness Widget

Reference

Description

現在買大樂透很方便, 可以用 APP 產生 QR Code 去給樂透彩店掃描, 省紙.
可是我手機很爛, 光是要把 APP 打開到 QR Code 那頁就很慢了.
所以就把 QR Code 那頁切下來存在手機, 想說要買大樂透的時候就打開圖片給店員掃.
店員拿去, 掃不到, 原來是螢幕不夠亮. 要把螢幕亮度調到最大才能掃到.

好吧, 後來每次買大樂透都要先把亮度調到最大再開圖, 掃完後再恢復.
太麻煩了, 就想 DIY 一個 APP 來做到 "點一下亮度最大, 再點一下亮度恢復" 的功能.
可能 Google Play 已經有人做好了吧...? Don't care. XD

做好後, 就放到 Github 跟 Google Play, 還花了 US$25, 有趣.

Development Notes

開發部分, 其實就是看 Android Developer Training 就行.
比較麻煩的是原本 Widget 點下會有個 Activity 跑出來, 不符需求.
而且在 Widget 無法拿到 Window instance, 沒有 Window instance 就不能調亮度.
在 Activity 就可以拿 Window instance, 但我又不想跑另一個 Activity 起來. @@

最後在 stackoverflow 網站上找到高手, 原來這時候只要把 Activity 用 style 設定為隱形就好了.
在 Activity 的 onCreate 最後發個 message 把自己結束掉就完成.

最後花錢放到 Google Play, 就發現整個東西很粗糙, 檔案 size 也很大.
Anyway...

PS

現在使用這個 Widget 是可以把亮度調高沒錯, 但還是要把圖打開.
決定要再做一個是可以設定要 show 哪張圖, 打開 APP 後就直接亮度最亮 & show 圖.
另外手機太爛, 沒空間了, 要可以放到 SD 卡才對.. (Widget 不能放 SD card)

split big log file

Description

一個 log 檔 3G 怎麼看? 不想裝工具, 就只能把檔案切小

Dependencies

JDK7
apache commons io

Codes

    public static void main(String[] params) throws IOException {
        String bigPath = "D:\\logfiles\\biglogfile.log";
        File f = new File(bigPath);
        try (BufferedReader r = new BufferedReader(new FileReader(f))) {
            System.out.println(f.exists());
            int fileCnt = 0;
            List lines = new ArrayList();
            String line;
            while ((line = r.readLine()) != null) {
                if ( lines.size() == 10000 ) {
                    File fileToWrite = new File(f.getParentFile(), f.getName() + "." + fileCnt++);
                    FileUtils.writeLines(fileToWrite, lines);
                    System.out.println("write file:" + fileToWrite);
                    lines.clear();
                }
                lines.add(line);
            }
            File fileToWrite = new File(f.getParentFile(), f.getName() + "." + fileCnt++);
            FileUtils.writeLines(fileToWrite, lines);
            System.out.println("write file:" + fileToWrite);
        } catch (Throwable ex) {
            ex.printStackTrace();
        } 
    }

Effective Java Builder Pattern Code Generator

Description

Sometimes I need to follow Effective Java to write Builder Pattern.
There are attributes, getters, Java Doc and a builder.
I wrote a Java based code generator before, I change to JavaScript based codes and put in GitHub.

Reference


python find pid by listen port

Codes

import sys
import subprocess
import shlex
import re

def find_pid_by_listen_port(port):
    if sys.platform == 'win32':
        output = subprocess.check_output('netstat -a -n -o', universal_newlines=True)
        match = re.search('.*:{0} +.* +.+ +[0-9]+'.format(port),output)
        if match:
            return shlex.split(match.group(0))[-1]
        else:
            return None
    else:
        raise Exception('not support platform ' + sys.platform)
    
pid = find_pid_by_listen_port(9160)
print(pid)

python download Cassandra and start up in Windows

Description

This program will kill existing Cassandra before deploy new one.

Codes

import os
import shutil
import sys
import math
import urllib.request
import subprocess
import shlex
import signal
import time
import re
from datetime import date

def http_download(download_url, download_file):
    with urllib.request.urlopen(download_url) as f:
        with open(download_file,'wb') as target:
            filesize = int(f.getheader('Content-Length'))
            wrotesize = 0
            while True:
                if wrotesize == int(filesize):
                    break
                wrotesize += target.write(f.read(1024))
                download_percent = math.ceil((wrotesize/filesize)*100)
                print('\rDownload {0} to {1} ... {2}%'.format(download_url,download_file,download_percent),end='')
    print()

def find_pid_by_listen_port(port):
    if sys.platform == 'win32':
        output = subprocess.check_output('netstat -a -n -o', universal_newlines=True)
        match = re.search('.*:{0} +.* +.+ +[0-9]+'.format(port),output)
        if match:
            return shlex.split(match.group(0))[-1]
        else:
            return None
    else:
        raise Exception('not support platform ' + sys.platform)
    
    
download_url = 'http://ftp.tc.edu.tw/pub/Apache/cassandra/2.0.4/apache-cassandra-2.0.4-bin.tar.gz'
download_file = 'd:/apache-cassandra-2.0.4-bin.tar.gz'
unzip_folder = 'd:/deploy/work/{0}/apache-cassandra-2.0.4-bin'.format(date.today().isoformat())
execute_folder = unzip_folder + '/apache-cassandra-2.0.4'
execute_path = execute_folder + '/bin/cassandra.bat'
executable = 'start cmd /c ' + execute_path
executable_argv = shlex.split(executable)
pid = find_pid_by_listen_port(9160)
if pid:
    print('kill process id',pid)
    os.kill(int(pid),signal.SIGTERM)
if os.path.exists('d:/deploy'):
    print('Delete d:/deploy')
    shutil.rmtree('d:/deploy')
http_download(download_url, download_file)
os.makedirs(unzip_folder)
print('unzip {0} to {1}'.format(download_file, unzip_folder))
shutil.unpack_archive(download_file, unzip_folder)
print('Execute',execute_path)
subprocess.Popen(executable_argv,shell=True)
    

python download text percent progress

Description

想學一下 Maven download 時候的 text percent progress

Codes

import sys
import math
import urllib.request

def http_download(download_url, download_file):
    with urllib.request.urlopen(download_url) as f:
        with open(download_file,'wb') as target:
            filesize = int(f.getheader('Content-Length'))
            wrotesize = 0
            while True:
                if wrotesize == int(filesize):
                    break
                wrotesize += target.write(f.read(1024))
                download_percent = math.ceil((wrotesize/filesize)*100)
                print('\rDownload {0} to {1} ... {2}%'.format(download_url,download_file,download_percent),end='')

if len(sys.argv) < 3:
    print('Usage: {0} download_url download_file'.format(sys.argv[0]))
else:    
    download_url = sys.argv[1]
    download_file = sys.argv[2]
    http_download(download_url, download_file)

使用方式

c:\workspace_python>python test.py http://ftp.tc.edu.tw/pub/Apache/cassandra/2.0.4/apache-cassandra-2.0.4-bin.tar.gz d:/apache-cassandra-2.0.4-bin.tar.gz

python http download cassandra

Reference

http://www.apache.org/dyn/closer.cgi?path=/cassandra/2.0.4/apache-cassandra-2.0.4-bin.tar.gz
http://docs.python.org/3/library/urllib.request.html#module-urllib.request

Description

用 urllib 下載 cassandra 到本地

Codes

簡單的方式

import urllib.request

with urllib.request.urlopen('http://ftp.tc.edu.tw/pub/Apache/cassandra/2.0.4/apache-cassandra-2.0.4-bin.tar.gz') as f:
    with open('d:/apache-cassandra-2.0.4-bin.tar.gz','wb') as target:
        target.write(f.read())

如果想要看進度可以這樣

import urllib.request

with urllib.request.urlopen('http://ftp.tc.edu.tw/pub/Apache/cassandra/2.0.4/apache-cassandra-2.0.4-bin.tar.gz') as f:
    with open('d:/apache-cassandra-2.0.4-bin.tar.gz','wb') as target:
        filesize = f.getheader('Content-Length')
        wrotesize = 0
        while True:
            if wrotesize == int(filesize):
                break
            wrotesize += target.write(f.read(1024))
            print('download...',wrotesize,'of',filesize)
            

想從公司內的 Maven download 還需要認證

import urllib.request

pwdmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
pwdmgr.add_password(None,'http://mvn.company.site','myid','mypw')
auth_handler = urllib.request.HTTPBasicAuthHandler(pwdmgr)
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
with urllib.request.urlopen('http://mvn.company.site/downloadfile.zip') as f:
    with open('d:/downloadfile.zip','wb') as target:
        target.write(f.read())

後記

還不太清楚 HTTPBasicAuthHandler 的 realm 要怎麼指定正確, 
使用 Maven response 的 realm 也不行.
只好先用 HTTPPasswordMgrWithDefaultRealm 了...

python compress/decompress file

Reference

http://docs.python.org/3/library/shutil.html#module-shutil

Description

把 d:/nginx-1.4.3 壓縮到 d:/ziptarget/test.zip
再把 d:/ziptarget/test.zip 解壓縮到 d:/unziptarget/

Codes (適合 python 3.2 以上)

import shutil

unzipfile_folder = 'd:/unziptarget'
archive_name = 'd:/ziptarget/test'
root_dir = 'd:/nginx-1.4.3'
archive_path = shutil.make_archive(archive_name, 'zip', root_dir)
shutil.unpack_archive(archive_path, unzipfile_folder)

python 的 iterator 與 (神奇的) yield (iterator generator)

Reference

http://docs.python.org/3/tutorial/classes.html#iterators
http://docs.python.org/3/tutorial/classes.html#generators

Iterator

這段程式

for i in range(5):
    print(i)

執行結果為

0
1
2
3
4

可以執行的原因是 range 回傳了一個可以 iterate 的物件,
讓 python 可以呼叫 "next" 取得下一個值.
如果想要自己實作一個能用 for iterate 的物件時,
只需要實作 __iter__ 與 __next__ 這兩個 function,
for 迴圈執行時 python 會自動執行這兩個 function.
我們只要在 __next__ 呼教時維持好物建的狀態即可.
通知 for 迴圈停止的方式是丟出一個 StopIteration 的 error


class NumberIterator:
    def __init__(self, max):
        self.max = max
        self.current = 0
    def __iter__(self):
        print('iter is called. max=',self.max)
        return self
    def __next__(self):
        print('next is called, current=', self.current, ', max=', self.max)
        if ( self.current == self.max ):
            raise StopIteration
        self.current += 1
        return self.current
        
it = NumberIterator(5)
# "for" statement will call iter() of NumberIterator
# so 'iter is called. max=' will be printed
for i in it:
    print(i) #print 1 ~ 5

# there is no "for" statement, so 'iter is called. max=' won't be printed
it = NumberIterator(4)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it)) #StopIteration

執行結果

d:\workspace_python>python test.py
iter is called. max= 5
next is called, current= 0 , max= 5
1
next is called, current= 1 , max= 5
2
next is called, current= 2 , max= 5
3
next is called, current= 3 , max= 5
4
next is called, current= 4 , max= 5
5
next is called, current= 5 , max= 5
next is called, current= 0 , max= 4
1
next is called, current= 1 , max= 4
2
next is called, current= 2 , max= 4
3
next is called, current= 3 , max= 4
4
next is called, current= 4 , max= 4
Traceback (most recent call last):
  File "test.py", line 27, in 
    print(next(it)) #StopIteration
  File "test.py", line 11, in __next__
    raise StopIteration
StopIteration

yield (iterator generator)

神奇的 yield, 這個 statement 是出現在 function 裡面, 
讓程式先回傳, 但 function 內的狀態保持不變, 
等下次程式的 __next__ 被呼叫時, 程式會從 yield 之後的碼繼續執行.
def test_yield():
    i = 0
    print('before the first yield',i)
    yield i
    print('after the first yield',i)
    i += 1
    print('before the second yield',i)
    yield i
    print('after the second yield',i)
    i += 1
    for i in range(3):
        print('before yield in for stmt')
        yield i**2
        print('after yield in for stmt')
    
for i in test_yield():
    print(i)

執行結果
d:\workspace_python>python test.py
before the first yield 0
0
after the first yield 0
before the second yield 1
1
after the second yield 1
before yield in for stmt
0
after yield in for stmt
before yield in for stmt
1
after yield in for stmt
before yield in for stmt
4
after yield in for stmt

JDK6 to JDK7 => Basic IO

Reference

讀寫小檔案

如果檔案比較小可以使用 Files 提供的 read/write.
package test;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;

public class TestIO {

    public static void main(String[] params) throws IOException {
        Charset cs = Charset.forName("utf-8");
        Path path = Paths.get("d:/", "test.txt");
        List lines = Arrays.asList("line1", "line2", "line3");
        Files.write(path, lines, cs, StandardOpenOption.TRUNCATE_EXISTING);
        List readLines = Files.readAllLines(path, cs);
        System.out.println(readLines);
    }

}

讀寫大檔案

如果是要讀寫大的檔案, 可以用 Files 的 newBufferedReader/newBufferedWriter/newByteChannel
package test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.UUID;

public class TestIO {

    public static void main(String[] params) throws IOException {
        Charset cs = Charset.forName("utf-8");
        Path path = Paths.get("d:/", "test.txt");
        int wLineCnt = 25000000;
        try (
            BufferedWriter writer = Files.newBufferedWriter(
                      path, cs, StandardOpenOption.TRUNCATE_EXISTING)) {
            for ( int i = 0; i < wLineCnt; i++ ) {
                writer.write(i + ":" + UUID.randomUUID().toString() + "\n");
            }
            writer.flush();            
        }
        try (BufferedReader reader = Files.newBufferedReader(path, cs)) {
            int rLineCnt = 0;
            String line = null;
            while ((line = reader.readLine()) != null) {
                int num = Integer.valueOf(line.substring(0, line.indexOf(":")));
                if ( rLineCnt++ != num ) {
                    throw new RuntimeException(
                                "not equal! " + num + "," + rLineCnt);
                }
            }
            if ( rLineCnt != wLineCnt ) {
                throw new RuntimeException(
                           "not equal! " + wLineCnt + "," + rLineCnt);
            }
        }
        System.out.println("done");
    }

}

取根目錄

package test;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;

public class TestIO {

    public static void main(String[] params) throws IOException {
        for (Path path: FileSystems.getDefault().getRootDirectories()) {
            System.out.println(path); //列出 C:/ & D:/ ..etc
        }
    }
    
}


建資料夾

package test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestIO {

    public static void main(String[] params) throws IOException {
        Files.createDirectories(Paths.get("d:/test1/test2/test3/test4"));
    }
    
}

Iterate資料夾

JDK 7提供新的 interface 讓你 iterate 資料夾: DirectoryStream.
在 API 上說這個 interface 比較 scalable, 可以用來 iterate 很大的 folder.
上網查有人說就的 File#listFiles 會把所有的 folder 載入會佔用比較多的記憶體.
要省記憶體應該要讓資料夾下有上百萬或上千萬個資料夾或檔案才有感覺吧.
我測一百萬個資料夾也沒甚麼差.
後來覺得關鍵是 API 這句話:
The iterator is weakly consistent. It is thread safe but does not freeze the directory while iterating, so it may (or may not) reflect updates to the directory that occur after the DirectoryStream is created.
在資料夾下檔案或資料夾數量很大時, 原本的 File 仍須要給 API client 精準的資訊.
scale 愈大, "精準"的成本就愈大.
DirectoryStream 選擇一開始就告訴你它不準, 所以贏在起跑點, 可以用較少的資源取資料.
等你真的要處理某個檔案再動作就行了.
我覺得這種 interface 更有利於硬碟在遠端的 case. 硬碟不在本機,
要求 File 物件保證整個資料夾資訊要對太難, 這時候 DirectoryStream 就可以處理很好.
要注意 DirectoryStream 用完要關掉, 可以在 try () 裡面宣告.

package test;

import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class TestIO {

    public static void main(String[] params) throws IOException {
        long start = System.currentTimeMillis();
        createFolders();
        System.out.println("spend " + (System.currentTimeMillis() - start) 
                    + " millis to create folders");
        
        start = System.currentTimeMillis();
        System.out.println(listFoldersNIO(Paths.get("d:/test")));
        System.out.println("spend " + (System.currentTimeMillis() - start) 
                    + " millis to list folders (NIO)");

        start = System.currentTimeMillis();
        System.out.println(listFoldersIO(Paths.get("d:/test").toFile()));
        System.out.println("spend " + (System.currentTimeMillis() - start) 
                    + " millis to list folders (IO)");
    }

    private static void createFolders() throws IOException {
        for ( int i = 0; i < 1000000; i++ ) {
            Files.createDirectories(Paths.get("d:/test/test" + i));
            System.out.println(i);
        }
        System.out.println("done");
    }

    private static int listFoldersIO(File folder) {
        int cnt = 0;
        for ( String child: folder.list() ) {
            new File(child);
            cnt++;
        }
        return cnt;
    }
    
    private static int listFoldersNIO(Path path) throws IOException {
        int cnt = 0;
        try ( DirectoryStream dirStream = Files.newDirectoryStream(path) ) {
            for ( Path child: dirStream ) {
                child.toFile();
                cnt++;
            }
        }
        return cnt;
    }
    
}

指定 pattern 找資料夾下的檔名

JDK 7提供新的概念叫 Glob, 蠻好理解的.
不過其中有個 ** 的語法說明 "works like * but crosses directory boundaries" 讓我有錯誤的期望以為只要 Files.newDirectoryStream(p, "**/*.{java,jar}") 就可以列舉全部的檔案, 測試結果不行..
可能要用 Files#walkFileTree. 我還沒看 walkFileTree 有甚麼特殊的地方,
但 API 沒特別好用的話我自己作就好了為什麼要提供 walkFileTree 呢...?
(在下面的段落練習了 walkFileTree, 比起自己寫用 walkFileTree 簡潔多了 XD)

package test;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class TestIO {

    public static void main(String[] params) throws IOException {
        listAll(Paths.get("d:\\Isaac"), "*.{java,jar}");
    }

    private static void listAll(Path p, String globPattern) throws IOException {
        try (
            DirectoryStream<Path> s = Files.newDirectoryStream(p, globPattern)
            ) {
            for ( Path c: s ) {
                System.out.println(c);
            }
        }
        try (DirectoryStream<Path> s = Files.newDirectoryStream(p)) {
            for ( Path c: s ) {
                if ( Files.isDirectory(c, LinkOption.NOFOLLOW_LINKS) ) {
                    listAll(c, globPattern);
                }
            }
        }
    }
    
}

搜尋 activemq source code 中有關鍵字 "Queue" 的檔案

自己實作 DirectoryStream.Filter 的時候不能方便的使用 glob 有點可惜..

package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;

public class TestIO {

    public static void main(String[] params) throws IOException {
        listAll(Paths.get("d:\\activemq-parent-5.8.0-source-release"), "Queue");
    }

    private static void listAll(Path p, final String containTxt) 
    throws IOException {
        DirectoryStream.Filter<Path> f = new DirectoryStream.Filter<Path>() {
            @Override
            public boolean accept(Path entry) throws IOException {
                if ( Files.isDirectory(entry, LinkOption.NOFOLLOW_LINKS) 
                               || !entry.toString().endsWith(".java") ) {
                    return false;
                }
                try (BufferedReader reader = Files.newBufferedReader(
                                      entry, Charset.forName("UTF8"))) {
                    String line = null;
                    while ( (line = reader.readLine()) != null ) {
                        if ( line.contains(containTxt) ) {
                            return true;
                        }
                    }
                }
                return false;
            }
        };
        try (DirectoryStream<Path> s = Files.newDirectoryStream(p, f)) {
            for ( Path c: s ) {
                System.out.println("match " + c);
            }
        }
        try (DirectoryStream<Path> s = Files.newDirectoryStream(p)) {
            for ( Path c: s ) {
                if ( Files.isDirectory(c, LinkOption.NOFOLLOW_LINKS) ) {
                    listAll(c, containTxt);
                }
            }
        }
    }
    
}

用 Files#walkFileTree + FileVisitor 列出所有資料夾下的 java 與 jar 檔

package test;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class TestIO {

    public static void main(String[] params) throws IOException {
        Path p = Paths.get("d:/Isaac");
        FileVisitor<Path> f = new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file,BasicFileAttributes attrs)                throws IOException {
                String filepath = file.toString();
                if ( filepath.endsWith(".java") || filepath.endsWith(".jar") ) {
                    System.out.println(filepath);
                }
                return super.visitFile(file, attrs);
            }
        };
        Files.walkFileTree(p, f);
    }

}

搜尋 activemq source code 中有關鍵字 "Queue" 的檔案 (用 glob)

前面才提到 "自己實作 FileVisitor 的話不方便用 glob 很可惜" 而已, 就發現其實有提供. 那就是 PathMatcher.
package test;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class TestIO {

    public static void main(String[] params) throws Throwable {
        final PathMatcher matcher = 
                   FileSystems.getDefault().getPathMatcher("glob:*.{java,jar}");
        Files.walkFileTree(Paths.get("d:/Isaac"), new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult visitFile(Path file,
                    BasicFileAttributes attrs) throws IOException {
                if ( matcher.matches(file.getFileName()) ) {
                    System.out.println("match " + file);
                }
                return super.visitFile(file, attrs);
            }
        });
    }

}

監聽一個 path 是否備新增/刪除/修改

package test;

import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestIO {

    public static void main(String[] params) throws Throwable {
        ExecutorService e = Executors.newSingleThreadExecutor();
        Path path = Paths.get("d:/Isaac");
        try (final WatchService w = FileSystems.getDefault().newWatchService()) {
            path.register(w, StandardWatchEventKinds.ENTRY_CREATE, 
                             StandardWatchEventKinds.ENTRY_DELETE, 
                             StandardWatchEventKinds.ENTRY_MODIFY);
            e.execute(new Runnable(){
                @Override
                public void run() {
                    while (true) {
                        try {
                            WatchKey k = w.take();
                            for ( WatchEvent<?> e: k.pollEvents() ) {
                                @SuppressWarnings("unchecked")
                                WatchEvent<Path> pathEvent = (WatchEvent<Path>)e;
                                Path path = pathEvent.context();
                                System.out.println(pathEvent.kind() + ":"  path);
                                if (!k.reset()) {
                                    break;
                                }
                            }
                        } catch (Throwable ex) {
                            ex.printStackTrace();
                        }                    
                    }
                }});
            TimeUnit.DAYS.sleep(1);
        }   
    }
}


JDK6 to JDK7 => API Enhancements

Description

打算把系統的執行環境從 JDK6 升級到 JDK7, 整理一下有哪些可用.
比較期待的部分是語法, NIO2, G1. 這頁是整理語法的部分.

Reference

Points

  1. Strings in switch Statements
    package test;
    
    public class TestStringSwitch {
    
     public static void main(String[] params) {
      inJDK7("abc");
      inJDK6("abc");
     }
     
     private static void inJDK7(String txt) {
      switch (txt) {
      case "a":
       System.out.println("a");
       break;
      case "b":
       System.out.println("b");
       break;
      case "abc":
       System.out.println("abc");
       break;
      default:
        System.out.println("not match");
      }
     }
     
     private static void inJDK6(String txt) {
      if ( "a".equals(txt) ) {
       System.out.println("a");
      } else if ( "b".equals(txt) ) {
       System.out.println("b");
      } else if ( "abc".equals(txt) ) {
       System.out.println("abc");
      } else {
       System.out.println("not match");
      }
     }
     
     
    }
    
  2. Type Inference for Generic Instance Creation
    package test;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class TestGenericDeclare {
    
      public static void main(String[] params) {
        // JDK6
        Map> map0 = new HashMap>();
        
        // JDK7
        Map> map1 = new HashMap<>();
      }
      
    }
    
  3. The try-with-resources Statement
    試用後可以看到這個語法可以讓程式簡化很多.
    package test;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.Reader;
    
    public class TestAutoClosable {
    
        public static void main(String[] params) throws IOException {
            File file1 = new File("d:/test.txt");
            File file2 = new File("d:/test2.txt");
            inJDK6(file1,file2);
            inJDK7(file1,file2);
        }
    
        private static void inJDK7(File file1, File file2) throws IOException {
            try (
                FileReader fileReader1 = new FileReader(file1);
                FileReader fileReader2 = new FileReader(file2);
                BufferedReader reader1 = new MyBufferedReader("7 reader1", fileReader1);
                BufferedReader reader2 = new MyBufferedReader("7 reader2", fileReader2);
            ) {
                System.out.println(reader1.readLine());
                System.out.println(reader2.readLine());
            }
        }
        
        private static void inJDK6(File file1, File file2) {
            BufferedReader reader1 = null;
            BufferedReader reader2 = null;
            try {
                reader1 = new MyBufferedReader("JDK6 style reader1", new FileReader(file1));
                reader2 = new MyBufferedReader("JDK6 style reader2", new FileReader(file2));
                System.out.println(reader1.readLine());
                System.out.println(reader2.readLine());
            } catch(Throwable ex) {
                ex.printStackTrace();
            } finally {
                if ( reader1 != null ) {
                    try {
                        reader1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if ( reader2 != null ) {
                    try {
                        reader2.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        
        private static class MyBufferedReader extends BufferedReader {
            private final String name;
            public MyBufferedReader(String name, Reader reader) {
                super(reader);
                this.name = name;
            }
            @Override
            public void close() throws IOException {
                super.close();
                System.out.println("close " + name);
            }
        }
        
    }
    
  4. Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking
    有時對於不特別處理的 checked exception 動作也只是往外丟, 在 JDK7 之前需要在 method 上特別註明這個 checked exception, 但 JDK7 之後, 沒有特別需要的, 直接往外丟的 exception 就算沒有在 method 上宣告, compiler 也不會判你錯.
    package test;
    
    public class TestHandleMoreThanOneException {
    
        public static void main(String[] params) throws Exception {
            rethrowEx("3");
        }
    
        private static void rethrowEx(String exNum) throws Ex1,Ex2 {
            try {
                throwEx(exNum);
            } catch (Ex1 | Ex2 e) {
                throw e;
            } catch (Exception e) {
                throw e;
            }
        }
        
        private static void throwEx(String exNum) throws Ex1, Ex2 {
            switch (exNum) {
            case "1":
                throw new Ex1();
            case "2":
                throw new Ex2();
            default:
                System.out.println("not match");
            }
        }
        
        public static class Ex1 extends Exception {}
        public static class Ex2 extends Exception {}
        
    }
    


python 處理 json

Description

現在很常用 json 當成儲存資料的格式, 用 java 的話需要導入 library.
python 則是內建 json 的 module, 只要操作原生的資料結構就行了. 很方便.

Reference

Codes

>>> import json
>>> m = {'a':1,'b':[1,2,3,4,5,6]}
>>> with open('d:/test.txt','w') as f:
        json.dump(m,f)

        
>>> with open('d:/test.txt') as f:
        f.readline()

        
'{"a": 1, "b": [1, 2, 3, 4, 5, 6]}'
>>> with open('d:/test.txt') as f:
        x = json.load(f)

        
>>> x
{'a': 1, 'b': [1, 2, 3, 4, 5, 6]}
>>> 

File content

{"a": 1, "b": [1, 2, 3, 4, 5, 6]}

python 的 main method

之前看 python 文章好像都沒提到怎麼寫 main method, 似乎這件事情很自然, 不過我卻不知道.
看了 6.1.1. Executing modules as scripts 裡面有寫
"the code in the module will be executed, just as if you imported it, but with the __name__ set to "__main__". That means that by adding this code at the end of your module:"
if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))
試著作一次

  1. C:\Python33\say.py
    def say(words):
            print(words)
    
    if __name__ == '__main__':
            import sys
            say(sys.argv[1])
            print(__name__,sys.argv[0])
    
    
  2. Output
    C:\Python33>python.exe say.py hello
    hello
    __main__ say.py

這樣就可以了, 很簡單..

處理正在 iterate 的 dictionary


今天練習 Python 的時候發現 5.6. Looping Techniques 有個提示
"To change a sequence you are iterating over while inside the loop (for example to duplicate certain items), it is recommended that you first make a copy. Looping over a sequence does not implicitly make a copy."

照著文件上的程式輸入
>>> words = ['a','b','cc']
>>> for w in words:
    if len(w)>1:
        print('insert',w)
        words.insert(0,w)

執行之後就出現
insert cc
insert cc
insert cc
insert cc
insert cc
insert cc
insert cc
insertTraceback (most recent call last):
  File "<pyshell#208>", line 3, in <module>
    print('insert',w)
  File "C:\Python33\lib\idlelib\PyShell.py", line 1318, in write
    return self.shell.write(s, self.tags)
KeyboardInterrupt

要 Ctrl+C 才能停下來.
這時候檢查一下哪打錯了, 原來我打
for w in words:

但文件上是
for w in words[:]:

修正之後果然就正常了.
原來 words[:] 是 words 的 copy.
試一下
>>> words is words
True
>>> words is words[:]
False
果然沒錯.

在 Java 也是一樣, 如果要改變正在 iterate 的 list 也會有問題
package test;

import java.util.Arrays;
import java.util.List;

public class TestIter {

    public static void main(String[] params) {
        List words = Arrays.asList("a","b","cc");
        for (String w: words) {
            if (w.length() > 1) {
                System.out.println("insert " + w);
                words.add(w);
            }
        }
    }
    
}
會報錯
insert cc
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(Unknown Source)
    at java.util.AbstractList.add(Unknown Source)
    at test.TestIter.main(TestIter.java:13)

要改成
package test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TestIter {

    public static void main(String[] params) {
        List words = Arrays.asList("a","b","cc");
        List copyWords = new ArrayList(words);
        for (String w: words) {
            if (w.length() > 1) {
                System.out.println("insert " + w);
                copyWords.add(0,w);
            }
        }
        System.out.println(words);
        System.out.println(copyWords);
    }
    
}

寫了 Python 跟 Java 這兩段程式, 感覺有微妙的差異...

巴斯卡三角形 in Python

Source Code

def print_pascal(row_cnt):
    pascal = []
    for row in range(row_cnt):
        cols = []
        for col in range(row+1):
            if row == 0:
                cols.append(1)
            elif col == 0 or col == row:
                cols.append(1)
            else:
                val = pascal[row-1][col-1]+pascal[row-1][col];
                cols.append(val)
        pascal.append(cols)
    for row in pascal:
        for i in range(row_cnt-len(row)):
            print('%5s' %'',end='')
        for col in row:
            print('%5d' %col,end='%5s' %'')
        print()

Usage


>>> print_pascal(6)
                             1     
                        1         1     
                   1         2         1     
              1         3         3         1     
         1         4         6         4         1     
    1         5        10        10         5         1  

Nginx Practice in Windows - 6 simple proxy server

Description

使用 nginx 的一個主因是要支援 web application 放在別台機器上, 將相關的 request 導過去.

Reference

Practice

  1. prepare files
    D:\nginx-1.4.3\nginx.exe
    D:\nginx-1.4.3\conf\nginx.conf
    D:\nginx-1.4.3\testhtml\testhtml.html
    D:\nginx-1.4.3\internal\internal.html
    D:\nginx-1.4.3\internal\service\service.html
    D:\nginx-1.4.3\internal\service\serviceA\serviceA.html
    
  2. setup nginx.conf
    (注意 proxy_pass 的 127.0.0.1 要對應到 port 12345 的 server_name 127.0.0.1, 在練習的時候發現如果設定成 proxy_pass http://localhost:12345 那 nginx 就很容易遇到錯誤: 68 upstream timed out (10060: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond) while connecting to upstream, client: 127.0.0.1, server: 127.0.0.1, request: "GET /service/serviceA/serviceA7.html HTTP/1.1)
        server {
            listen       8080;
            server_name  127.0.0.1;
            root         testhtml;
            location / {
                index testhtml.html;
            }
            location /service/ {
                proxy_pass http://127.0.0.1:12345;
            }
        } 
        server {
            listen       12345;
            server_name  127.0.0.1;
            root         internal;
            location / {
                index  internal.html;
            }        
            location /service/ {
                index  service.html;
            }
        } 
    
  3. run "nginx -s reload"
  4. run "telnet 127.0.0.1 8080"
  5. send request
    GET /service/ HTTP/1.1
    Host: 127.0.0.1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  6. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Wed, 30 Oct 2013 23:25:39 GMT
    Content-Type: text/html
    Content-Length: 20
    Connection: close
    Last-Modified: Wed, 30 Oct 2013 23:25:34 GMT
    ETag: "5271956e-14"
    Accept-Ranges: bytes
    
    This is service.html
  7. 如果把 debug log 打開, 就是把 nginx.conf 上的"#error_log  logs/error.log  debug;" 注解拿掉, 就可以看到 error.log 裡面出現下面這些 log, 看似 nginx 在 proxy_pass 的時候改寫了 http request 然後重新發一次. (不過說起來 proxy 本來就是在作這種事情就是了)

    2013/10/31 07:25:39 [debug] 4348#6716: *139 http script copy: "Host: "
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http script var: "127.0.0.1:12345"
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http script copy: "
    "
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http script copy: "Connection: close
    "
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http script copy: ""
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http script copy: ""
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http proxy header: "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36"
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http proxy header: "Accept: */*"
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http proxy header: "Accept-Encoding: gzip,deflate,sdch"
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http proxy header: "Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4"
    2013/10/31 07:25:39 [debug] 4348#6716: *139 http proxy header:
    "GET /service/ HTTP/1.0
    Host: 127.0.0.1:12345
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    "
  8. send request
    GET /service/serviceA/serviceA.html HTTP/1.1
    Host: 127.0.0.1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  9. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Wed, 30 Oct 2013 23:33:12 GMT
    Content-Type: text/html
    Content-Length: 21
    Connection: close
    Last-Modified: Wed, 30 Oct 2013 23:33:08 GMT
    ETag: "52719734-15"
    Accept-Ranges: bytes
    
    This is serviceA.html

Nginx Practice in Windows - 5 static content

Description

使用 nginx 主要目的之一就是要存放靜態檔如 html 或圖片

Reference

Practice

設定讓 "GET /" 的 request 首頁導向 testhtml/data/www/index.html

  1. prepare folders
    D:\nginx-1.4.3\nginx.exe
    D:\nginx-1.4.3\conf\nginx.conf
    D:\nginx-1.4.3\testhtml\data\www\index.html
    D:\nginx-1.4.3\testhtml\data\images
    
  2. edit index.html
    Hello!, This is testhtml\data\www\index.html
  3. setup nginx.conf
        server {
            listen       8080;
            server_name  127.0.0.1;
            location / {
                root   testhtml/data/www;
                index  index.html;
            }
        } 
    
  4. run "nginx -s reload"
  5. run "telnet 127.0.0.1 8080"
  6. send request
    GET / HTTP/1.1
    Host: server1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  7. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Sun, 27 Oct 2013 23:22:23 GMT
    Content-Type: text/html
    Content-Length: 44
    Last-Modified: Sun, 27 Oct 2013 23:22:19 GMT
    Connection: close
    ETag: "526da02b-2c"
    Accept-Ranges: bytes
    
    Hello!, This is testhtml\data\www\index.html
    

設定去取 "/" 以外的檔案

  1. prepare files
    D:\nginx-1.4.3\testhtml\data\www\www.html
    D:\nginx-1.4.3\testhtml\data\data.html
    D:\nginx-1.4.3\conf\nginx.conf
    
  2. edit data.html
    Hello!, This is data.html 
  3. edit www.html
    Hello!, This is www.html
    
  4. setup nginx.conf
        server {
            listen       8080;
            server_name  127.0.0.1;
            location / {
                root   testhtml/data/www;
                index  www.html;
            }
            location /data/ {
                root   testhtml;
                index  data.html;
            }
        } 
    
  5. run "nginx -s reload"
  6. run "telnet 127.0.0.1 8080"
  7. send request
    GET / HTTP/1.1
    Host: server1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  8. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Mon, 28 Oct 2013 15:33:05 GMT
    Content-Type: text/html
    Content-Length: 24
    Last-Modified: Sun, 27 Oct 2013 23:32:37 GMT
    Connection: close
    ETag: "526da295-18"
    Accept-Ranges: bytes
    
    Hello!, This is www.html
  9. send request (這個 request 是去要 /data/ 的 resource, 其實也可以對上 "/" 的 location, 不過 nginx 會選擇 prefix 對應最長的 location)
    GET /data/ HTTP/1.1
    Host: server1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  10. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Mon, 28 Oct 2013 15:49:13 GMT
    Content-Type: text/html
    Content-Length: 25
    Last-Modified: Sun, 27 Oct 2013 23:32:15 GMT
    Connection: close
    ETag: "526da27f-19"
    Accept-Ranges: bytes
    
    Hello!, This is data.html
    
  11. send request (這個 request 是去找 "/" 的 index.html)
    GET /data/www/www.html HTTP/1.1
    Host: server1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  12. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Mon, 28 Oct 2013 15:50:23 GMT
    Content-Type: text/html
    Content-Length: 24
    Last-Modified: Sun, 27 Oct 2013 23:32:37 GMT
    Connection: close
    ETag: "526da295-18"
    Accept-Ranges: bytes
    
    Hello!, This is www.html

如果多個 location 使用同樣的 root, 只要在 server 區塊中設定 root 即可

  1. prepare files
    D:\nginx-1.4.3\testhtml\testhtml.html
    D:\nginx-1.4.3\testhtml\data\data.html
    D:\nginx-1.4.3\conf\nginx.conf
    
  2. edit data.html
    Hello!, This is data.html 
  3. edit testhtml.html
    Hello!, This is testhtml.html
    
  4. setup nginx.conf
        server {
            listen       8080;
            server_name  127.0.0.1;
            root   testhtml;
            location / {
                index  testhtml.html;
            }
            location /data/ {
                index  data.html;
            }
        } 
    
  5. run "nginx -s reload"
  6. run "telnet 127.0.0.1 8080"
  7. send request
    GET / HTTP/1.1
    Host: server1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  8. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Mon, 28 Oct 2013 23:51:46 GMT
    Content-Type: text/html
    Content-Length: 29
    Last-Modified: Mon, 28 Oct 2013 23:45:17 GMT
    Connection: close
    ETag: "526ef70d-1d"
    Accept-Ranges: bytes
    
    Hello!, This is testhtml.html
  9. send request
    GET /data/ HTTP/1.1
    Host: server1:8080
    Connection: close
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Accept: */*
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
    
  10. get response
    HTTP/1.1 200 OK
    Server: nginx/1.4.3
    Date: Mon, 28 Oct 2013 23:52:29 GMT
    Content-Type: text/html
    Content-Length: 25
    Last-Modified: Sun, 27 Oct 2013 23:32:15 GMT
    Connection: close
    ETag: "526da27f-19"
    Accept-Ranges: bytes
    
    Hello!, This is data.html

Lessons Learned While Benchmarking vLLM with GPU

Recently, I benchmarked vLLM on a GPU to better understand how much throughput can realistically be expected in an LLM serving setup. One ...