空白での分割は、_str.strip
_を使用して2つの方法で実行できます。引数なしで呼び出しを発行するか、str.strip()
を発行できます。これは、デフォルトで空白区切り文字を使用するか、引数にstr.strip(' ')
を明示的に指定します。
しかし、なぜこれらの機能が非常に異なって実行されるのですか?
意図的な量の空白を含むサンプル文字列の使用:
_s = " " * 100 + 'a' + " " * 100
_
s.strip()
とs.strip(' ')
のタイミングはそれぞれ次のとおりです。
_%timeit s.strip()
The slowest run took 32.74 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 396 ns per loop
%timeit s.strip(' ')
100000 loops, best of 3: 4.5 µs per loop
_
strip
は_396ns
_を取り、strip(' ')
は_4.5 μs
_を取りますが、同じ条件下でrsplit
とlsplit
を使用した場合も同様のシナリオが存在します。 。また、 _bytes objects
_も影響を受けているようです 。
タイミングは_Python 3.5.2
_に対して実行され、_Python 2.7.1
_では差はそれほど大きくありません。 _str.split
_ のドキュメントは有用なものを何も示していないので、なぜこれが発生するのですか?
これは、 _unicode_strip
_ ;に見られるように、2つの異なるケースに対して2つの関数が存在するためです。 _do_strip
_および__PyUnicodeXStrip
_最初の実行は2番目の実行よりもはるかに高速です。
関数_do_strip
_は、引数が存在しない一般的なケースstr.strip()
用です。 _do_argstrip
_ (__PyUnicode_XStrip
_をラップします)str.strip(arg)
が呼び出された場合つまり、引数が提供されます。
_do_argstrip
_はセパレーターをチェックするだけで、それが有効でNone
(この場合は_do_strip
_を呼び出します)と等しくない場合は __PyUnicode_XStrip
_ を呼び出します。
_do_strip
_と__PyUnicode_XStrip
_はどちらも同じロジックに従い、2つのカウンターが使用されます。1つはゼロに等しく、もう1つは文字列の長さに等しくなります。
2つのwhile
ループを使用して、最初のカウンターはセパレーターと等しくない値に達するまでインクリメントされ、2番目のカウンターは同じ条件が満たされるまでデクリメントされます。
違いは、現在の文字が区切り文字と等しくないかどうかをチェックする方法にあります。
do_strip
_の場合:分割される文字列の文字をascii
で表すことができる最も一般的なケースでは、追加の小さなパフォーマンスの向上が見られます。
_while (i < len) {
Py_UCS1 ch = data[i];
if (!_Py_ascii_whitespace[ch])
break;
i++;
}
_
Py_UCS1 ch = data[i];
__Py_ascii_whitespace[ch]
_ という配列に行われます。つまり、非常に効率的です。
文字がascii
の範囲にない場合、違いはそれほど劇的ではありませんが、全体的な実行が遅くなります。
_while (i < len) {
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
if (!Py_UNICODE_ISSPACE(ch))
break;
i++;
}
_
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
で行われますPy_UNICODE_ISSPACE(ch)
マクロ(単に別のマクロを呼び出す: _Py_ISSPACE
_ )によって行われます。_PyUnicodeXStrip
_の場合:この場合、基になるデータへのアクセスは、前の場合と同様に、_PyUnicode_Read
_で行われます。一方、文字が空白(または実際に提供された文字)であるかどうかを確認するチェックは、かなり複雑です。
_while (i < len) {
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
if (!BLOOM(sepmask, ch))
break;
if (PyUnicode_FindChar(sepobj, ch, 0, seplen, 1) < 0)
break;
i++;
}
_
_PyUnicode_FindChar
_ が使用されます。これは効率的ですが、配列アクセスに比べてはるかに複雑で低速です。文字列内の各文字について、その文字が指定された区切り文字に含まれているかどうかを確認するために呼び出されます。文字列の長さが長くなると、この関数を継続的に呼び出すことによって生じるオーバーヘッドも大きくなります。
興味のある人のために、かなりのチェックの後、_PyUnicode_FindChar
_は最終的にstringlib
内で _find_char
_ を呼び出します。セパレータの長さが_< 10
_の場合、キャラクターを見つけます。
これとは別に、ここに到達するためにすでに呼び出される必要がある追加の関数を検討してください。
lstrip
とrstrip
については、状況は似ています。実行するストライピングのモードが存在するフラグ。つまり、RIGHTSTRIP
の場合はrstrip
、LEFTSTRIP
の場合はlstrip
、BOTHSTRIP
の場合はstrip
。 _do_strip
_および__PyUnicode_XStrip
_内のロジックは、フラグに基づいて条件付きで実行されます。
@Jimsの回答で説明されている理由により、同じ動作がbytes
オブジェクトに見られます。
b = bytes(" " * 100 + "a" + " " * 100, encoding='ascii')
b.strip() # takes 427ns
b.strip(b' ') # takes 1.2μs
bytearray
オブジェクトの場合、これは発生しません。この場合、split
を実行する関数は、どちらの場合も同様です。
さらに、Python 2
私のタイミングによると、同じことが少しは当てはまります。