Tcl Donal关于expr命令性能的后续介绍

Tcl Donal关于expr命令性能的后续介绍,tcl,Tcl,我刚刚读到了关于这件事的精彩回复。它信息量很大 但是关于这一部分我有一个问题:永远不要使用带有if、for或while的动态表达式,因为这样会抑制大量编译。 我读了两遍,但我不认为我完全明白 多纳尔或其他人,你能再详细一点吗 [更新1] 出于好奇,我在tkcon内部尝试了Donal在回答中给出的示例: % set a {1||0} 1||0 % set b {[exit 1]} [exit 1] % expr {$a + $b} can't use non-numeric string as o

我刚刚读到了关于这件事的精彩回复。它信息量很大

但是关于这一部分我有一个问题:
永远不要使用带有if、for或while的动态表达式,因为这样会抑制大量编译。

我读了两遍,但我不认为我完全明白

多纳尔或其他人,你能再详细一点吗

[更新1] 出于好奇,我在tkcon内部尝试了Donal在回答中给出的示例:

% set a {1||0}
1||0
% set b {[exit 1]}
[exit 1]
% expr {$a + $b}
can't use non-numeric string as operand of "+"
% expr $a + $b
1
有趣的是,为什么“
expr$a+$b
”最后变成了“1”?“
expr$a+$b
”不是扩展为“
expr 1 | | 0+[exit 1]
”吗?如果我只是运行扩展版本,tkcon就会关闭,这对我来说很有意义,因为
[exit 1]
正在运行

[更新2]我仍在思考更新1中的问题。按照建议,我又做了一个实验:

% concat $a + $b
1||0 + [exit 1]
% expr 1||0 + [exit 1]
...tkcon closes...

tkcon关闭是我所期望的,我仍然想知道为什么
expr$a+$b
会产生1。

强烈建议不要以这种方式编写表达式,因为这样很容易出错并造成(潜在的)安全漏洞

set x 1
set y {[exec echo >@stdout rm -rf /]};   # Assume this string has come from the user
expr $x+$y
# After Tcl language substitution, it's equivalent to this:
#     expr {1+[exec echo >@stdout rm -rf /]}
# If you're not sure why that might be a problem, think a little more...
它也不是强编译的,因为Tcl的字节码编译器(通常)不会进行大量的常量折叠,相反,您会得到一个操作码调用,该操作码获取一个字符串,并在运行时将该字符串编译为字节码以供执行。它既不安全也不高效

然而,还有更多。如果我们看看这个:

if $x==$y {
    # ...
}
如果
if
的主体没有被编译,因为
if
编译器代码只是看到替换并退出,将事情推回到(有效的)解释模式执行。如果,则会减慢整个
臂的速度。如果您正在使用组合表达式(出于安全原因,我不鼓励使用组合表达式),那么至少要执行以下操作:

if {[expr $x==$y]} {
    # ...
}
这至少使
if
保持其有效模式。(在其他方面,它在语义上是等价的。)


上面的字节码 首先,对于
expr

% tcl::unsupported::disassemble script {expr $x+$y}
ByteCode 0x0x1008b2210, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "expr $x+$y"
  Cmds 1, src 10, inst 12, litObjs 3, aux 0, stkDepth 3, code/src 0.00
  Commands 1:
      1: pc 0-10, src 0-9
  Command 1: "expr $x+$y"
    (0) push1 0     # "x"
    (2) loadStk 
    (3) push1 1     # "+"
    (5) push1 2     # "y"
    (7) loadStk 
    (8) strcat 3 
    (10) exprStk 
    (11) done 
% tcl::unsupported::disassemble script {if $x==$y {
    incr hiya
}}
ByteCode 0x0x10095e210, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "if $x==$y {\n        incr hiya\n   "...
  Cmds 1, src 35, inst 17, litObjs 5, aux 0, stkDepth 4, code/src 0.00
  Commands 1:
      1: pc 0-15, src 0-34
  Command 1: "if $x==$y {\n        incr hiya\n   "...
    (0) push1 0     # "if"
    (2) push1 1     # "x"
    (4) loadStk 
    (5) push1 2     # "=="
    (7) push1 3     # "y"
    (9) loadStk 
    (10) strcat 3 
    (12) push1 4    # "\n        incr hiya\n  "...
    (14) invokeStk1 3 
    (16) done 
请注意,在第一个版本中,我们使用了
exprStk
(一种通用字符串操作),而第二个版本使用了
add
(它知道它正在处理数字,否则会抛出错误)

然后,对于
if

% tcl::unsupported::disassemble script {expr $x+$y}
ByteCode 0x0x1008b2210, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "expr $x+$y"
  Cmds 1, src 10, inst 12, litObjs 3, aux 0, stkDepth 3, code/src 0.00
  Commands 1:
      1: pc 0-10, src 0-9
  Command 1: "expr $x+$y"
    (0) push1 0     # "x"
    (2) loadStk 
    (3) push1 1     # "+"
    (5) push1 2     # "y"
    (7) loadStk 
    (8) strcat 3 
    (10) exprStk 
    (11) done 
% tcl::unsupported::disassemble script {if $x==$y {
    incr hiya
}}
ByteCode 0x0x10095e210, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "if $x==$y {\n        incr hiya\n   "...
  Cmds 1, src 35, inst 17, litObjs 5, aux 0, stkDepth 4, code/src 0.00
  Commands 1:
      1: pc 0-15, src 0-34
  Command 1: "if $x==$y {\n        incr hiya\n   "...
    (0) push1 0     # "if"
    (2) push1 1     # "x"
    (4) loadStk 
    (5) push1 2     # "=="
    (7) push1 3     # "y"
    (9) loadStk 
    (10) strcat 3 
    (12) push1 4    # "\n        incr hiya\n  "...
    (14) invokeStk1 3 
    (16) done 
请注意,第二个版本是如何理解它正在执行增量(
incrStkImm
)的?这对性能有很大帮助,特别是对于较长、不那么琐碎的脚本。第一个版本只是汇编一个参数列表,并使用
invokeStk1
调用解释的
if
实现

FWIW,“金标准”(假设我们不在程序中)是这样的:

为了完整性,在一个过程中(本例中是lambda,但字节码是相同的):


对于更新问题,请查看运行
concat$a+$b
(这基本上是计算
expr
s参数的第一步)会得到什么。然后查看
expr
手册页上关于“惰性评估”的部分(关于需要大括号的说明在这里不适用)。对于更新Q,请注意
expr$a+$b
expr{$a+$b}
完全不同。Tcl替代规则适用,在这种情况下,它们很重要。另外,
+
操作符只对数字起作用……
expr$a+$b
expr[concat$a+$b]
相同,因此(在您的情况下)与
expr{1 | 0+[exit 1]}
相同,但与
expr 1 | 0+[exit 1]
不同。牙套很重要。替换很重要。举个例子,如果$a==$b{…},我想知道为什么“if”命令没有编译“$a==$b”就退出了。根据if命令规范,第一个参数是一个表达式,因此它应该只接受第一个参数“$a==$b”并编译它,就像它被大括号括起来一样。@我的问题是,它退出了,因为在这种情况下,编译器没有足够的智能来执行其他操作。很少有人想要那种东西。(抱歉,这是一个很普通的原因…)明白了,谢谢。你能在UPDATE2中回答我的问题吗?@我的问题你的UPDATE2表明你需要花一些时间回顾一下基本的Tcl替换语义;您已经找到了一个测试用例,其中微妙之处非常重要。
% tcl::unsupported::disassemble script {if {$x==$y} {
    incr hiya
}}
ByteCode 0x0x1008efb10, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "if {$x==$y} {\n    incr hiya\n"...
  Cmds 2, src 29, inst 18, litObjs 4, aux 0, stkDepth 2, code/src 0.00
  Commands 2:
      1: pc 0-16, src 0-28        2: pc 9-12, src 18-26
  Command 1: "if {$x==$y} {\n    incr hiya\n"...
    (0) push1 0     # "x"
    (2) loadStk 
    (3) push1 1     # "y"
    (5) loadStk 
    (6) eq 
    (7) jumpFalse1 +8   # pc 15
  Command 2: "incr hiya"...
    (9) push1 2     # "hiya"
    (11) incrStkImm +1 
    (13) jump1 +4   # pc 17
    (15) push1 3    # ""
    (17) done 
tcl::unsupported::disassemble lambda {{} {if {$x==$y} {
    incr hiya
}}}
ByteCode 0x0x1008ecc10, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "if {$x==$y} {\n    incr hiya\n"...
  Cmds 2, src 29, inst 15, litObjs 1, aux 0, stkDepth 2, code/src 0.00
  Proc 0x0x102024610, refCt 1, args 0, compiled locals 3
      slot 0, scalar, "x"
      slot 1, scalar, "y"
      slot 2, scalar, "hiya"
  Commands 2:
      1: pc 0-13, src 0-28        2: pc 7-9, src 18-26
  Command 1: "if {$x==$y} {\n    incr hiya\n"...
    (0) loadScalar1 %v0     # var "x"
    (2) loadScalar1 %v1     # var "y"
    (4) eq 
    (5) jumpFalse1 +7   # pc 12
  Command 2: "incr hiya"...
    (7) incrScalar1Imm %v2 +1   # var "hiya"
    (10) jump1 +4   # pc 14
    (12) push1 0    # ""
    (14) done