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