LoginSignup
1
1

More than 1 year has passed since last update.

スクラッチで作るnlコマンド

Last updated at Posted at 2021-08-21
1 / 5

きっかけ

V言語でcoreutilsをつくるプロジェクトを見つけました。

nlは未実装だったので作ることにしました。

スクリーンショット 2021-08-20 20.54.59.png

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倍程度に改善したようです。なかなかいいですね。


1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1