改行って一言で言っても内部的な改行コードはCr(\r)、Lf(\n)、CrLf(\r\n)の三種類があると言われる。そこで、今回はどの改行コードの場合でも確実に配列(リスト)に分割する方法を調査した。また、同一言語でもsplitメソッドには文字列と正規表現など、複数の実装が存在していることがある。この時の速度を比較して最適な手法を求める。
D言語
std.string.splitLines()
//import std.string;
auto wordList=words.splitLines();
//1000000ループの処理時間: 565ms(dmd)
D言語には言語機能で改行で分割するためのメソッドが用意されている。
終端の要素が空欄となる場合は省略される。
std.string.split(string,string)
//import std.string;
auto wordList=words.replace("\r\n","\n").replace("\r","\n").split("\n");
//1000000ループの処理時間: 2680ms(dmd)
split単体ではすべての改行コードに対応できないため、replaceと併用する。
splitLinesに比べてかなり遅い。
###std.regex.split(string,Regex!char)
//import std.regex;
auto wordList=words.split(regex(r"\r\n|\n|\r"));
//1000000ループの処理時間: 8288ms(dmd)
正規表現での分割はさらに遅い。
特別使用したい理由がないのならわざわざ使う必要はないだろう。
一応正規表現の部分を変数に入れてやると誤差程度に速くなる。
.NET(C#,VisualBasic)
System.String.Split(Char | Char[])
//using System;
var wordList=words.Replace("\r\n","\n").Split(new[]{'\n','\r'});
//1000000ループの処理時間: 474ms(csc/.NET Framework)
Dim wordList=words.Replace(vbCrLf,vbLf).Split({vbLf(0),vbCr(0)})
'1000000ループの処理時間: 472ms(vbc/.NET Framework)
.NetのSplitは標準では文字(Char型)でしか置き換えの対象を指定することができない。
そのため、Replaceで"\r\n"(vbCrLf)を"\n"(vbLf)に統合しておく。
ちなみにVBの改行用の定数のvbCr/vbLf/vbCrLfは全て型がStringなのでCharとして指定する必要がある。
System.String.Split(String[], StringSplitOptions)
//using System;
var wordList=words.Split(new[]{"\r\n","\n","\r"},StringSplitOptions.None);
//1000000ループの処理時間: 504ms(csc/.NET Framework)
Dim wordList=words.Split({vbCrLf,vbLf,vbCr},StringSplitOptions.None)
'1000000ループの処理時間: 508ms(vbc/.NET Framework)
上記のSplitの第2引数にStringSplitOptions.Noneという長ったらしい定数を入れると文字列(String)による分割が使用できる。
ただし、文字列の指定は必ず配列の形で渡さなくてはいけない。
ちなみに速度についてはChar[]での分割と比較しても誤差レベルなのでケースバイケースで使い分けるといいと思う。
System.Text.RegularExpressions.Regex.Split(String)
//using System;
//using System.Text.RegularExpressions;
var wordList=Regex.Split(words,$"\r\n|\n|\r");
//1000000ループの処理時間: 3605ms(csc/.NET Framework)
'Imports System.Text.RegularExpressions
Dim wordList=Regex.Split(words,"\r\n|\n|\r");
//1000000ループの処理時間: 3748ms(vbc/.NET Framework)
正規表現版Split。正規表現での分割はかなり遅いので理由がなければあえて使う必要はない。
一応正規表現の部分を変数に入れてやる(new Regex($"\r\n|\n|\r")と誤差程度に速くなる。
Microsoft.VisualBasic.Strings.Split(String,String)
Dim wordList=Split(words.Replace(vbCrLf,vbLf).Replace(vbCr,vbLf),vbLf)
'1000000ループの処理時間: 1256ms(vbc/.NET Framework)
VB6時代から引き継いでいるVB用のSplit。
単体では複数文字列で分割することはできないのでReplaceを併用する。
記述は簡潔だけど少し遅い。
JavaScript
string.split(RegExp)
var wordList=words.split(/\r\n|\n|\r/);
//1000000ループの処理時間: 364ms(node)
JavaScriptでは初めから正規表現で分割や置換をする方が一般的。
他の言語に比べても正規表現なのになんか爆速で凄い。
string.split(string)
var words.replace(/\r\n/g,"\n").replace(/\r/g,"\n").split("\n");;
//1000000ループの処理時間: 1363ms(node)
本来こんなことはあまりやらないけど、replaceを併用してstringでsplit。
replaceを無駄に挟んでいるせいかかなり遅くなっている。
Python3
str.splitlines()
wordList=words.splitlines()
#1000000ループの処理時間: 476ms(py -3) 211ms(pypy3)
PythonにはD言語と同様にsplitlinesという改行類をまとめて置き換えられるメソッドが用意されている。
終端の要素が空欄となる場合は省略される。
インタプリタ言語なのであまり速度は期待していなかったけど、普通に早くて驚いた。
pypy3に至っては今回の比較対象だと最速じゃないかな。
文字列処理にはインタプリタ言語が使用されていることが多いから最適化されているのだろうか。
str.split(str)
wordList=words.replace("\r\n","\n").replace("\r","\n").split("\n")
#1000000ループの処理時間: 863ms(py -3) 238ms(pypy3)
splitでは複数文字列での分割ができないため、replaceで"\n"に統合してから分割。
インタプリタ言語なことを考えるとこれでも破格の速度に見える。
re.split(str,str)
import re
wordList=re.split(r"\r\n|\n|\r",words)
#1000000ループの処理時間: 2839ms(py -3) 1081ms(pypy3)
正規表現版split。
やはり正規表現はコストが大きい。
特に理由がなければ使わない方が良い。
一応正規表現の部分を変数に入れてやる(re.compile(r"\r\n|\n|\r")と誤差程度に速くなる。
HSP3
split str,str,ref array
sdim lf:poke lf,,10
sdim wordList
s=words
strrep s,"\n",lf
strrep s,"\r",lf
split s,lf,wordList
;1000000ループの処理時間: 1777ms(hsp3cl)
HSP3は改行コードが独特で"\n"=CrLfらしい。
strlepで改行コードをlfにまとめてsplitする。
CommonLisp
(uiop:split-string string :separator string)
(require :asdf)
(asdf:load-system :uiop)
(defconstant cr (princ-to-string #\Return))
(defconstant lf (princ-to-string #\Linefeed))
(defconstant crlf (format nil "~s~s" cr lf))
(defparameter wordList
(uiop:split-string
(uiop:frob-substrings words (list crlf cr) lf)
:separator lf))
;1000000ループの処理時間: 6125ms(sbcl) 9359ms(wx86cl64)
CommonLispの言語仕様には文字を分割する関数は存在しないが、
SBCL、ClozureCL等の一部主要なCL処理系にデフォルトでuiopというライブラリが入っている。
この中にsplit相当のstring-split関数とreplace相当のfrob-substrings関数が含まれているので
これで改行コードを置換して分割する。
速度としてはかなり遅いが、用途によっては気にならないだろう。
あとがき
文字列処理は言語による速度の影響を受けにくいというのは新たな収穫でした。
特にNode.jsの正規表現がめっちゃ優秀とかpypy3の最適化も凄い、とかCPython3は遅くないとか。
実行コード
動作環境
Windows10(x64)
以下略