tail

tailを書くのが流行ってるみたいなのでやってみた。

main = do cs <- getContents
          putStr $ lastNlines 10 cs

lastNlines :: Int -> String -> String
lastNlines n cs = unlines $ takeLast n $ lines cs

takeLast :: Int -> [String] -> [String]
takeLast n ss = if (length ss) <= n 
                   then ss
                   else drop (length ss - n) ss

しかし、

  • ふつける版: 0.00s user 0.66s system 93% cpu 0.708 total
  • 自分版 : 0.00s user 0.71s system 85% cpu 0.826 total

と自分が書いた方が遅くなってしまった。おそらくlength ss - n の中身を展開する際にすべてのテキストを読んでわなきゃいけないので結局あまり変わらないのかなと思う。

ちなみにベンチマークには100000行hogeと入力したテキストファイルを使いました。

おまけでRubyでもやってみました。

puts ARGF.map {|i| i}.slice(-10, 10)

ベンチマークは0.00s user 0.71s system 98% cpu 0.721 totalでした。大体同じくらい。
メモリはバカ食いです。

ただこういうときinjectを活用すると少し省メモリに書けたりしそうという発見があった。と思って書いたあとベンチ取ったらinject使う方が遅かったけど。メモリは食うけどメソッド呼び出しの回数を少なくした方が早くなるっぽい。

<追記(06/07/04 00:25)>

どうもRubyの一番のパフォーマンスのネックはGCの起こる回数のようで、injectを使うとメモリの使う部分は多くは無いですが基本的にinjectで途中で生成されたものは捨てオブジェクトのためGCが頻繁に発生し遅くなるといったことのようです。

メソッドチェーンでは破壊的メソッドを使うという派閥があるのもこれと関係しているみたいです。これを知るとすこし破壊的メソッドを使う派へ改宗しようかなと思った。

Rubyの最適化に関して詳しくは青木さんのページを参照してください
http://i.loveruby.net/w/OptimizingRubyProgram.html