次のコードの場合:
set str "a bb ccc"
if {[string first bb "$str"] >= 0} {
puts "yes"
}
私の大学は、パフォーマンスの違いがあるため、$str を二重引用符で囲むべきではないと言いました。TCL のようなものは、$str を使用して内部的に新しいオブジェクトを作成します。
これについて説得力のある文書を見つけることができません。主張が正しいかどうか知っていますか?
次のコードの場合:
set str "a bb ccc"
if {[string first bb "$str"] >= 0} {
puts "yes"
}
私の大学は、パフォーマンスの違いがあるため、$str を二重引用符で囲むべきではないと言いました。TCL のようなものは、$str を使用して内部的に新しいオブジェクトを作成します。
これについて説得力のある文書を見つけることができません。主張が正しいかどうか知っていますか?
あなたの同僚は実際には間違っています.Tclのパーサーは"$str"
が $str
生成されたバイトコードを見てみましょう (これは Tcl 8.6.0 を使用したものですが、詳細を確認する部分は実際には 8.0a1 までの古いバージョンでも同じです):
% tcl::unsupported::disassemble script {
set str "a bb ccc"
if {[string first bb "$str"] >= 0} {
puts "yes"
}
}
ByteCode 0x0x78710, refCt 1, epoch 15, interp 0x0x2dc10 (epoch 15)
Source "\nset str \"a bb ccc\"\nif {[string first bb \"$str\"] >= 0} "
Cmds 4, src 74, inst 37, litObjs 7, aux 0, stkDepth 2, code/src 0.00
Commands 4:
1: pc 0-5, src 1-18 2: pc 6-35, src 20-72
3: pc 15-20, src 25-46 4: pc 26-31, src 61-70
Command 1: "set str \"a bb ccc\""
(0) push1 0 # "str"
(2) push1 1 # "a bb ccc"
(4) storeScalarStk
(5) pop
Command 2: "if {[string first bb \"$str\"] >= 0} {\n puts \"yes\"\n}"
(6) startCommand +30 2 # next cmd at pc 36, 2 cmds start here
Command 3: "string first bb \"$str\""
(15) push1 2 # "bb"
(17) push1 0 # "str"
(19) loadScalarStk
(20) strfind
(21) push1 3 # "0"
(23) ge
(24) jumpFalse1 +10 # pc 34
Command 4: "puts \"yes\""
(26) push1 4 # "puts"
(28) push1 5 # "yes"
(30) invokeStk1 2
(32) jump1 +4 # pc 36
(34) push1 6 # ""
(36) done
ご覧のとおり ( (17)
–<code>(19) を参照)、"$str"
は変数名のプッシュと逆参照 ( loadScalarStk
) にコンパイルされます。これは、ローカル変数テーブルがない (つまり、プロシージャ内にない) 場合に最適なシーケンスです。コンパイラは非ローカル最適化を行いません。
あなたの同僚は正しいと思います: Tcl が$str
単語が期待される場所を明白に見た場合、その "str" を変数の名前として解析し、適切なスコープでそれを検索し、その変数からその値を表す内部オブジェクトを抽出します。次に、その値の文字列表現を生成するようにそのオブジェクトに要求します。この時点で、その文字列表現はすでに利用可能で (オブジェクト内に) キャッシュされているか、またはオブジェクトによって透過的に生成されてキャッシュされます。
変数の逆参照 ( ) を二重引用符で囲まれた文字列に入れると$str
、Tcl は次のようになります:"
単語が期待される場所で最初の文字を見つけると、次の文字を解析するモードに入り、変数を実行します。次のエスケープされていない が表示されるまで、コマンドの置換が"
行われます。この時点で、開始以降に蓄積された置換テキスト"
は 1 つの単語と見なされ、その単語の値を表す (新しく作成された) 内部オブジェクトになります。
ご覧のとおり、2 番目の (あなたの) ケースでは、"str" という名前の変数の値を保持している元のオブジェクトがその値を求められ、最初のケースでは最初の値が別の値を構築するために使用されます。すぐに使用されます。
ここで、もっと微妙な問題があります。評価するスクリプトについては、Tcl はそのインタープリターが特定の評価規則に従うことのみを保証し、それ以上は保証しません。他のすべては実装の詳細です。これらの詳細は、バージョンごとに異なる場合があります。たとえば、Tcl 8.6 では、非再帰的評価 (NRE) を使用してエンジンが再実装されました。これらは Tcl 内部の大幅な変更でしたが、既存のスクリプトはそれに気づきませんでした。
私があなたを導きたいのは、現在私たちが行っているような暗黙的なパフォーマンスの「ハック」についての議論は、ランタイムの特定のバージョンに適用された場合にのみ意味があるということです。現在、Tcl"$str"
が単にオブジェクトを再利用するように最適化されて$str
いるとは思えませんが、理論的には最終的に開始される可能性があります。
あなたのアプローチの本当の「問題」は、パフォーマンスの低下ではなく、疑わしいスタイルのTclコードにつながる、自分自身に適用しているように見える明らかな自己妄想です。説明させてください。「より伝統的な」言語 (通常は C などの影響を受ける) とは対照的に、Tcl には文字列用の特別な構文はありません。これは、文字列リテラルがないためです。スクリプト内でリテラルから始まるすべての値は、最初は文字列です。値の実際の型は、それらの値を操作するコマンドによって実行時に定義されます。デモンストレーションのためset x 10; incr x
に、文字列「10」を「x」という名前の変数に入れます。incr
コマンドは、その変数「x」の値を強制的に、それが保持する文字列「10」を(値10の)整数に変換します。次に、この整数は 1 ずつインクリメントされ (11 を生成)、副作用として文字列表現が無効になります。後で行う場合puts $x
、文字列表現は整数から再生成され(「11」を生成)、値にキャッシュされてから出力されます。
したがって、あなたが採用したコード スタイルは、Tcl コードをより Python (または Perl など、以前の言語であったもの) に似せようと実際に試みています。Tcl では、二重引用符と中括弧の両方がグループ化のために使用されますが、それぞれ文字列値とコード ブロックを生成するためではありません。これらは、さまざまなグループ化方法の特定の使用例にすぎません。背景については、このスレッドを読むことを検討してください。
更新:さまざまなタイプのグループ化は、全体として読む価値のあるチュートリアルで非常によく説明されています。