TCL proc和字节码编译-链接是什么?

TCL proc和字节码编译-链接是什么?,tcl,Tcl,我多次提到,最好将脚本放入proc中,以提高运行时性能,例如,具有以下功能: 这就是建议将所有代码放在过程中的原因之一(它们以这种方式编译字节) 有些东西不适合我 正如答案中所描述的,第一次运行脚本时,会检查命令是否可以编译字节码,如果可以,则会编译。这完全有道理。但我看不出“proc”如何发挥重要作用。例如,比较以下两种脚本: set v [concat [lindex $::argv 1] [lindex $::argv 2]] myCmd $v 及 我对这两个脚本的高级解释如下: 在第一

我多次提到,最好将脚本放入proc中,以提高运行时性能,例如,具有以下功能:

这就是建议将所有代码放在过程中的原因之一(它们以这种方式编译字节)

有些东西不适合我

正如答案中所描述的,第一次运行脚本时,会检查命令是否可以编译字节码,如果可以,则会编译。这完全有道理。但我看不出“proc”如何发挥重要作用。例如,比较以下两种脚本:

set v [concat [lindex $::argv 1] [lindex $::argv 2]]
myCmd $v

我对这两个脚本的高级解释如下:

  • 在第一次运行任一脚本时,将编译“set”、“concat”、“lindex”和“return”命令
  • 第二个脚本还编译了“proc”
  • “myCmd”未在两个脚本中编译
  • 任何一个脚本的后续运行都会运行除“myCmd”之外的字节码
  • 那么,“proc”的优势是什么呢

    我确实在脚本上运行了dissamble:

    第一个脚本:

    ByteCode 0x0x83fc70, refCt 1, epoch 3, interp 0x0x81d680 (epoch 3)
      Source "set v [concat [lindex $::argv 1] [lindex $::argv 2]]\nmy"
      Cmds 5, src 61, inst 50, litObjs 4, aux 0, stkDepth 4, code/src 0.00
      Commands 5:
          1: pc 0-41, src 0-51         2: pc 2-39, src 7-50
          3: pc 4-20, src 15-30        4: pc 21-37, src 34-49
          5: pc 42-48, src 53-60
      Command 1: "set v [concat [lindex $::argv 1] [lindex $::argv 2]]"
        (0) push1 0         # "v"
      Command 2: "concat [lindex $::argv 1] [lindex $::argv 2]"
        (2) push1 1         # "concat"
      Command 3: "lindex $::argv 1"
        (4) startCommand +17 1      # next cmd at pc 21
        (13) push1 2        # "::argv"
        (15) loadScalarStk
        (16) listIndexImm 1
      Command 4: "lindex $::argv 2"
        (21) startCommand +17 1     # next cmd at pc 38
        (30) push1 2        # "::argv"
        (32) loadScalarStk
        (33) listIndexImm 2
        (38) invokeStk1 3
        (40) storeScalarStk
        (41) pop
      Command 5: "myCmd $v"
        (42) push1 3        # "myCmd"
        (44) push1 0        # "v"
        (46) loadScalarStk
        (47) invokeStk1 2
        (49) done
    
    第二个脚本:

    ByteCode 0x0xc06c80, refCt 1, epoch 3, interp 0x0xbe4680 (epoch 3)
      Source "proc p1 {v1 v2} {\n    set v [concat $v1 $v2]\n    return"
      Cmds 4, src 109, inst 50, litObjs 5, aux 0, stkDepth 4, code/src 0.00
      Commands 4:
          1: pc 0-10, src 0-67         2: pc 11-48, src 69-108
          3: pc 13-29, src 73-88       4: pc 30-46, src 92-107
      Command 1: "proc p1 {v1 v2} {\n    set v [concat $v1 $v2]\n    return"
        (0) push1 0         # "proc"
        (2) push1 1         # "p1"
        (4) push1 2         # "v1 v2"
        (6) push1 3         # "\n    set v [concat $v1 $v2]\n    return ["
        (8) invokeStk1 4
        (10) pop
      Command 2: "p1 [lindex $::argv 1] [lindex $::argv 2]"
        (11) push1 1        # "p1"
      Command 3: "lindex $::argv 1"
        (13) startCommand +17 1     # next cmd at pc 30
        (22) push1 4        # "::argv"
        (24) loadScalarStk
        (25) listIndexImm 1
      Command 4: "lindex $::argv 2"
        (30) startCommand +17 1     # next cmd at pc 47
        (39) push1 4        # "::argv"
        (41) loadScalarStk
        (42) listIndexImm 2
        (47) invokeStk1 3
        (49) done
    
    因此,脚本2确实少了1个TCL命令,但两个脚本都有49个字节码的命令

    最后,在运行测试时,我注释掉了“myCmd”,因为我实际上没有这样的扩展。结果如下:

    % time {source 1.tcl} 10000
    242.8156 microseconds per iteration
    % time {source 2.tcl} 10000
    257.9389 microseconds per iteration
    
    所以proc版本更慢


    我错过了什么?或者更确切地说,对proc和性能的确切理解是什么?

    以下两个脚本演示了由于使用
    proc
    s而获得的性能增益。在第二个脚本中,内部循环被提取到
    proc
    ,从而导致5倍的加速

    无需过程tcl

    #!/usr/bin/env tclsh
    
    set sum 0
    set n 10000
    set k 100
    for { set i 0 } { $i < $k } { incr i } {
        set s 0
        for { set j 0 } { $j < $n } { incr j } {
            set s [expr {$s + $j}]
        }
        set sum [expr {$sum + $s}]
    }
    puts "sum=$sum"
    
    #!/usr/bin/env tclsh
    
    proc foo {n} {
        set s 0
        for { set j 0 } { $j < $n } { incr j } {
            set s [expr {$s + $j}]
        }
        return $s
    }
    
    set sum 0
    set n 10000
    set k 100
    for { set i 0 } { $i < $k } { incr i } {
        set s [foo $n]
        set sum [expr {$sum + $s}]
    }
    puts "sum=$sum"
    


    将内容放入过程中非常重要的一个重要原因是,过程有一个局部变量表。LVT中的变量可以通过数字索引进行访问,这比另一种方法(通过哈希表进行查找,尽管Tcl实现了极快的哈希表)要快得多。对于一次性调用没有多大区别,但是对于重复调用或循环,性能差异很快就会累积成显著的差异。这很容易使额外编译和堆栈框架管理的额外成本(虽然我们尽量使它们便宜,但过程不能自由进入)在实际脚本中基本上无关紧要

    是的,Tcl实际上是字节码编译所有东西。只是它经常在过程(-like context)之外生成次优字节码;在次优性的极限情况下,字节码所做的就是将参数组装到一个列表中,执行动态命令调用,并路由结果


    (在阅读Tcl的反汇编字节码时,一定要记住,特定字节码的成本并不完全相同。你不能简单地计算指令的数量,以任何有用的方式计算出成本。例如,
    push1
    非常便宜,但
    invokeStk1
    可能非常昂贵。另一个例子是,
    加载calarStk
    通常比loadScalar1昂贵得多;后者仅在过程中使用。)

    concat
    通常是一个相当昂贵的操作,FWIW。此外,@Leon的回答很好地说明了为什么这些东西很重要。
    #!/usr/bin/env tclsh
    
    proc foo {n} {
        set s 0
        for { set j 0 } { $j < $n } { incr j } {
            set s [expr {$s + $j}]
        }
        return $s
    }
    
    set sum 0
    set n 10000
    set k 100
    for { set i 0 } { $i < $k } { incr i } {
        set s [foo $n]
        set sum [expr {$sum + $s}]
    }
    puts "sum=$sum"
    
    $ tclsh
    % time {source with_proc.tcl} 1
    sum=4999500000
    67482 microseconds per iteration
    % time {source without_proc.tcl} 1
    sum=4999500000
    406557 microseconds per iteration
    
    $ time tclsh with_proc.tcl 
    sum=4999500000
    
    real    0m0.089s
    user    0m0.080s
    sys     0m0.004s
    
    $ time tclsh without_proc.tcl
    sum=4999500000
    
    real    0m0.401s
    user    0m0.388s
    sys     0m0.016s