sci

最果て風呂

Python3 の print 関数でハマる?

前回の記事ベンチマークをしたのですけれど、今回はもう少しちゃんとやってみることにしました。

日本国憲法はほぼ 32KiB の大きさがあるので、これを元にして 32, 64, 128, 256, 512, 1024, 2048, 3072 の大きさのファイルを作成してベンチマークを測るようにしました。

今回は 5 回計測してその平均をとりました。

64KiB
Python3: 0.13 0.12 0.13 0.13 0.13 | 0.13
Perl5:   0.19 0.18 0.19 0.19 0.19 | 0.19
PHP7:    0.22 0.20 0.20 0.20 0.20 | 0.20
Ruby:    0.32 0.34 0.33 0.32 0.34 | 0.33
JS:      0.11 0.11 0.11 0.11 0.13 | 0.11
C#:      0.19 0.19 0.19 0.19 0.20 | 0.19
C:       0.48 0.49 0.50 0.52 0.47 | 0.49
C++:     0.43 0.43 0.43 0.44 0.44 | 0.43
Go:      0.31 0.31 0.30 0.30 0.31 | 0.31

128KiB
Python3: 0.19 0.20 0.20 0.20 0.19 | 0.20
Perl5:   0.31 0.31 0.33 0.34 0.33 | 0.32
PHP7:    0.39 0.42 0.40 0.38 0.38 | 0.39
Ruby:    0.58 0.59 0.60 0.59 0.60 | 0.59
JS:      0.13 0.14 0.13 0.15 0.13 | 0.14
C#:      0.26 0.26 0.26 0.30 0.25 | 0.27
C:       0.97 0.94 0.95 0.96 0.95 | 0.95
C++:     0.86 0.85 0.85 0.85 0.85 | 0.85
Go:      0.50 0.49 0.48 0.47 0.48 | 0.48

256KiB
Python3: 0.33 0.35 0.34 0.33 0.34 | 0.34
Perl5:   0.59 0.61 0.59 0.58 0.60 | 0.59
PHP7:    0.74 0.74 0.75 0.77 0.73 | 0.75
Ruby:    1.05 1.06 1.04 1.03 1.06 | 1.05
JS:      0.17 0.18 0.18 0.18 0.17 | 0.18
C#:      0.40 0.40 0.42 0.40 0.40 | 0.40
C:       1.88 1.93 1.87 1.91 1.88 | 1.89
C++:     1.67 1.66 1.67 1.67 1.68 | 1.67
Go:      0.83 0.82 0.82 0.83 0.82 | 0.82

512KiB
Python3: 0.61 0.63 0.63 0.63 0.61 | 0.62
Perl5:   1.10 1.14 1.11 1.12 1.15 | 1.12
PHP7:    1.43 1.44 1.40 1.41 1.41 | 1.42
Ruby:    1.97 1.97 1.95 1.96 1.99 | 1.97
JS:      0.26 0.27 0.29 0.27 0.27 | 0.27
C#:      0.69 0.72 0.69 0.69 0.74 | 0.71
C:       3.80 3.85 3.76 3.77 3.74 | 3.78
C++:     3.33 3.33 3.33 3.32 3.32 | 3.33
Go:      1.52 1.51 1.49 1.53 1.49 | 1.51

1024KiB
Python3: 1.16 1.25 1.18 1.14 1.14 | 1.17
Perl5:   2.12 2.14 2.15 2.09 2.12 | 2.12
PHP7:    2.74 2.81 2.78 2.74 2.75 | 2.76
Ruby:    3.94 3.86 3.80 3.84 3.80 | 3.85
JS:      0.65 0.48 0.45 0.45 0.46 | 0.50
C#:      1.50 1.29 1.28 1.26 1.27 | 1.32
C:       7.49 7.49 7.54 7.50 7.43 | 7.49
C++:     6.61 6.63 6.61 6.62 6.58 | 6.61
Go:      2.91 2.89 2.86 2.86 2.84 | 2.87

2048KiB
Python3:  2.30  2.30  2.25  2.23  2.24 |  2.26
Perl5:    4.10  4.24  4.14  4.13  4.20 |  4.16
PHP7:     5.39  5.51  5.42  5.46  5.52 |  5.46
Ruby:     7.43  7.44  7.42  7.39  7.46 |  7.43
JS:       0.80  0.80  0.80  0.79  0.80 |  0.80
C#:       2.42  2.41  2.42  2.41  2.42 |  2.42
C:       14.93 14.94 14.95 15.11 14.93 | 14.97
C++:     13.15 13.21 13.21 13.20 13.20 | 13.19
Go:       5.51  5.62  5.53  5.52  5.50 |  5.54

3072KiB
Python3:  3.36  3.47  3.36  3.35  3.42 |  3.39
Perl5:    6.20  6.42  6.19  6.19  6.45 |  6.29
PHP7:     8.05  8.24  8.07  8.04  8.14 |  8.11
Ruby:    11.08 11.39 11.12 11.06 11.27 | 11.18
JS:       1.17  1.18  1.15  1.16  1.17 |  1.17
C#:       3.56  3.63  3.59  3.60  3.62 |  3.60
C:       22.35 22.53 22.32 22.32 22.34 | 22.37
C++:     19.81 19.78 19.76 19.73 19.79 | 19.77
Go:       8.24  8.32  8.24  8.19  8.31 |  8.26
64 128 256 512 1024 2048 3078
Python3: 0.13 0.20 0.34 0.62 1.17 2.26 3.39
Perl5: 0.19 0.32 0.59 1.12 2.12 4.16 6.29
PHP7: 0.20 0.39 0.75 1.42 2.76 5.46 8.11
Ruby: 0.33 0.59 1.05 1.97 3.85 7.43 11.18
JS: 0.11 0.14 0.18 0.27 0.50 0.80 1.17
C#: 0.19 0.27 0.40 0.71 1.32 2.42 3.60
C: 0.49 0.95 1.89 3.78 7.49 14.97 22.37
C++: 0.43 0.85 1.67 3.33 6.61 13.19 19.77
Go: 0.31 0.48 0.82 1.51 2.87 5.54 8.26

こんな結果になりました。Node.js の JavaScript が速すぎるように思うのですけれど、ちゃんと変換はされてるのかしら? …… されていました。

あれ?Python だけ他のスクリプトと比べて出力された結果が異なっている……?diff をとってみると、改行ひとつ少なくなっているようです。なんで?

元ファイルの最後に改行が複数個ある場合に、Python3 では削除されてしまう?それともファイルを読み込んだ時がマズいのかな?

text = a_file.read()
...
print('{0}'.format(text).rstrip('\n'))

このへんに原因がありそう。当初は読み込んだファイルを一行ずつ処理するようにしていて問題はなかったのですよ。rstrip('\n') って何で付けてるのだろう?rstrip() を削除してみると、今度は改行が増え過ぎちゃう。read() の方が原因?

わかった!Pythonprint 関数はデフォルトで改行を入れるんですわ。Ruby でいうところの puts なんやね。当時の自分は改行が多くなってしまったので rstrip() を入れたのですね。しかしこのようにすると、行末に改行が複数個ある場合は全部取られてしまうので、今回の例文のケースでは逆に少なくなってしまうというわけね。結局、次のようにすれば良くなりました。

print('{0}'.format(text), end='')

個人的にはファイルの最後は改行ひとつにしているので、今回のように改行を複数個付ける機会なんてありませんでした。長く使ってみるといろいろな事がありますね。そして予期しない不具合も発見されると。

f:id:nakinor:20170624234019p:plain

ということでグラフを作成してみると、きれいな直線になっていました(128KiB は他と近いので除きました)。数値の表を見ただけではわからないものですね。

フリーな文章で例文の容量をかせぐという姑息な手段を用いたため、中身は同じ内容の繰り返しになっていますものね……。もう少し変化のある文章にしなければ……。次は青空文庫から適当な文章を借りて実験してみようかしら?

おまけ

何も考えていない富豪的アルゴリズムなので、次のような関係になっています。

O (m x n)
m: 辞書の要素数
n: 変換対象ファイルの行数

ここで C と C++ を除くものについては、ファイルの内容を一気に読み込んで n が 1 となるようにする(while 文のループが不要になる)ことで、処理速度は改善しました。そして先日、調べまくって C と C++ についても一気に読み込んで長大な文字列を処理させるようにしたのですが、逆に 50 倍も遅くなってしまいました(ベンチマークは一行ずつ処理する方式のもの)。なんでなん?

C++ は標準添付の String::replace を使っているのですけれど、コストの高い処理なのですかね……。そういえば Objective-C も Framework で用意されているものは遅かったし……。アルゴリズムを考えるといっても、「ひとつ前で処理された結果に対して次の処理をしていく必要がある」ので、並列化なんてできませんし……。

今回はベンチマークの目的のためだけにファイルを用意しましたが、通常は EmacsVim 上で段落毎に変換をして、確認しながら作業をしています。だからこのベンチマークは意味が無いと言えば意味が無いのですけどね。

突然ですが、JavaScript というか Node.js の性能の良さにはびっくりしますね!