perl でスレッドを使う

大きなファイルを扱うのに、CPU は 10% 台で、HDD アクセスも「アクティブな時間」が 10% まで行かない。ということで、マルチスレッド化してみよう。

入力ファイル kenpo2 と言うファイルをそれぞれのスレッドで読み込みながら、そのまま kenpo2.th-out に書き込む。行は入れ替わっても全ての行がコピーされる筈。
それぞれのスレッドでファイルを seek して、＄in_file_pos から ＄in_file_pos + ＄size_per_th まで読み込む。(行末までは前のスレッドで読む)

# スレッド処理するプログラムを書いてみた
色々動かなかったりしたけど、なんとか動いたのがこんな感じ。

```perl:threads-teset
#!/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  のバグに阻まれる)
[出版労連](http://www.syuppan.net/kyoto/s2-kan-05.htm) のページにあった日本国憲法を kenpo というテキストファイルにして適当に折り返し、ざっくりと 9999倍にして kenpo2 というファイルを作成。
```tcsh:入力ファイルの作成 (失敗)
% 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
```tcsh:入力ファイルの作成 (成功)
% 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 を失ふ。
%
```
うむ。それっぽいのができたので、このファイルを使って実行！

```tcsh:tcsh ならこんな書き方も
% repeat 9999 fold -w 50 kenpo | cat -n > kenpo2
% repeat 9999 echo kenpo | xargs cat | fold -w 50 | cat -n > kenpo2
```
一番下のが一番早いでしょうか。シェルの内部コマンドで成る可くプロセスをフォークしない、xargs で、フォークするプロセスの数を減らす。ということで xargs 最強です。ってなんの話や？

# 実行
```tcsh:
% ./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.
...ｓｎｉｐ...
---- 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文字を超える行を引っ張り出してみた。
```tcsh:
% 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 より導入)

```tcsh:環境1
% /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
```

```tcsh:環境2
% 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
```