JSON::PPのencodeメソッドの速度比較

昨日はshogo82148さんで[Perl 5.40からiterating over multiple values at a time機能が安定版になりました](https://shogo82148.github.io/blog/2024/12/10/perl-iterating-over-multiple-values-is-no-longer-experimental/)でした。
今日は6日、7日のついでの投稿になります。申し訳ないです。
# 概要
JSON::PPのエンコードは二通りあります。
```perl
$json_text = encode_json $perl_scalar
$json_text = JSON::PP->new->utf8->encode($perl_scalar)
```
メソッドの説明ではこれらは一緒だけど、速さが違うとありましたので、どの程度違うのか調べることにします。
ソースコードはutf8で記述し、データには日本語を含むものとします。

# 比較するメソッド
- encode_json
- JSON::PP->new->utf8->encode
    - 以上2つはJSON文字列をutf8としてエンコードします。
- Encode::encode_utf8( JSON::PP->new->utf8(0)->encode )
    - JSON::PP->new->utf8(0)とすることで、utf8にエンコードせず、内部コードとして文字列が作られるので、さらにEncodeモジュールのencode_utf8を用いてutf8文字列を得ます

# 方法とコード（12月12日修正）
　50レコードのデータを作成し、5000回エンコードし、その速度を比較します。
　@mackee_wさんにコメントで教えていただきコードを修正しました。前のコードは文末にあります。
　Benchmarkモジュールを使って書き換えてあります。コメントありがとうございました。コードが見やすくなりました。
```perl
#!perl
use v5.40;
use JSON::PP;
use utf8;
use Encode;
use Benchmark qw(timethese);

my @sum = (0, 0, 0);
my $d = [{a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"}];

   timethese(5000, {
    'method1' => sub {encode_json($d)},
    'method2' => sub {JSON::PP->new->utf8->encode($d)},
    'method3' => sub {encode_utf8(JSON::PP->new->utf8(0)->encode($d))}
   });
```

# 結果
```
Benchmark: timing 5000 iterations of Name1, Name2, Name3...
　method1:  1 wallclock secs ( 1.55 usr +  0.00 sys =  1.55 CPU) @ 3232.06/s (n=5000)
　method2:  2 wallclock secs ( 1.58 usr +  0.00 sys =  1.58 CPU) @ 3168.57/s (n=5000)
　method3:  1 wallclock secs ( 1.47 usr +  0.00 sys =  1.47 CPU) @ 3405.99/s (n=5000)
```
| メソッド | 順位 |
| --- |  :---: |
| encode_json |  2 |
| JSON::PP->new->utf8->encode |  3 |
| encode_utf8(<br>　JSON::PP->new->utf8(0)->encode<br>) | 1 |

# まとめ
　1件あたりの速度の違いは微々たるものです。
　3種類比較し、3番目の方法「JSON::PPでutf8の文字列を得るのではなく、内部コードのJSON文字列とし、改めてEncodeモジュールでutf8にエンコード」が早いことに驚きました。
　実験としては不備不足等あるかと存じます。コメント欄で指摘いただけるとありがたいです。
　以上です。ありがとうございました。

# 謝辞
　@mackee_wさん、ありがとうございます。あまりの便利さに震えました。コードもスッキリしましたし、Advent Calendarに参加して良かったと思います。ありがとうございました。

# 修正前のコードと結果
```perl
#!perl
use v5.40;
use JSON::PP;
use utf8;
use Encode;
use Time::HiRes qw/gettimeofday tv_interval/;

my @sum = (0, 0, 0);
my $d = [{a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"},
         {a=>1, b=>"テスト"}, {a=>1, b=>"試験"}, {a=>2, b=>"文字列"}, {a=>2, b=>"任意"}, {a=>3, b=>"仮文"}];

for(my $i=0; $i<5000; $i++){
    &pGo;
}

sub pGo{
    my $starttime = [gettimeofday];

    my $json1 = encode_json($d);
    my $raptime1 = tv_interval($starttime);
    
    my $json2 = JSON::PP->new->utf8->encode($d);
    my $raptime2 = tv_interval($starttime);
    
    my $json3 = encode_utf8(JSON::PP->new->utf8(0)->encode($d));
    my $raptime3 = tv_interval($starttime);;
    
    $sum[0] += $raptime1;
    $sum[1] += $raptime2-$raptime1;
    $sum[2] += $raptime3-$raptime2;
    
}
printf("A:%0.6f\n",$sum[0]);
printf("B:%0.6f\n",$sum[1]);
printf("C:%0.6f\n",$sum[2]);
```
| メソッド | 5000回処理時間(秒) | 順位 |
| --- | ---: | :---: |
| encode_json | 1.602037 | 2 |
| JSON::PP->new->utf8->encode | 1.610351 | 3 |
| encode_utf8(<br>　JSON::PP->new->utf8(0)->encode<br>) | 1.481043 | 1 |