というわけで解説。

2013/03/04:Unicode::UTF8 がガチ爆速すぎる - bayashi.net
encode より decode のが差が大きい感じ。encode だけだと、文字列長くなると Encode の方が速いっぽい。

まずは改めて検証してみましょう。

https://gist.github.com/dankogai/5079930

確かにその通りになっています。Unicode::UTF8EncodeはおろかPerl組み込みのutf8::decodeより高速なのか(文字列をコピーしなければutf8::decodeが最速だけど、破壊的操作なのでベンチマークではコピーをとっている点に留意)。逆になぜencodeではEncodeの方が高速なのか。

答えは、 validation にあります。 decode では入力が正しい UTF-8 になっているかをいずれもチェックしていますが、Unicode::UTF8ではこの部分が最適化されています。その一方、Encodeでは他の文字コードとの共通APIのために余計なオーバーヘッドがあります。特に大きいのは PerlIO とのやりとりで、そのためのメソッド呼び出しが必ず一回以上発生します。

その一方 encode に validation は不要で、必ず成功します。このことは、プラグマではなくモジュールとしてのutf8を見てもわかります。

$success = utf8::decode($string)

Attempts to convert in-place the octet sequence in UTF-X to the
corresponding character sequence. That is, it replaces each
sequence of characters in the string whose ords represent a valid
UTF-X byte sequence, with the corresponding single character.  The
UTF-8 flag is turned on only if the source string contains
multiple-byte UTF-X characters.  If $string is invalid as UTF-X,
returns false; otherwise returns true.
utf8::encode($string)

Converts in-place the character sequence to the corresponding octet
sequence in UTF-X. That is, every (possibly wide) character gets
replaced with a sequence of one or more characters that represent
the individual UTF-X bytes of the character.  The UTF8 flag is
turned off.  Returns nothing.

それでは、実際の利用シーンではどうするのが一番よいのでしょう?

結論を先に書くと、以下のとおりとなるでしょうか。

  • UTF-8文字列がソースコード中に全てある場合、use utf8;プラグマを指定するだけ。すでにdecodeされた状態になっているので、再デコードは不要。
  • UTF-8文字列をファイルから読み込む場合
    • 検証が不要なら、ファイルハンドルに:utf8を指定
      binmode STDIN,  ':utf8'; # NO VALIDATION APPLIED
      binmode STDOUT, ':utf8';
      while (<>) {
          # do something to $_
          print $_
      }
      close $rfh;
      :encoding(utf8)とは異なる点に留意。この場合は検証も行われます。
    • 必要なら、読んだ後utf8::decode()を適用
      open my $rfh, "<", $infile or die "$infile:$_";
      open my $rwh, ">:utf8", $outfile or die "$outfile:$_"; # no validation needed
      while (<$rfh>) {
          utf8::decode($_) or die "Malformed UTF-8 at line $.";
          # do something to $_
          print {$wfh} $_
      }
      close $rfh;
      close $wfh;
      
  • その他外部から取り込む場合、utf8::decode()で検証
    map { utf8::decode($_) or die "Malformed UTF-8" } @ARGV;
    

Unicode::UTF8::decode_utf8()は確かに高速なのですが、Encodeの代わりならとにかく、utf8::decode()を置き換えるほどではありませんし(Signature後ろのベンチマーク参照)、UTF-8しか絡まないシーンで Encode は今となっては Overkill です。

Dan the Man with too Many Bytes to Transcode

追記:

404 Blog Not Found:#perl - utf8::decode()ではなくEncode::decode_utf8()を使うべき理由
見ての通り、utf8::decode()は、不正なUTF-8バイト列に対して何もしません

が、現在のutf8::decode()は少なくとも不正かどうかは返り値で判定できるようになっています(たしか utf-8-strict が加わったあたりから)。本記事の例ではそれを利用して不正なUTF-8を弾いています。例えばUTF-8として不正なバイト列を\x80のように置き換えたい場合などには Encode::decode_utf8 ないし find_encoding('UTF-8')->decode は依然意味がありますが、「不正ならそこでdie」のような果断でもよければ--おそらく大抵の場合が該当--、utf8::decodeでも充分目的が達成できるというわけです。

https://gist.github.com/dankogai/5080017