2016年4月10日日曜日

perlの無名配列について

自作の perl スクリプトで、次のようなコードがありました。
sub xxxx {
...
    my @tmp_xxxx = split(" ", <FH>) ;
    return $tmp_xxxx[38] ;
}
これを見ていたら、@tmp_xxxx が無駄に思えてきました。オブジェクト指向言語っぽく、split(" ", <FH>)[38] のように書けないのか?と思ったわけです。
調べてみると、無名配列を使うと、次のように書けることを知りました。
sub xxxx {
...
    return = [split(" ", <FH>)]->[38] ;
}
これいいなと思って、他の似た箇所も全部これのほうが良いかなと、さらに自作スクリプトを眺めてみたら、次のようなコードもありました。
...
    (undef, $p) = split(" ", $k)
...
これを見て、性能面では、どの書き方が良いのか?ということが気になってしまいました。
そこで、ベンチマークで確認しました。
#use strict;
#use warnings;
use Benchmark qw(cmpthese timethese :hireswallclock);

$x = "a b c d e f g h i j k l m n o p" ;

cmpthese(1000000,{
    method1 => sub {
        (undef,undef,undef,undef,undef,undef,undef,undef,undef,undef,undef,undef,undef,undef,undef, my $y)
            = split(" ", $x) ;
    },
    method2 => sub {
        my $y = [split(" ", $x)]->[15] ;
    },
    method3 => sub {
        my @temp = split(" ", $x) ;
        my $y = $temp[15] ;
    },
});
[root@hoge ]# cat /etc/redhat-release 
CentOS Linux release 7.2.1511 (Core)
[root@hoge ]# rpm -q perl
perl-5.16.3-286.el7.x86_64
[root@hoge ]# perl bench_split16.pl 
            Rate method2 method3 method1
method2 386100/s      --    -11%    -43%
method3 434783/s     13%      --    -36%
method1 680272/s     76%     56%      --
このように、無名配列(method2)が一番遅いという結果になりました。しかしながら、[38] などという場合は、実行頻度も加味して、可読性の観点から、無名配列による書き方を選んだほうが良い場合もあると思います。
なお、興味深いことに、CentOS 6 では順序が入れ替わります。
[root@hoge ~]# cat /etc/redhat-release 
CentOS release 6.7 (Final)
[root@hoge ~]# rpm -q perl
perl-5.10.1-141.el6.x86_64
[root@hoge ~]# perl bench_split16.pl 
            Rate method3 method2 method1
method3 349650/s      --    -19%    -48%
method2 432900/s     24%      --    -35%
method1 671141/s     92%     55%      --
ThinkPad W520 のマルチブート環境にて実行しています。

perl を毛嫌いする人を見かけますが、この記事に書いたような側面(同じことを幾通りにも書ける)からなのだろうか?と思いました。ところが、これこそ perl の面白さと思うのです。私的には。
よーし、今こそ、もっと perl 勉強しよう (活用しよう) っと!

2016-04-14追記
「Effective Perl」を読んでみたら、スライスを使って次のように書けることを知りました。
    method4 => sub {
        my ($y) = (split(" ", $x))[15] ;
    },
ふたたび、ベンチマークしてみると、スライス(method4)が1番速いという結果になりました。
[root@hoge ~]# cat /etc/redhat-release 
CentOS release 6.7 (Final)
[root@hoge ~]# rpm -q perl
perl-5.10.1-141.el6_7.1.x86_64
[root@hoge ~]# perl bench_split16.pl
            Rate method3 method2 method1 method4
method3 255754/s      --    -17%    -46%    -48%
method2 307692/s     20%      --    -34%    -38%
method1 469484/s     84%     53%      --     -5%
method4 495050/s     94%     61%      5%      --
[root@hoge ~]# service cpuspeed start
Enabling ondemand cpu frequency scaling:                   [  OK  ]
[root@hoge ~]# perl bench_split16.pl
            Rate method3 method2 method1 method4
method3 347222/s      --    -19%    -46%    -49%
method2 431034/s     24%      --    -33%    -36%
method1 645161/s     86%     50%      --     -5%
method4 675676/s     95%     57%      5%      --
本題からそれますが、cpuspeed を止めていると Turbo Boost まで効かなくなるようで、パフォーマンス低下するようです。前回のベンチの際は、chkconfig on にしてたんですが、性能下がるかと思って off にしてましたが、逆効果でした。マシンは、ThinkPad W520 (Core i7 2960XM) です。利用シーンによっては、逆手にとって、静音性のために chkconfig cpuspeed off もよいかもしれません。

2016年3月10日木曜日

CentOS 7 で Btrfs のシングル構成を raid1 に変換

CentOS 7 で Btrfs のシングル構成で利用していた領域を、raid1 に変換しました。
その際、スペースに余裕があるように見えるのに ENOSPC になったので、メモです。

もともと sdb5 のみのシングル構成で、CentOS 7.2 の OS インストール領域として使っていました。これに、sda5 を add しました。
[root@hoge ~]# cat /etc/redhat-release 
CentOS Linux release 7.2.1511 (Core) 
[root@hoge ~]# uname -a
Linux hoge 3.10.0-327.10.1.el7.x86_64 #1 SMP Tue Feb 16 17:03:50 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge ~]# btrfs device add /dev/sda5 /    ※もともとsdb5のシングルだった
[root@hoge ~]# df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/sdb5         40G  8.0G   31G   21% /
devtmpfs         7.9G     0  7.9G    0% /dev
tmpfs            7.9G  220K  7.9G    1% /dev/shm
tmpfs            7.9G  9.1M  7.9G    1% /run
tmpfs            7.9G     0  7.9G    0% /sys/fs/cgroup
tmpfs            1.6G  8.0K  1.6G    1% /run/user/0
[root@hoge ~]# btrfs fi show -m
Label: none  uuid: 73fd9fe0-6681-4389-abe5-05b7db9d5fe8
 Total devices 2 FS bytes used 7.79GiB
 devid    1 size 19.53GiB used 19.53GiB path /dev/sdb5
 devid    2 size 19.53GiB used 0.00B path /dev/sda5

btrfs-progs v3.19.1
次に、raid1 への変換を試みましたが、失敗。
[root@hoge ~]# btrfs device usage /
/dev/sda5, ID: 2
   Device size:            19.53GiB
   Unallocated:            19.53GiB

/dev/sdb5, ID: 1
   Device size:            19.53GiB
   Data,single:            18.74GiB
   Metadata,single:       776.00MiB
   System,single:          32.00MiB
   Unallocated:             1.00MiB

[root@hoge ~]# btrfs fi df /
Data, single: total=18.74GiB, used=7.42GiB
System, single: total=32.00MiB, used=16.00KiB
Metadata, single: total=776.00MiB, used=374.86MiB
GlobalReserve, single: total=128.00MiB, used=0.00B
[root@hoge ~]# btrfs balance start -dconvert=raid1 -mconvert=raid1 /
ERROR: error during balancing '/' - No space left on device
There may be more info in syslog - try dmesg | tail
[root@hoge ~]# dmesg | tail
[   30.408740] Bluetooth: RFCOMM TTY layer initialized
[   30.408752] Bluetooth: RFCOMM socket layer initialized
[   30.408817] Bluetooth: RFCOMM ver 1.11
[   54.279442] EXT4-fs (sda5): mounting ext3 file system using the ext4 subsystem
[   54.280985] EXT4-fs (sda5): mounted filesystem with ordered data mode. Opts: (null)
[   54.281113] SELinux: initialized (dev sda5, type ext3), uses xattr
[  461.381125] SELinux: initialized (dev tmpfs, type tmpfs), uses transition SIDs
[  529.804577] SELinux: initialized (dev tmpfs, type tmpfs), uses transition SIDs
[ 1076.279348] BTRFS info (device sdb5): disk added /dev/sda5
[ 1720.107266] BTRFS info (device sdb5): 26 enospc errors during balance
たぶん、いったんは、このままリバランス(sdb5 のデータの半分を sda5 へ転送)してから、再実行すれば、上手くいくものと推察し、やってみたら案の定うまくいきました。
[root@hoge ~]# btrfs balance start /
Done, had to relocate 26 out of 26 chunks
[root@hoge ~]# btrfs balance start -dconvert=raid1 -mconvert=raid1 /
Done, had to relocate 12 out of 12 chunks
[root@hoge ~]# btrfs device usage /
/dev/sda5, ID: 2
   Device size:            19.53GiB
   Data,RAID1:              8.00GiB
   Metadata,RAID1:        768.00MiB
   System,RAID1:           32.00MiB
   Unallocated:            10.75GiB

/dev/sdb5, ID: 1
   Device size:            19.53GiB
   Data,RAID1:              8.00GiB
   Metadata,RAID1:        768.00MiB
   System,RAID1:           32.00MiB
   Unallocated:            10.75GiB

[root@hoge ~]#
コーナーケースでしょうけれど、このあたりがレッドハットが未だに Btrfs を Technology Preview 扱いにしている理由かもしれませんね。

2016年1月23日土曜日

gawk で文字列 "01" の比較でハマった

長年(20年以上) AWK を使っていて、わかってるつもりでしたが、文字列が暗黙に数値として扱われる際の落とし穴にハマりましたので、備忘録です。以前にも、このパターンに悩まされた記憶があるんですが、忘れたころにふたたびバグってしまいました。
BEGIN {
    s="01 02"
    split(s, ss)
}
{
    for (i in ss) {
        if ($1 == ss[i]) {
            printf "%s == %s\n", $1, ss[i]
        } else {
            printf "%s != %s\n", $1, ss[i]
        }
    }
}
[root@hoge tmp]# echo 01 | gawk -f test_string_01.awk 
01 == 01
01 != 02
[root@hoge tmp]# echo 1 | gawk -f test_string_01.awk 
1 == 01
1 != 02
$1 が 1 の場合には、if ($1 == ss[i]) において ss[i] が数値として扱われ、文字列の "01" が暗黙に数値の 1 として比較が実行されるのだと思います。次のように修正(C言語のキャストっぽく修正)することで、意図した動作になりました。
BEGIN {
    s="01 02"
    split(s, ss)
}
{
    for (i in ss) {
        if ($1 == ""ss[i]) {
            printf "%s == %s\n", $1, ss[i]
        } else {
            printf "%s != %s\n", $1, ss[i]
        }
    }
}
[root@hoge tmp]# diff -u test_string_01.awk test2_string_01.awk 
--- test_string_01.awk 2016-01-23 20:06:54.537647508 +0900
+++ test2_string_01.awk 2016-01-23 20:25:23.543167642 +0900
@@ -4,7 +4,7 @@
 }
 {
     for (i in ss) {
-        if ($1 == ss[i]) {
+        if ($1 == ""ss[i]) {
             printf "%s == %s\n", $1, ss[i]
         } else {
             printf "%s != %s\n", $1, ss[i]
[root@hoge tmp]# echo 01 | gawk -f test2_string_01.awk 
01 == 01
01 != 02
[root@hoge tmp]# echo 1 | gawk -f test2_string_01.awk 
1 != 01
1 != 02
ちなみに、別実装(nawk, mawk)でも、同様の結果でした。
[root@hoge tmp]# echo 01 | nawk -f test_string_01.awk 
01 != 02
01 == 01
[root@hoge tmp]# echo 1 | nawk -f test_string_01.awk 
1 != 02
1 == 01
[root@hoge tmp]# echo 01 | mawk -f test_string_01.awk 
01 != 02
01 == 01
[root@hoge tmp]# echo 1 | mawk -f test_string_01.awk 
1 != 02
1 == 01
nawk, mawk では、for (i in ss) の順序が、gawk とは違うようですが、今回の問題の比較部分は同様に意図しない動作になります。そして、同じ修正で、意図した動作になります。
[root@hoge tmp]# echo 1 | mawk -f test2_string_01.awk 
1 != 02
1 != 01
[root@hoge tmp]# echo 1 | nawk -f test2_string_01.awk 
1 != 02
1 != 01

こういうことも、たまにはあるけども、いやー AWK って本当にいいもんですね。って思います。
この 20年 どんだけお世話になったことか。費用対効果(学習コストに対する成果)が抜群じゃないかなと。手短に手早く書いて、あっという間に結果を得られることがしばしばです。
人気ブログランキングへ にほんブログ村 IT技術ブログへ