Donal 关于 expr 命令性能的后续文章



我刚刚读到了Donal Fellows关于这个问题的精彩回复。它是如此翔实。

但是我对这部分有一个问题:never use a dynamic expression with if, for or while as that will suppress a lot of compilation.

读了两遍,但不要以为我完全明白了。

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

[UPDATE1]出于好奇,我在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]运行。

[UPDATE2] 我还在思考我的问题UPDATE1。按照建议,我又做了一个实验:

% 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的整个手臂。如果您正在做组合表达式(出于安全原因我不鼓励这样做),那么至少要这样做:

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 {expr {$x+$y}}
ByteCode 0x0x1008eb610, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "expr {$x+$y}"
  Cmds 1, src 12, inst 8, litObjs 2, aux 0, stkDepth 2, code/src 0.00
  Commands 1:
      1: pc 0-6, src 0-11
  Command 1: "expr {$x+$y}"
    (0) push1 0     # "x"
    (2) loadStk 
    (3) push1 1     # "y"
    (5) loadStk 
    (6) add 
    (7) done 

请注意,在第一个版本中,我们使用exprStk(一般的字符串操作),而第二个版本使用add(它知道它正在处理数字并抛出错误)。

然后,对于if.

% 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 hiyan   "...
  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 hiyan   "...
    (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 hiyan  "...
    (14) invokeStk1 3 
    (16) done 
% tcl::unsupported::disassemble script {if {[expr $x==$y]} {
    incr hiya
}}
ByteCode 0x0x10095cc10, refCt 1, epoch 96, interp 0x0x100829a10 (epoch 96)
  Source "if {[expr $x==$y]} {n        incr hiyan   "...
  Cmds 3, src 44, inst 32, litObjs 5, aux 0, stkDepth 3, code/src 0.00
  Commands 3:
      1: pc 0-30, src 0-43        2: pc 0-10, src 5-15
      3: pc 14-26, src 29-37
  Command 1: "if {[expr $x==$y]} {n        incr hiyan   "...
  Command 2: "expr $x==$y"...
    (0) push1 0     # "x"
    (2) loadStk 
    (3) push1 1     # "=="
    (5) push1 2     # "y"
    (7) loadStk 
    (8) strcat 3 
    (10) exprStk 
    (11) nop 
    (12) jumpFalse1 +17     # pc 29
  Command 3: "incr hiya"...
    (14) startCommand +13 1     # next cmd at pc 27, 1 cmds start here
    (23) push1 3    # "hiya"
    (25) incrStkImm +1 
    (27) jump1 +4   # pc 31
    (29) push1 4    # ""
    (31) done 

注意第二个版本是如何理解它正在执行增量(incrStkImm)的?这对性能有很大帮助,特别是对于较长、不太琐碎的脚本。第一个版本只是组装一个参数列表,并使用invokeStk1来调用解释if实现。

FWIW,"黄金标准"(假设我们不在一个程序中)是这样的:

% 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 hiyan"...
  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 hiyan"...
    (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 

为了完整起见,在一个过程中(在这种情况下是lambda,但字节码是相同的):

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 hiyan"...
  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 hiyan"...
    (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 

最新更新