Donal关于expr命令性能的后续内容

时间:2015-01-19 05:56:47

标签: tcl

我刚刚在Donal Fellows上阅读了question的精彩回复。它是如此翔实。

但我对此部分有疑问:never use a dynamic expression with if, for or while as that will suppress a lot of compilation.

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

Donal或其他人,你能再详细说明一下吗?

[ 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。

1 个答案:

答案 0 :(得分: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 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 
% 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 hiya\n   "...
  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 hiya\n   "...
  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 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 

为了完整性,在一个过程中(在这种情况下,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 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