#きっかけ
V言語でcoreutilsをつくるプロジェクトを見つけました。
nlは未実装だったので作ることにしました。
V言語の特徴
- シンプルな構文(goっぽい)
- 自動メモリ安全を目指してるらしい(Rustっぽい)
- バイナリが小さい(Rustっぽい)
- 中間言語がC言語らしい
なのでC言語でできることは基本的にできるっぽいですね。
vlang/coreutilsの要件
Complete set of coreutils, written as closely as possible to the POSIX spec, with as many GNU extensions as feasible.
We are looking for solid, working implementations of the commands, not 100% 1-to-1 parity, especially not quirks and unintended side-effects.
「POSIXで、できる限りGNU拡張で、余計なことはしない」ということですね。
nlの実装方針
とりあえず
- Vのライブラリは積極的に使う
- 原則POSIX、GNU拡張フルスペックにする
- 細かな実装差異はGNU版に合わせる
個人的にやりたいこと
- コンパイルオプションでBSD互換動作も網羅
- これは quirksな(癖のある)実装 ではない…はず
使い方
こちらを読んでください。
コンパイル
GNU互換
$ v nl.v
BSD互換
$ v -d all_line_separator=1 -d remove_delimiter_line=1 -d one_delimiter_posix=1 -d fixed_width=1 nl.v
実装の差異
nlg
がGNU互換バーションで、nlb
がBSD互換バージョンです。
all_line_separator
$ echo '' | ./nlg -s @
$ echo '' | ./nlb -s @
@
remove_delimiter_line
$ echo -e 'a\n\\:\nb' | ./nlg
1 a
b
$ echo -e 'a\n\\:\nb' | ./nlb
1 a
b
one_delimiter_posix
$ echo -e 'a\n@:\nb\n@\nc' | ./nlg -d '@'
1 a
2 @:
3 b
c
$ echo -e 'a\n@:\nb\n@\nc' | ./nlb -d '@'
1 a
b
@
c
fixed_width
$ seq 2 | ./nlg -v 99 -w 2
99 1
100 2
$ seq 2 | ./nlb -v 99 -w 2
99 1
00 2
既知の問題
ヘルプの引数
-i, --line-increment <string>
64ビット整数まで受け付けるオプション(-vとか-i)の引数の説明が<string>
になっています。これはオプションを解析するvlibのflag
ライブラリが、整数は<int>
(=32ビット)しか対応していないためです。
(2021/8/30追記)
ヘルプに関しては、val_desc
を設定することで引数の説明を変更できるようです。今度直そう。
ヘルプのオプション
-h, --help display this help and exit
ヘルプによれば、-h
と--help
でヘルプが表示されるのですが、nlの-h
はヘッダーのスタイル指定のオプションと重複します。これもflag
の仕様によるもです。-h a
など引数を渡せば、スタイル指定になりますので実害はないです。
正規表現
-b pBRE
はPOSIXでは基本正規表現としていますが、今回はvlibのregex
をそのまま利用しています。何正規表現をサポートするのかは調べてないです。
オプション処理
nl -nln
と書くとエラーになります。nl -n ln
とスペースをちゃんと入れましょう。これもflag
の仕様です。
整数オーバーフロー
現状では行番号の符号反転で検出する環境依存の実装になっています。INT64_MAX
みたいなリテラルがほしい…
性能
GNU版より10倍くらい遅い…遅い…
(-prod
で2倍くらい速くなります。後述)
- 付番なし
#GNU
$ yes '' | head -n 10000000 | time gnl >/dev/null
gnl > /dev/null 0.52s user 0.01s system 98% cpu 0.534 total
$ yes '' | head -n 10000000 | time gnl >/dev/null
gnl > /dev/null 0.53s user 0.01s system 99% cpu 0.535 total
$ yes '' | head -n 10000000 | time gnl >/dev/null
gnl > /dev/null 0.53s user 0.01s system 99% cpu 0.536 total
#V
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 6.09s user 1.61s system 97% cpu 7.905 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 6.15s user 1.60s system 97% cpu 7.943 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 6.11s user 1.60s system 96% cpu 8.023 total
- 付番あり
#GNU
$ yes '' | head -n 10000000 | time gnl -b a >/dev/null
gnl -b a > /dev/null 0.96s user 0.01s system 99% cpu 0.976 total
$ yes '' | head -n 10000000 | time gnl -b a >/dev/null
gnl -b a > /dev/null 0.97s user 0.01s system 99% cpu 0.982 total
$ yes '' | head -n 10000000 | time gnl -b a >/dev/null
gnl -b a > /dev/null 0.96s user 0.01s system 99% cpu 0.981 total
#V
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 9.08s user 1.75s system 97% cpu 11.057 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 9.08s user 1.75s system 97% cpu 11.067 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 9.10s user 1.75s system 97% cpu 11.076 total
悩みどころ
one_delimiter_posix
現時点(2021/08/20)ではGNU coreutils v8.32が最新ですが、次版ではPOSIX互換に修正され1文字デリミタが使えなくなる見込みです。今回のVの実装ではPOSIX非互換のGNUの動作にあわせています。このPOSIXの仕様どう思いますか?
性能と -prod
コンパイルオプション
(2021/08/29追記)コメント頂いて試してみました。
vは-prodオプションを利用してコンパイルすると、コンパイル時間は長くなりますが、速いバイナリが出てきます。
以下の結果は、現在実装されている23コマンドについてのコンパイル時間になります。あくまでも手元の環境(Macmini M1)ではこのくらいの時間かかりました。というご参考です。
-prod
あり
コンパイル
$ time make prod
make prod 39.05s user 1.06s system 99% cpu 40.258 total
付番なし
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 2.38s user 1.47s system 95% cpu 4.035 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 2.37s user 1.47s system 95% cpu 3.999 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 2.37s user 1.47s system 95% cpu 4.013 total
付番あり
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 4.08s user 1.61s system 97% cpu 5.867 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 4.09s user 1.62s system 96% cpu 5.887 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 4.09s user 1.62s system 97% cpu 5.875 total
-prod
なし(ご参考)
$ time make
make 7.78s user 0.69s system 98% cpu 8.620 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null 6.10s user 1.42s system 97% cpu 7.724 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null 9.07s user 1.56s system 96% cpu 10.972 total
-prod
コンパイルで時間は5倍弱かかりますが、処理速度は2倍程度に改善したようです。なかなかいいですね。