Upgrade to Pro — share decks privately, control downloads, hide ads and more …

標準ライブラリのコードリーディングで学ぶGo

yu81
August 10, 2017

 標準ライブラリのコードリーディングで学ぶGo

eureka Meetup #06 -Go! Go! Go!- での発表資料です。
https://eure.connpass.com/event/61778/

yu81

August 10, 2017
Tweet

More Decks by yu81

Other Decks in Technology

Transcript

  1. 自己紹介 { " p r o f i l e

    " : { " n a m e " : " 臼井 友亮" , " n a m e _ e n " : " Y u s u k e U s u i " , " s i t e " : " h t t p : / / y u 8 1 . g i t h u b . i o / " , " p r o g r a m m i n g _ l a n g u a g e " : [ " G o l a n g " , " P H P " , " S h e l l S c r i p t " , " P y t h o n " , " C # " , " C " , " C + + " , " a n d m o r e . . . " ] , " c a r r e r " : [ " 株式会社システム計画研究所( 2 0 0 8 / 0 4 - 2 0 1 2 / 0 3 ) " , " ハンズラボ株式会社/ 株式会社東急ハンズ( 2 0 1 2 / 0 4 - 2 0 1 6 / 0 5 ) " , " 株式会社エウレカ( 2 0 1 6 / 0 6 - ) " ] } }
  2. 可読性の高い標準ライブラリ 読める、 読めるぞ……! 言語仕様の小ささから、 コー ドゴルフのような書き方ははしにく い。 黒魔術的コー ドも書きにくい。 そのようにしようとすれば不自然でパフォー

    マンスが出ないコ ー ドになることが多いからか。 標準ライブラリも可読性とパフォー マンスのバランスに優れ、 コー ドリー ディングしやすい。 ちなみに G oには三項演算子すらない。 三項演算子 ネスト 複雑 読めない つらい 集合演算系関数はあってもいいのではと思うこともある 今日は、 c o m p r e s s / g z i p ちょっと読んでみましょう。
  3. gzip とは RFC 1952によって定義されているデー タ圧縮フォー マットのひとつ M acや L inux

    系 OS だと標準でだいたい使える(gz ip コマンドもある) 圧縮方法は L Z77+ハフマン符号の組み合わせのdeflat e法が主に用い られる。
  4. コー ドリー ディングするためのツー ル 普段使っているエディタの機能で。 G oの構文解析をしてくれるプラグインがあるとなおよし。 個人的には (I nt

    elli J IDEA + G o p lu gin) or G ogland (どちらも機能は 同じ) int er face の実装を満たす構造体へのジャンプも可能。 捗る。 V im……?いえ、 知らない子ですね。 s our cegr ap h.com はブラウザベー スだが定義ジャンプなども可能 なのでこれでも十分。 デフォルトのカラー スキー ムが好き。
  5. compress/gzip#pkg‑examples https://golang.or g/p kg/compr ess/gz ip/#p kg‑ex amp les からスタ

    ー ト 適宜省略します(定型的なエラー 処理など)
  6. compress/gzip#pkg‑examples p a c k a g e m a

    i n i m p o r t ( " b y t e s " " c o m p r e s s / g z i p " " f m t " " i o " " l o g " " o s " " t i m e " ) f u n c m a i n ( ) { v a r b u f b y t e s . B u f f e r z w : = g z i p . N e w W r i t e r ( & b u f ) / / S e t t i n g t h e H e a d e r f i e l d s i s o p t i o n a l . z w . N a m e = " a - n e w - h o p e . t x t " z w . C o m m e n t = " a n e p i c s p a c e o p e r a b y G e o r g e L u c a s " z w . M o d T i m e = t i m e . D a t e ( 1 9 7 7 , t i m e . M a y , 2 5 , 0 , 0 , 0 , 0 , t i m e . U T C ) _ , e r r : = z w . W r i t e ( [ ] b y t e ( " A l o n g t i m e a g o i n a g a l a x y f a r , f a r a w a y . . . " ) ) i f e r r ! = n i l { l o g . F a t a l ( e r r ) } i f e r r : = z w . C l o s e ( ) ; e r r ! = n i l { l o g . F a t a l ( e r r ) } z r , e r r : = g z i p . N e w R e a d e r ( & b u f ) i f e r r ! = n i l { l o g . F a t a l ( e r r ) } f m t . P r i n t f ( " N a m e : % s \ n C o m m e n t : % s \ n M o d T i m e : % s \ n \ n " , z r . N a m e , z r . C o m m e n t , z r . M o d T i m e . U T C ( ) ) i f _ , e r r : = i o . C o p y ( o s . S t d o u t , z r ) ; e r r ! = n i l { l o g . F a t a l ( e r r ) } i f e r r : = z r . C l o s e ( ) ; e r r ! = n i l { l o g . F a t a l ( e r r ) } }
  7. compress/gzip#pkg‑examples v a r b u f b y t

    e s . B u f f e r z w : = g z i p . N e w W r i t e r ( & b u f ) N ew${stru ct_name} という名前の関数で、 他言語のclassの constructorやファクトリメソッドと同等のことを行うイディオム。 gz ip.W r it er 構造体を書き込み先領域の指定と共に生成 g z i p . N e w W r i t e r ( w i o . W r i t e r ) のパラメー タの i o . W r i t e r は インター フェー スであり、W r it eメソッドを実装している構造体であ ればこれを満たすことが出来る。 o s . F i l e (os.S t din, os.S t dout もこの型) 書き込み先指定を W r it er の初期化時に行うことが出来る。 バッファ系の構造体、 ファイル、 標準入出力などをよしな に差し替えられる。
  8. compress/gzip#pkg‑examples _ , e r r : = z w

    . W r i t e ( [ ] b y t e ( " A l o n g t i m e a g o . . . " ) ) S our cegr ap h だとここから。 https://s our cegr ap h.com/git hu b.com/golang/go/‑/blob/sr c/comp r ess/gz ip W r i t e r . W r i t e ( p [ ] b y t e ) でバイト列を指定した先に書き込む。 W r i t e r . W r i t e の実装を追っていく。
  9. compress/gzip.W riter.W rite() / / W r i t e

    w r i t e s a c o m p r e s s e d f o r m o f p t o t h e u n d e r l y i n g i o . W r i t e r . T h e / / c o m p r e s s e d b y t e s a r e n o t n e c e s s a r i l y f l u s h e d u n t i l t h e W r i t e r i s c l o s e d . f u n c ( z * W r i t e r ) W r i t e ( p [ ] b y t e ) ( i n t , e r r o r ) { i f z . e r r ! = n i l { r e t u r n 0 , z . e r r } v a r n i n t / / W r i t e t h e G Z I P h e a d e r l a z i l y . i f ! z . w r o t e H e a d e r { z . w r o t e H e a d e r = t r u e z . b u f = [ 1 0 ] b y t e { 0 : g z i p I D 1 , 1 : g z i p I D 2 , 2 : g z i p D e f l a t e } i f z . E x t r a ! = n i l { z . b u f [ 3 ] | = 0 x 0 4 } i f z . N a m e ! = " " { z . b u f [ 3 ] | = 0 x 0 8 } i f z . C o m m e n t ! = " " { z . b u f [ 3 ] | = 0 x 1 0 } i f z . M o d T i m e . A f t e r ( t i m e . U n i x ( 0 , 0 ) ) { / / S e c t i o n 2 . 3 . 1 , t h e z e r o v a l u e f o r M T I M E m e a n s t h a t t h e / / m o d i f i e d t i m e i s n o t s e t . l e . P u t U i n t 3 2 ( z . b u f [ 4 : 8 ] , u i n t 3 2 ( z . M o d T i m e . U n i x ( ) ) ) } i f z . l e v e l = = B e s t C o m p r e s s i o n { z . b u f [ 8 ] = 2 } e l s e i f z . l e v e l = = B e s t S p e e d { z . b u f [ 8 ] = 4 } z . b u f [ 9 ] = z . O S n , z . e r r = z . w . W r i t e ( z . b u f [ : 1 0 ] ) i f z . e r r ! = n i l { r e t u r n n , z . e r r } i f z . E x t r a ! = n i l { z . e r r = z . w r i t e B y t e s ( z . E x t r a ) i f z . e r r ! = n i l { r e t u r n n , z . e r r } } i f z . N a m e ! = " " { z . e r r = z . w r i t e S t r i n g ( z . N a m e ) i f z . e r r ! = n i l { r e t u r n n , z . e r r } } i f z . C o m m e n t ! = " " { z . e r r = z . w r i t e S t r i n g ( z . C o m m e n t ) i f z . e r r ! = n i l { r e t u r n n , z . e r r } } i f z . c o m p r e s s o r = = n i l { z . c o m p r e s s o r , _ = f l a t e . N e w W r i t e r ( z . w , z . l e v e l ) } } z . s i z e + = u i n t 3 2 ( l e n ( p ) ) z . d i g e s t = c r c 3 2 . U p d a t e ( z . d i g e s t , c r c 3 2 . I E E E T a b l e , p ) n , z . e r r = z . c o m p r e s s o r . W r i t e ( p ) r e t u r n n , z . e r r }
  10. compress/gzip.W riter.W rite() z . b u f = [

    1 0 ] b y t e { 0 : g z i p I D 1 , 1 : g z i p I D 2 , 2 : g z i p D e f l a t e } マジックナンバー2バイトと、 圧縮方法にdeflat eを指定するバイト を書き込む。 先頭10バイトがgz ip 形式の必須ヘッダ部分であるので、 逐次代入し ていく。 今一度、S ee RFC 1952 https://www.iet f.or g/r fc/r fc1952.txt i f z . E x t r a ! = n i l { z . b u f [ 3 ] | = 0 x 0 4 } . . .
  11. compress/gzip.W riter.W rite() z . s i z e +

    = u i n t 3 2 ( l e n ( p ) ) z . d i g e s t = c r c 3 2 . U p d a t e ( z . d i g e s t , c r c 3 2 . I E E E T a b l e , p ) n , z . e r r = z . c o m p r e s s o r . W r i t e ( p ) r e t u r n n , z . e r r 新しく書き込むサイズを W r it er が保持している値に加えて状態を更 新する。 これまでに書き込まれた圧縮対象の CRC 32と新しく書き込む対象の デー タから、CRC 32を更新する。 CRC 32: 巡回冗長検査。 エラー 検出符号。 z.compr ess or は * f l a t e . W r i t e r 型であるので、 f l a t e . W r i t e r . c o m p r e s s ( d a t a [ ] b y t e ) , f l a t e . c o m p r e s s o r . w r i t e ( b [ ] b y t e ) と追っていく。
  12. compress/flate.compressor.write() f u n c ( d * c o

    m p r e s s o r ) w r i t e ( b [ ] b y t e ) ( n i n t , e r r e r r o r ) { . . . n = l e n ( b ) f o r l e n ( b ) > 0 { d . s t e p ( d ) b = b [ d . f i l l ( d , b ) : ] i f d . e r r ! = n i l { r e t u r n 0 , d . e r r } } r e t u r n n , n i l } d.st ep, d.fill の実装がどうなっているか気になります!
  13. type compress/flate.compressor struct t y p e c o m

    p r e s s o r s t r u c t { . . . / / c o m p r e s s i o n a l g o r i t h m f i l l f u n c ( * c o m p r e s s o r , [ ] b y t e ) i n t / / c o p y d a t a t o w i n d o w s t e p f u n c ( * c o m p r e s s o r ) / / p r o c e s s w i n d o w . . . fill, st ep はcompr ess or 型のポインタを受け取って処理をする関数 として、 compr ess or 型のメソッドとして宣言されている。 これらが保持する関数を変更することによって、 圧縮アルゴリズム を変更することが出来る。 この変更は、 f l a t e . c o m p r e s s o r . i n i t ( w i o . W r i t e r , l e v e l i n t ) にて行われている。
  14. compress/flate.compressor.init() f u n c ( d * c o

    m p r e s s o r ) i n i t ( w i o . W r i t e r , l e v e l i n t ) ( e r r e r r o r ) { d . w = n e w H u f f m a n B i t W r i t e r ( w ) s w i t c h { c a s e l e v e l = = N o C o m p r e s s i o n : d . w i n d o w = m a k e ( [ ] b y t e , m a x S t o r e B l o c k S i z e ) d . f i l l = ( * c o m p r e s s o r ) . f i l l S t o r e d . s t e p = ( * c o m p r e s s o r ) . s t o r e c a s e l e v e l = = H u f f m a n O n l y : d . w i n d o w = m a k e ( [ ] b y t e , m a x S t o r e B l o c k S i z e ) d . f i l l = ( * c o m p r e s s o r ) . f i l l S t o r e d . s t e p = ( * c o m p r e s s o r ) . s t o r e H u f f . . . } 圧縮アルゴリズムのレベルの指定により、 処理の実体とな る f l a t e . c o m p r e s s o r のメソッドを差し替えている。 ハフマン符号化(st or e H u ff)の処理を見てみる。
  15. compress/flate.compressor.store H uff() f u n c ( d *

    c o m p r e s s o r ) s t o r e H u f f ( ) { i f d . w i n d o w E n d < l e n ( d . w i n d o w ) & & ! d . s y n c | | d . w i n d o w E n d = = 0 { r e t u r n } d . w . w r i t e B l o c k H u f f ( f a l s e , d . w i n d o w [ : d . w i n d o w E n d ] ) d . e r r = d . w . e r r d . w i n d o w E n d = 0 } 処理の実体はさらに c o m p r e s s / f l a t e . h u f f m a n B i t W r i t e r . w r i t e B l o c k H u f f ( e o f b o o l , i n p u t [ ] b y t e ) にて。 d . w i n d o w [ : d . w i n d o w E n d ] w indow の先頭からw indowE ndまでの 長さのバイト列に対して処理を行っている。
  16. compress/flate.huffman B itW riter.write B l ock H uff() /

    / w r i t e B l o c k H u f f e n c o d e s a b l o c k o f b y t e s a s e i t h e r / / H u f f m a n e n c o d e d l i t e r a l s o r u n c o m p r e s s e d b y t e s i f t h e / / r e s u l t s o n l y g a i n s v e r y l i t t l e f r o m c o m p r e s s i o n . f u n c ( w * h u f f m a n B i t W r i t e r ) w r i t e B l o c k H u f f ( e o f b o o l , i n p u t [ ] b y t e ) { i f w . e r r ! = n i l { r e t u r n } / / C l e a r h i s t o g r a m f o r i : = r a n g e w . l i t e r a l F r e q { w . l i t e r a l F r e q [ i ] = 0 } / / A d d e v e r y t h i n g a s l i t e r a l s h i s t o g r a m ( i n p u t , w . l i t e r a l F r e q ) w . l i t e r a l F r e q [ e n d B l o c k M a r k e r ] = 1 c o n s t n u m L i t e r a l s = e n d B l o c k M a r k e r + 1 c o n s t n u m O f f s e t s = 1 w . l i t e r a l E n c o d i n g . g e n e r a t e ( w . l i t e r a l F r e q , 1 5 ) / / F i g u r e o u t s m a l l e s t c o d e . / / A l w a y s u s e d y n a m i c H u f f m a n o r S t o r e v a r n u m C o d e g e n s i n t / / G e n e r a t e c o d e g e n a n d c o d e g e n F r e q u e n c i e s , w h i c h i n d i c a t e s h o w t o e n c o d e / / t h e l i t e r a l E n c o d i n g a n d t h e o f f s e t E n c o d i n g . w . g e n e r a t e C o d e g e n ( n u m L i t e r a l s , n u m O f f s e t s , w . l i t e r a l E n c o d i n g , h u f f O f f s e t ) w . c o d e g e n E n c o d i n g . g e n e r a t e ( w . c o d e g e n F r e q [ : ] , 7 ) s i z e , n u m C o d e g e n s : = w . d y n a m i c S i z e ( w . l i t e r a l E n c o d i n g , h u f f O f f s e t , 0 ) / / S t o r e b y t e s , i f w e d o n ' t g e t a r e a s o n a b l e i m p r o v e m e n t . i f s s i z e , s t o r a b l e : = w . s t o r e d S i z e ( i n p u t ) ; s t o r a b l e & & s s i z e < ( s i z e + s i z e > > 4 ) { w . w r i t e S t o r e d H e a d e r ( l e n ( i n p u t ) , e o f ) w . w r i t e B y t e s ( i n p u t ) r e t u r n } / / H u f f m a n . w . w r i t e D y n a m i c H e a d e r ( n u m L i t e r a l s , n u m O f f s e t s , n u m C o d e g e n s , e o f ) e n c o d i n g : = w . l i t e r a l E n c o d i n g . c o d e s [ : 2 5 7 ] n : = w . n b y t e s f o r _ , t : = r a n g e i n p u t { / / B i t w r i t i n g i n l i n e d , ~ 3 0 % s p e e d u p c : = e n c o d i n g [ t ] w . b i t s | = u i n t 6 4 ( c . c o d e ) < < w . n b i t s w . n b i t s + = u i n t ( c . l e n ) i f w . n b i t s < 4 8 { c o n t i n u e } / / S t o r e 6 b y t e s b i t s : = w . b i t s w . b i t s > > = 4 8 w . n b i t s - = 4 8 b y t e s : = w . b y t e s [ n : n + 6 ] b y t e s [ 0 ] = b y t e ( b i t s ) b y t e s [ 1 ] = b y t e ( b i t s > > 8 ) b y t e s [ 2 ] = b y t e ( b i t s > > 1 6 ) b y t e s [ 3 ] = b y t e ( b i t s > > 2 4 ) b y t e s [ 4 ] = b y t e ( b i t s > > 3 2 ) b y t e s [ 5 ] = b y t e ( b i t s > > 4 0 ) n + = 6 i f n < b u f f e r F l u s h S i z e { c o n t i n u e } w . w r i t e ( w . b y t e s [ : n ] ) i f w . e r r ! = n i l { r e t u r n / / R e t u r n e a r l y i n t h e e v e n t o f w r i t e f a i l u r e s } n = 0 } w . n b y t e s = n w . w r i t e C o d e ( e n c o d i n g [ e n d B l o c k M a r k e r ] ) }
  17. compress/flate.huffman B itW riter.write B l ock H uff() f

    u n c ( w * h u f f m a n B i t W r i t e r ) w r i t e B l o c k H u f f ( e o f b o o l , i n p u t [ ] b y t e ) { . . . / / A d d e v e r y t h i n g a s l i t e r a l s h i s t o g r a m ( i n p u t , w . l i t e r a l F r e q ) w . l i t e r a l F r e q [ e n d B l o c k M a r k e r ] = 1 c o n s t n u m L i t e r a l s = e n d B l o c k M a r k e r + 1 c o n s t n u m O f f s e t s = 1 w . l i t e r a l E n c o d i n g . g e n e r a t e ( w . l i t e r a l F r e q , 1 5 ) ハフマン符号化は出現頻度の高いデー タを空間上短く表現する目的 があるため、 input にあるリテラルの集合に対しこれを計算する。
  18. compress/flate.huffman B itW riter.write B l ock H uff() f

    u n c ( w * h u f f m a n B i t W r i t e r ) w r i t e B l o c k H u f f ( e o f b o o l , i n p u t [ ] b y t e ) { . . . v a r n u m C o d e g e n s i n t w . g e n e r a t e C o d e g e n ( n u m L i t e r a l s , n u m O f f s e t s , w . l i t e r a l E n c o d i n g , h u f f O f f s e t ) w . c o d e g e n E n c o d i n g . g e n e r a t e ( w . c o d e g e n F r e q [ : ] , 7 ) s i z e , n u m C o d e g e n s : = w . d y n a m i c S i z e ( w . l i t e r a l E n c o d i n g , h u f f O f f s e t , 0 ) i f s s i z e , s t o r a b l e : = w . s t o r e d S i z e ( i n p u t ) ; s t o r a b l e & & s s i z e < ( s i z e + s i z e > > 4 ) { w . w r i t e S t o r e d H e a d e r ( l e n ( i n p u t ) , e o f ) w . w r i t e B y t e s ( i n p u t ) r e t u r n } . . . 実際直前の処理で算出したリテラルの集合の度数分布から、 実際に ハフマンテー ブルを生成している。
  19. compress/flate.huffman B itW riter.write B l ock H uff() /

    / H u f f m a n . w . w r i t e D y n a m i c H e a d e r ( n u m L i t e r a l s , n u m O f f s e t s , n u m C o d e g e n s , e o f ) e n c o d i n g : = w . l i t e r a l E n c o d i n g . c o d e s [ : 2 5 7 ] n : = w . n b y t e s f o r _ , t : = r a n g e i n p u t { / / B i t w r i t i n g i n l i n e d , ~ 3 0 % s p e e d u p c : = e n c o d i n g [ t ] w . b i t s | = u i n t 6 4 ( c . c o d e ) < < w . n b i t s w . n b i t s + = u i n t ( c . l e n ) i f w . n b i t s < 4 8 { c o n t i n u e } / / S t o r e 6 b y t e s b i t s : = w . b i t s w . b i t s > > = 4 8 w . n b i t s - = 4 8 b y t e s : = w . b y t e s [ n : n + 6 ] b y t e s [ 0 ] = b y t e ( b i t s ) b y t e s [ 1 ] = b y t e ( b i t s > > 8 ) b y t e s [ 2 ] = b y t e ( b i t s > > 1 6 ) b y t e s [ 3 ] = b y t e ( b i t s > > 2 4 ) b y t e s [ 4 ] = b y t e ( b i t s > > 3 2 ) b y t e s [ 5 ] = b y t e ( b i t s > > 4 0 ) n + = 6 i f n < b u f f e r F l u s h S i z e { c o n t i n u e } w . w r i t e ( w . b y t e s [ : n ] ) i f w . e r r ! = n i l { r e t u r n / / R e t u r n e a r l y i n t h e e v e n t o f w r i t e f a i l u r e s } n = 0 } w . n b y t e s = n w . w r i t e C o d e ( e n c o d i n g [ e n d B l o c k M a r k e r ] ) } 直前に作成したハフマンテー ブルを元にデー タ圧縮を実行して書き 込みを行う。
  20. golang.org/pkg/compress/gzip/#pkg‑ examples i f e r r : = z

    w . C l o s e ( ) ; e r r ! = n i l { l o g . F a t a l ( e r r ) } ex amp le に戻ってきた。 g z i p . W r i t e r . C l o s e ( ) は見た目通り後始末メソッド。 i f e r r ! = n i l { / / e r r o r h a n d l i n g . . . } は、 例外機構のない G oにおいては最頻出イディオムといっても過言ではない。
  21. golang.org/pkg/compress/gzip/#pkg‑ examples z r , e r r : =

    g z i p . N e w R e a d e r ( & b u f ) i f e r r ! = n i l { l o g . F a t a l ( e r r ) } f m t . P r i n t f ( " N a m e : % s \ n C o m m e n t : % s \ n M o d T i m e : % s \ n \ n " , z r . N a m e , z r . C o m m e n t , z r . M o d T i m e . U T C ( ) ) i f _ , e r r : = i o . C o p y ( o s . S t d o u t , z r ) ; e r r ! = n i l { l o g . F a t a l ( e r r ) } i f e r r : = z r . C l o s e ( ) ; e r r ! = n i l { l o g . F a t a l ( e r r ) } } g z i p . R e a d e r を g z i p . W r i t e r で生成した圧縮済みデー タで初 期化。 i o . C o p y ( ) の第1引数は i o . W r i t e r 型であり、 標準出力が指定 されている。 第2引数が i o . R e a d e r 型であり、 i o . C o p y の処理内で g z i p . R e a d e r . R e a d ( ) が呼ばれ、 伸張処理が行われる。
  22. compress パッケー ジのリー ディングから学 べたこと G oの考え方やイディオム エラー ハンドリング 構造体生成(N

    ew${stru ct_name}) 抽象化された入出力である、 io.R eader, io.W r it er int er faceを 満たす構造体の使い方 ビット演算 スライス演算 G oにおけるバイト列の操作方法の一部。 gz ip 圧縮の概要とそのアプリケー ションコー ドレベルの記述。
  23. その他 G oの学習リソー ス 公式ドキュメント、W iki G o B log,

    G o W iki, E ffect iv e G o あたり 有名なパッケー ジ aws‑s dk‑go は API C lient のお手本実装の一つ。 AWS の API の知見も得られて一石二鳥 G o界隈の有名人を SNS でフォロー する G o関連のカンファレンス・ イベントのスライドをチェックする G op herC on https://git hu b.com/gop her con/2017‑t alks など 書籍 プログラミング言語 G o(日英両方あり) はじめての G o言語 スター ティング G o言語