vim-plug: プラグインがインストール済みかどうかを判定する
追記: 以下の記事にfunction! s:plug.is_installed(name)
としてプラグインの存在確認方法が載っていました。
おい、NeoBundle もいいけど vim-plug 使えよ by @b4b4r07 on @Qiita
例として、プラグインの存在確認をするための関数を定義してみます。vim-plug ではプラグインのリストがグローバル変数 g:plugs に辞書として格納されているので、これをうまく利用します。
vim-plugはVimのプラグインマネージャー。
今回、NeoBundleからvim-plugへ乗り換えたので、その時にしたことをメモ。
vim-plugにはNeoBundleのneobundle#is_installed({bundle-name})
に相当する関数がないため、プラグイン依存の設定をvimrcに書きたいときに少し不便。
そこで、以下のような関数is_plugged({plugin-name})
を書いて、プラグインがインストール済みかどうかを判定する。
" check the specified plugin is installed function s:is_plugged(name) if exists('g:plugs') && has_key(g:plugs, a:name) && isdirectory(g:plugs[a:name].dir) return 1 else return 0 endif endfunction
以下のように、neobundle#is_installed({bundle-name})
と同じように使用できる。
" w3m plugin setting if s:is_plugged("w3m.vim") let g:w3m#external_browser = 'open' let g:w3m#download_ext = [ 'zip', 'lzh', 'cab', 'tar', 'gz', 'z', 'exe' ] let g:w3m#search_engine = 'http://www.google.co.jp/search?ie=' . &encoding . '&q=%s' nmap <Space>w :W3m endif " vim-go plugin setting if s:is_plugged("vim-go") let g:go_bin_path = expand("~/.vim-go") endif
参考
bit置換
ある値のn bit目からn+m bit目までを別の値で置き換えたい、という時がある。
例えば、以下のような値a
,b
があったとして
uint8_t a = 0b01010101; // 0x55 uint8_t b = 0b10101010; // 0xAA
a
の値の2〜4bit目をb
の値で置き換えた値c
を求めたい。
a
,b
が上記の値だった場合、c
の値は0x49
になる。
a: 01010101 (0x55) b: ***010** ------------ c: 01001001 (0x49)
これを行うbit演算はこうなる。
uint8_t offset = 2; uint8_t len = 3; uint8_t mask = (~(0xFF << len)) << offset; uint8_t c = (a & ~mask) | (b & mask);
以前に書いたものをまた使う機会があったのでメモとして残しておく。
テストコードはGistに。
LPC812マイコンでUSBシリアルからGPIO制御
組み込み開発の自動テスト化検討のためにUSBGPIO8とか買ってみようかと思ったけど、マニュアル読むと単にPCからUARTコマンド送ってGPIO制御するだけっぽい
— ryochack (@ryochack) September 2, 2015
これならUARTがあるマイコンとUSBシリアル変換モジュール買えば、1000円以内で作れるな
— ryochack (@ryochack) September 2, 2015
UARTが付いているってのを最低条件にして、PICとAVRとNXPのマイコンの価格調べたらNXPのLPC812が一番安かった
— ryochack (@ryochack) September 2, 2015
ということで作ってみた。
GitHubリポジトリ
何ができる?
PCからのUARTコマンドでLPC812のGPIOを制御できる。(LPC812の電源供給はUSBから行う)
それだけ。
(UARTコマンドについてはGitHubのリポジトリを参照)
ただし、PIO0_0〜PIO0_17のうち、以下のGPIOポートはUART、もしくはRESETに割り当てているので使えない。
- PIO0_0 : RXD
- PIO0_4 : TXD
- PIO0_5 : RESET
これとJenkinsと連携させて、コミットがあったら開発デバイスの電源を入れて自動テスト、テストが完了したら電源をOFFっていうのを後々やりたい。
買ったもの
- LPC812マイコン 100円
- IC変換基板(20ピン・SOP) 524円/5枚 -> 105円/枚
- USBシリアル変換アダプタ
- VCC出力を5V/3.3Vで切り替え可能のもの。今回は既に持っていたこちらを使用。 1,512円
- VCC出力は5V固定だがこちらはとても安い。3.3Vに降圧する必要あり。 600円
- http://akizukidenshi.com/catalog/g/gM-08461/
材料費はマイコン100円+IC変換基板105円+USBシリアル変換アダプタ600円で800円くらい。(他にジャンパや抵抗なんかもあるけれども…)
LPCマイコンの開発環境
LPCマイコンの開発には以下を使用。
Flash Magicの書き込み設定。
LPC812はPIO0_12をLOWにしながら電源を入れるとISPモードで起動できる。(Boot loaderのversionが13.4より前では、PIO0_1)
ISPモードで起動後、ここのhexファイルをFlash Magicで書き込めばUARTコマンドからLPC812のGPIO制御ができるようになる。
その他参考情報
ctagsでCとアセンブラ間のタグジャンプができるようにする
ctags使っていて、Cとアセンブラでお互いにタグジャンプできないのが不便だったので、やっつけでtagsファイルを書き換えるスクリプト書いた。
アセンブラからCの関数を呼ぶ時には、関数名の先頭に"_
"がつく。
そのため、tagsファイルにある関数名の記載と異なり、タグジャンプできないことがままある。
call _sys_call ! sys_call(function, src_dest, m_ptr)
こういった時に、
$ ruby ctags_ac.rb tags
で、Cとアセンブラのシンボルをお互いにタグジャンプで行き来できるようにtagsファイルを書き換える。
要Ruby2.1.0以上、もしくはstring-scrub。
スクリプトでやっていることは、
tagsファイルに記載されているCのシンボル名の頭に"_
"を付加したものと、
アセンブラのシンボル名の頭から"_
"をとったものを追記するだけのお粗末なもの。
もちろんtagsのファイルサイズは大きくなる。
あとCとアセンブラしか対応していない。
参考
Go言語のヒープに確保するデータの初期化コストについて調べてみた
2014.6.14追記
途中MakeContainer()/MakeContainerOneLine()がごちゃごちゃになっていたのを修正
golangでは、ヒープに置かれるデータの初期化方法によって内部の挙動が若干異なるみたい。
(環境はx86_64、go version 1.2.2)
違いが出たのは以下のコード。
構造体containerをヒープに確保してポインタ型の戻り値を返す関数を、3つの方法で定義している。
// alloc_overhead.go package main type container struct { v [64]byte } func MakeContainer() *container { c := container{} return &c } func MakeContainerOneLine() *container { return &container{} } func MakeContainerNew() *container { return new(container) } func main() { _ = MakeContainer() _ = MakeContainerOneLine() _ = MakeContainerNew() }
これを以下のコードでベンチマークをとってみる。
package main import ( "testing" ) func BenchmarkMakeContainer(b *testing.B) { for i := 0; i < b.N; i++ { _ = MakeContainer() } } func BenchmarkMakeContainerOneLine(b *testing.B) { for i := 0; i < b.N; i++ { _ = MakeContainerOneLine() } } func BenchmarkMakeContainerNew(b *testing.B) { for i := 0; i < b.N; i++ { _ = MakeContainerNew() } }
以下、ベンチマーク結果。
$ go test -bench . PASS BenchmarkMakeContainer 100000000 17.2 ns/op BenchmarkMakeContainerOneLine 50000000 25.9 ns/op BenchmarkMakeContainerNew 100000000 13.9 ns/op ok _/*/* 4.507s
この結果を見ると、new()するのが最も速く、1行でreturn &container{}
した時が最も遅い結果になった。
アセンブラを読んでみた
なぜ速度に違いが出るのか、アセンブラを出力させて読んでみた。
x86_64の場合、アセンブラは
$ go tool 6g -S -S *.go
で出力できる。
以下、出力させたアセンブラから抜粋。(すべてのアセンブラ出力結果はGistを参照)
--- prog list "MakeContainer" --- 0000 (alloc_overhead.go:7) TEXT MakeContainer+0(SB),$80-8 0001 (alloc_overhead.go:7) FUNCDATA $0,gcargs揃0+0(SB) 0002 (alloc_overhead.go:7) FUNCDATA $1,gclocals揃0+0(SB) 0003 (alloc_overhead.go:7) TYPE ~anon0+0(FP){*"".container},$8 0004 (alloc_overhead.go:7) TYPE autotmp_0001+-64(SP){"".container},$64 0005 (alloc_overhead.go:8) MOVQ $type."".container+0(SB),(SP) 0006 (alloc_overhead.go:8) PCDATA $0,$16 0007 (alloc_overhead.go:8) CALL ,runtime.new+0(SB) 0008 (alloc_overhead.go:8) PCDATA $0,$-1 0009 (alloc_overhead.go:8) MOVQ 8(SP),AX 0010 (alloc_overhead.go:8) LEAQ statictmp_0002+0(SB),BX // 初期化データのアドレスをBXに設定 0011 (alloc_overhead.go:8) LEAQ autotmp_0001+-64(SP),BP 0012 (alloc_overhead.go:8) MOVQ BP,DI 0013 (alloc_overhead.go:8) MOVQ BX,SI // BXを入力に設定 0014 (alloc_overhead.go:8) MOVQ $8,CX // 以下3行で64byte分コピー 0015 (alloc_overhead.go:8) REP , 0016 (alloc_overhead.go:8) MOVSQ , 0017 (alloc_overhead.go:8) MOVQ AX,DI 0018 (alloc_overhead.go:8) MOVQ BP,SI // コピーされたアドレスを今度は入力に設定 0019 (alloc_overhead.go:8) MOVQ $8,CX // 以下3行で64byte分コピー 0020 (alloc_overhead.go:8) REP , 0021 (alloc_overhead.go:8) MOVSQ , 0022 (alloc_overhead.go:9) MOVQ AX,~anon0+0(FP) 0023 (alloc_overhead.go:9) RET , --- prog list "MakeContainerOneLine" --- 0024 (alloc_overhead.go:12) TEXT MakeContainerOneLine+0(SB),$16-8 0025 (alloc_overhead.go:12) FUNCDATA $0,gcargs揃1+0(SB) 0026 (alloc_overhead.go:12) FUNCDATA $1,gclocals揃1+0(SB) 0027 (alloc_overhead.go:12) TYPE ~anon0+0(FP){*"".container},$8 0028 (alloc_overhead.go:13) MOVQ $type."".container+0(SB),(SP) 0029 (alloc_overhead.go:13) PCDATA $0,$16 0030 (alloc_overhead.go:13) CALL ,runtime.new+0(SB) 0031 (alloc_overhead.go:13) PCDATA $0,$-1 0032 (alloc_overhead.go:13) MOVQ 8(SP),BX 0033 (alloc_overhead.go:13) MOVQ BX,DI 0034 (alloc_overhead.go:13) CMPQ BX,$0 0035 (alloc_overhead.go:13) JNE $1,37 0036 (alloc_overhead.go:13) MOVL AX,(BX) 0037 (alloc_overhead.go:13) MOVQ $0,AX // AXに0を設定 0038 (alloc_overhead.go:13) MOVQ $8,CX // 以下3行で64byte分0をセット 0039 (alloc_overhead.go:13) REP , 0040 (alloc_overhead.go:13) STOSQ , 0041 (alloc_overhead.go:13) MOVQ BX,~anon0+0(FP) 0042 (alloc_overhead.go:13) RET , --- prog list "MakeContainerNew" --- 0043 (alloc_overhead.go:16) TEXT MakeContainerNew+0(SB),$16-8 0044 (alloc_overhead.go:16) FUNCDATA $0,gcargs揃2+0(SB) 0045 (alloc_overhead.go:16) FUNCDATA $1,gclocals揃2+0(SB) 0046 (alloc_overhead.go:16) TYPE ~anon0+0(FP){*"".container},$8 0047 (alloc_overhead.go:17) MOVQ $type."".container+0(SB),(SP) 0048 (alloc_overhead.go:17) PCDATA $0,$16 0049 (alloc_overhead.go:17) CALL ,runtime.new+0(SB) 0050 (alloc_overhead.go:17) PCDATA $0,$-1 0051 (alloc_overhead.go:17) MOVQ 8(SP),BX 0052 (alloc_overhead.go:17) MOVQ BX,~anon0+0(FP) 0053 (alloc_overhead.go:17) RET ,
このアセンブラ出力を見ると、それぞれデータの生成方法が異なっているようだ。
MakeContainer()では、new()で確保された領域にstatic領域に確保された初期化データ(statictmp)をmemcpy(MOVSQ命令)してデータを生成しているのに対し、
MakeContainerOneLine()では、new()で確保された領域をmemset(STOSQ命令)してデータを生成している。
MakeContainerNew()は1番シンプルで、new()して領域を確保するだけだ。
MakeContainer()の方がMakeContainerOneLine()よりも速い理由は、MOVSQ命令がSTOSQ命令よりも高速に実行できるためだと思う。
Intelのx86最適化マニュアル(Table C-16. General Purpose Instructions)を見ると、MOVSBの命令レイテンシが0.5なのに対し、STOSBの命令レイテンシは2と、4倍のレイテンシになっている。(参照箇所が正しいか自信がないが…)
MakeContainer()ではMOVSQ命令が 8 × 2 回、MakeContainerOneLine()ではSTOSQ命令が 8 × 1 回行われている。
そのため、MOVSQ/STOSQ命令がMOVSB/STOSB命令と同じ命令レイテンシだと仮定すると、MakeContainer()は 0.5(MOVSQ) × 2 = 1 、MakeContainerOneLineは 2(STOSQ) × 1 = 2 となり、MakeContainerOneLine()の方が処理時間がかかることになる。
バイナリサイズの違い
MakeContainer()とMakeContainerOneLine()、MakeContainerNew()のそれぞれを使った場合に生成されるバイナリサイズの違いについてだが、MakeContainer()だけが他の2つよりもサイズが大きくなる。
$ ls -l -rwxr-xr-x 1 ryochack staff 564960 Jun 8 18:11 MakeContainer* -rwxr-xr-x 1 ryochack staff 564912 Jun 8 18:11 MakeContainerNew* -rwxr-xr-x 1 ryochack staff 564912 Jun 8 18:11 MakeContainerOneLine*
これは、先ほどに書いたように、MakeContainer()のような以下の書き方をすると、ビルド時に初期化用の値がstatic領域(statictmp)に確保されるためだろう。
以下コマンドでバイナリファイルのデータ配置も見てみたが、MakeContainer()の時のみ statictmp が配置されているのを確認した。
$ go tool 6l -a *.6
まとめ
golangでデータをヒープに確保するときは、内部的にnew()が呼ばれる。
複合リテラル記法を使うと、new()された後に memset/memcpy のいずれかの処理が走る。
おそらく、複合リテラル記法で初期値を指定しないフィールドに関しては、暗示的に0を指定されたことになる。
golangのnew()は、値がゼロ初期化されていることが保証されているので、&container{}
みたいな書き方をすると、new()でゼロ初期化された後に再度ゼロが設定される挙動になるようだ。
①new()した後にmemcpy()の挙動。バイナリサイズが他2つよりも大きくなる。②より速く、③より遅い。
func MakeContainer() *container { c := container{} return &c }
②new()した後にmemset()の挙動。最も遅い。
func MakeContainerOneLine() *container { return &container{} }
③new()のみの挙動。最も速い。
func MakeContainerNew() *container { return new(container) }
参考
x86_64の命令セットと最適化マニュアル