大きなファイルを扱うのに、CPU は 10% 台で、HDD アクセスも「アクティブな時間」が 10% まで行かない。ということで、マルチスレッド化してみよう。
入力ファイル kenpo2 と言うファイルをそれぞれのスレッドで読み込みながら、そのまま kenpo2.th-out に書き込む。行は入れ替わっても全ての行がコピーされる筈。
それぞれのスレッドでファイルを seek して、$in_file_pos から $in_file_pos + $size_per_th まで読み込む。(行末までは前のスレッドで読む)
スレッド処理するプログラムを書いてみた
色々動かなかったりしたけど、なんとか動いたのがこんな感じ。
#!/opt/perl-5.34.1/bin/perl5.34.1
use strict;
use warnings;
use Fcntl qw(:seek);
use threads;
use threads::shared;
use Thread::Running;
use Data::Dumper;
my $in_file = "kenpo2";
my $out_file = "kenpo2.th-out";
my $in_file_size : shared = 1; # 読み込みファイルサイズ
my $size_per_th = 20 * 1024 * 1024; # 一つのスレッドで一度に読むファイルサイズ
my $th_max = 8; # スレッドの最大数
# ファイルが開けるかテスト & 出力ファイルのクリア
{
my ( $in_fh, $out_fh );
open $in_fh, "<:encoding(UTF-8)", $in_file or die "file open error : $in_file $!\n";
open $out_fh, ">:encoding(UTF-8)", $out_file or die "file open error : $out_file $!\n";
close $in_fh;
close $out_fh;
$in_file_size = -s $in_file;
}
{
my $in_file_pos = 0; # 次のスレッドの読み込み開始位置
my $th_data;
my @th_data_array;
my @running = undef;
while ( $in_file_pos < $in_file_size ) {
# $th_max 未満ならスレッドを立てる
while ( $th_max > $#running && $in_file_pos < $in_file_size ) {
$th_data = threads->create(
\&read_line,
(
$in_file, $out_file,
$in_file_pos, $in_file_pos + $size_per_th
)
);
push @th_data_array, $th_data;
$in_file_pos += $size_per_th;
$size_per_th = $in_file_size - $in_file_pos
if ( ( $in_file_pos + $size_per_th ) > $in_file_size );
@running = threads->running(@th_data_array);
printf "%d thread(s) running\n", $#running;
}
# join できるスレッドができるまで待ち、join 可能な全スレッドをjoinする
# (スレッドの看取り
sleep 1 until threads->tojoin(@th_data_array);
$_->join foreach threads->tojoin(@th_data_array);
@running = threads->running(@th_data_array);
}
# 全てのスレッドが実行を終了するまで待つ
print "****** wait for the last thred. ******\n";
sleep 1 while threads->running;
$_->join foreach threads->tojoin(@th_data_array);
print "****** finished. ******\n";
}
sub read_line {
# $_[0] $in_file 入力ファイル名
# $_[1] $out_file 出力ファイル名
# $_[2] $start_pos 読み始め位置
# $_[3] $end_pos 終了位置
my ( $in_file, $out_file, $start_pos, $end_pos ) = @_;
my $in_fh;
my $out_fh;
open $in_fh, "<:encoding(UTF-8)", $in_file or die "in_file open error : $in_file $!\n";
open $out_fh, ">>:encoding(UTF-8)", $out_file or die "out_file open error : $out_file $!\n";
seek $in_fh, $start_pos, SEEK_SET or die "input file seek error.\n$!\n";
my $dummy = <$in_fh> if $start_pos != 0;
printf "---- th no %3d / tell %d / end %d / file_size %d start ----\n",
threads->tid(), ( tell $in_fh ), $end_pos, $in_file_size;
# my $count = 0;
do {
my $line = <$in_fh>;
print $out_fh $line;
# if (($count % 10000) == 0) {
# printf "%d, %s", threads->tid(), $line;
# printf "th no %3d / tell %d / end %d / file_size %d \n", threads->tid(), (tell $in_fh), $end_pos, $in_file_size ;
# }
# ++$count;
} while ( ( tell $in_fh ) < $end_pos );
printf "---- th no %3d / tell %10d /\n", threads->tid(), tell $in_fh;
printf "---- / end %10d / file_size %d done ----\n",
$end_pos, $in_file_size;
close $in_fh;
close $out_fh;
}
入力用のファイルを作成 (Fedora remix on Windows Subsystem for Linux Preview のバグに阻まれる)
出版労連 のページにあった日本国憲法を kenpo というテキストファイルにして適当に折り返し、ざっくりと 9999倍にして kenpo2 というファイルを作成。
% seq 9999 | xargs -I0 -n 1 fold -w 50 kenpo | cat -n > kenpo2
% cat kenpo2
1 日本国憲法(全文)
2 日本国民は,正当に選挙された国会における代表者を通
3 じて行動し,われらとわれらの子孫のために,諸国民との
4 協和による成果と,わが国全土にわたつて自由のもたらす
5 恵沢を確保し,政府の行為によつて再び戦争の惨禍が起る
6 ことのないやうにすることを決意し,ここに主権が国民に
7 存することを宣言し,この憲法を確定する。そもそも国政
8 は,国民の厳粛な信託によるものであつて,その権威は国
9 民に由来し,その権力は国民の代表者がこれを行使し,そ
10 の福利は国民がこれを享受する。これは人類普遍の原理
11 であり,この憲法は,かかる原理に基くものである。われ
12 らは,これに反する一切の憲法,法令及び詔勅を排除する
13 。
14
15 日本国民は,恒久の平和を念願し,人間相互の関係を支
16 配する崇高な理想を深く自覚するのであつて,平和を愛す
17 る諸国民の公正と信義に信頼して,われらの安全と生存を
18 保持しようと決意した。われらは,平和を維持し,専制と
19 隷従,圧迫と偏狭を地上から永遠に除去しようと努めてゐ
20 る国際社会において,名誉ある地位を占めたいと思ふ。わ
21 れらは,全世界の国民が,ひとしく恐怖と欠乏から免かれ,
22 平和のうちに生存する権利を有することを確認する。
....
4031958 第11章 補則
4031959 〔施行期日と施行前の準備行為〕
4031960 第100条 この憲法は,公布の日から起算して六箇月を経過した日〔昭二二・五・三〕から,これを施行する。
4031961 2 この憲法を施行するために必要な法律の制定,参議院議員の選挙及び国会召集の手続並びにこの憲法を施行するために必要な準備手続は,前項の期日よりも前に,これを行ふことができる。
4031962
4031963 〔参議院成立前の国会〕
4031964 第101条 この憲法施行の際,参議院がまだ成立してゐないときは,その成立するまでの間,衆議院は,国会としての権限を行ふ。
4031965
4031966 〔参議院議員の任期の経過的特例〕
4031967 第102条 この憲法による第一期の参議院議員のうち,その半数の者の任期は,これを三年とする。その議員は,法律の定めるところにより,これを定める。
4031968
4031969 〔公務員の地位に関する経過規定〕
4031970 第103条 この憲法施行の際現に在職する国務大臣,衆議院議員及び裁判官並びにその他の公務員で,その地位に相応する地位がこの憲法で認められてゐる者は,法律で特別の定をした場合を除いては,この憲法施行のため,当然にはその地位を失ふことはない。但し,この憲法によつて,後任者が選挙又は任命されたと きは,当然その地位を失ふ。
%
あれ?前文がないのか。日本国憲法は前文も大事だと思うんだけどな。ま、今は内容は気にしないので。いや、折り返しできてない?あれ?fold コマンドとは???
FreeBSD 上で実行したらちゃんと折り返しできてるんだけどなぁ? Fedora か Windows Subsystem for Linux Preview の何かが腐っているようです。残念。
→私の頭が腐ってました seq 9999 | xargs -Ix -n 1 fold -w 50 kenpo | cat -n > kenpo2
なら上手くいきます。
入力用のファイルを作成 その2
% seq 9999 | sed 's/[0-9]*/kenpo/' | xargs cat | fold -w 50 | cat -n > kenpo2
% cat kenpo2
1 日本国憲法(全文)
2 日本国民は,正当に選挙された国会における代表者を
3 通じて行動し,われらとわれらの子孫のために,諸国民と
4 の協和による成果と,わが国全土にわたつて自由のもた
5 らす恵沢を確保し,政府の行為によつて再び戦争の惨禍
6 が起ることのないやうにすることを決意し,ここに主権
...
7009292 〔公務員の地位に関する経過規定〕
7009293 第103条 この憲法施行の際現に在職する国務大臣,衆議
7009294 院議員及び裁判官並びにその他の公務員で,その地位に
7009295 相応する地位がこの憲法で認められてゐる者は,法律で
7009296 特別の定をした場合を除いては,この憲法施行のため,当
7009297 然にはその地位を失ふことはない。但し,この憲法によ
7009298 つて,後任者が選挙又は任命されたときは,当然その地位
7009299 を失ふ。
%
うむ。それっぽいのができたので、このファイルを使って実行!
% repeat 9999 fold -w 50 kenpo | cat -n > kenpo2
% repeat 9999 echo kenpo | xargs cat | fold -w 50 | cat -n > kenpo2
一番下のが一番早いでしょうか。シェルの内部コマンドで成る可くプロセスをフォークしない、xargs で、フォークするプロセスの数を減らす。ということで xargs 最強です。ってなんの話や?
実行
% ./threads-test
---- th no 1 / tell 0 / end 20971520 / file_size 386061291 start ----
0 thread(s) running
1 thread(s) running
---- th no 2 / tell 20971560 / end 41943040 / file_size 386061291 start ----
2 thread(s) running
UTF-8 "\xAC" does not map to Unicode at ./threads-test line 82.
---- th no 3 / tell 41943112 / end 62914560 / file_size 386061291 start ----
3 thread(s) running
---- th no 4 / tell 62914579 / end 83886080 / file_size 386061291 start ----
4 thread(s) running
UTF-8 "\x81" does not map to Unicode at ./threads-test line 82.
UTF-8 "\xAE" does not map to Unicode at ./threads-test line 82.
...snip...
---- th no 16 / tell 314572808 / end 335544320 / file_size 386061291 start ----
7 thread(s) running
8 thread(s) running
UTF-8 "\xAD" does not map to Unicode at ./threads-test line 82.
UTF-8 "\xB0" does not map to Unicode at ./threads-test line 82.
---- th no 17 / tell 335544390 / end 356515840 / file_size 386061291 start ----
UTF-8 "\x81" does not map to Unicode at ./threads-test line 82.
UTF-8 "\x8C" does not map to Unicode at ./threads-test line 82.
---- th no 18 / tell 356515898 / end 377487360 / file_size 386061291 start ----
---- th no 11 / tell 230686773 /
---- / end 230686720 / file_size 386061291 done ----
---- th no 16 / tell 335544390 /
---- / end 335544320 / file_size 386061291 done ----
---- th no 13 / tell 272629812 /
---- / end 272629760 / file_size 386061291 done ----
---- th no 18 / tell 377487369 /
---- / end 377487360 / file_size 386061291 done ----
---- th no 10 / tell 209715226 /
---- / end 209715200 / file_size 386061291 done ----
---- th no 14 / tell 293601348 /
---- / end 293601280 / file_size 386061291 done ----
---- th no 12 / tell 251658299 /
---- / end 251658240 / file_size 386061291 done ----
---- th no 17 / tell 356515898 /
---- / end 356515840 / file_size 386061291 done ----
---- th no 15 / tell 314572808 /
---- / end 314572800 / file_size 386061291 done ----
0 thread(s) running
UTF-8 "\x81" does not map to Unicode at ./threads-test line 82.
UTF-8 "\x99" does not map to Unicode at ./threads-test line 82.
---- th no 19 / tell 377487369 / end 386061291 / file_size 386061291 start ----
---- th no 19 / tell 386061291 /
---- / end 386061291 / file_size 386061291 done ----
****** wait for the last thred. ******
****** finished. ******
% ls -l kenpo2*
-rw-r--r-- 1 abc123 abc123 386061291 5月 1 03:54 kenpo2
-rw-r--r-- 1 abc123 abc123 386061267 5月 1 04:18 kenpo2.th-out
Unicode のエラーが出てるのは、文字の途中でぶった切られてるからかな。82行目は、読み捨てているところなので問題なし。
あれ?行は入れ替わっても良いけど、サイズが変わるのはおかしいな。
cat で見てみると行が混ざっているような出力もある。例として、1行90文字を超える行を引っ張り出してみた。
% perl -ne 'print if /.{90,}/' kenpo2
% perl -ne 'print if /.{90,}/' kenpo2.th-out
681 るために必要な準備手続は,前項の所の裁判官は,すべて定期に相当額の報酬を
386836 第85条 国費を支出し,又は国が債務を負担に反する一切の憲法,法令及び詔
826 ない。栄典の授与は,現にこれを有し,又は将来これを第96条 この憲法の改正は,各議院の総議員の三分の二以
...
元ファイルには存在しない行が、出力ファイルに存在してる。
print 文は単位では OS のシステムコールとして独立してるから、出力途中に割り込みは入らないと思いきや、そうではないんですねぇ。
どうやら、書き込み用のスレッドを立てて threads::shared、Thread::Queue 辺りを使って渡す必要がありそう。
実行環境
試した環境
環境 1
WSL2 Fedora-remix / perl 5.34.1 (手動 build)
環境 2
FreeBSD 13.1-RC5 / perl 5.34.1 (ports より導入)
% /opt/perl-5.34.1/bin/perl5.34.1 -v
This is perl 5, version 34, subversion 1 (v5.34.1) built for x86_64-linux-thread-multi-ld
Copyright 1987-2022, Larry Wall
Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.
% uname -smr
Linux 5.10.102.1-microsoft-standard-WSL2 x86_64
% cat /etc/os-release
NAME="Fedora Remix for WSL"
VERSION="34"
ID=fedoraremixforwsl
ID_LIKE=fedora
VERSION_ID=34
PLATFORM_ID="platform:f34"
PRETTY_NAME="Fedora Remix for WSL"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:34"
HOME_URL="https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL"
SUPPORT_URL="https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL"
BUG_REPORT_URL="https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/issues"
PRIVACY_POLICY_URL="https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/blob/master/PRIVACY.md"
FEDORA_REMIX_VERSION=34.13.4
% perl -v
This is perl 5, version 34, subversion 1 (v5.34.1) built for amd64-freebsd-thread-multi
Copyright 1987-2022, Larry Wall
Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.
% uname -smr
FreeBSD 13.1-RC5 amd64