1

对于以下代码:

set str "a bb ccc"
if {[string first bb "$str"] >= 0} {
    puts "yes"
}

我的大学说我不应该双引号 $str 因为存在性能差异,比如 TCL 在内部使用 $str 创建一个新对象。

我在这方面找不到令人信服的文件。你知道索赔是否准确吗?

4

2 回答 2

4

你的同事实际上是错的,因为 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)。鉴于没有局部变量表(即,我们不在过程中),这是最优化的序列。编译器不进行非本地优化。

于 2013-01-30T14:14:05.917 回答
2

我认为您的同事是正确的:如果 Tcl$str在预期单词的地方看到了简单的内容,它会将“str”解析为变量的名称,在适当的范围内查找它,然后从该变量中提取一个表示其值的内部对象然后要求该对象生成该值的字符串表示形式。此时,字符串表示要么已经可用并被缓存(在对象中)——在你的情况下,它将被缓存——或者它将由对象透明地生成并缓存。

如果将变量 ( $str) 的取消引用放在双引号字符串中,那么 Tcl 会这样:当它"在预期单词的地方看到第一个时,它会进入解析以下字符的模式,执行变量-和命令替换,直到它看到下一个 unescaped ",此时从开头累积的替换文本"被认为是一个单词,它最终位于表示该单词值的(新创建的)内部对象中。

如您所见,在第二种(您的)情况下,将询问保存名为“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 开发人员来说也显得格格不入。双引号和花括号都在 Tcl中用于分组,而不是分别用于生成字符串值和代码块——这些只是不同分组方式的特定用例。考虑阅读此线程以获取更多背景信息。

更新:教程中对各种类型的分组进行了很好的解释,值得一读。

于 2013-01-30T08:23:17.240 回答