【问题标题】:TCL proc and byte code compile - what is the link?TCL proc 和字节码编译 - 链接是什么?
【发布时间】:2016-07-22 16:15:24
【问题描述】:

我多次提到最好将脚本放入 proc 以提高运行时性能,例如this answer 有以下内容:

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

有些东西没有点击我。

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

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

proc p1 {v1 v2} {
  set v [concat $v1 $v2]
  return [myCmd $v]
}
p1 [lindex $::argv 1] [lindex $::argv 2]

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

  1. 在第一次运行任一脚本时,都会编译“set”、“concat”、“lindex”和“return”命令
  2. 第二个脚本也编译了“proc”。
  3. “myCmd”未在任一脚本中编译
  4. 随后运行任一脚本都会运行除“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和performance的确切理解是什么?

【问题讨论】:

  • concat 通常是一个相当昂贵的操作,FWIW。

标签: tcl


【解决方案1】:

将事物放入过程中的真正重要原因是过程具有局部变量表。 LVT 中的变量可以通过数字索引访问,这比其他方法快得多(通过哈希表查找,即使 Tcl 有一个 非常 快速的哈希表实现)。一次性调用并没有太大的区别,但是对于重复调用或循环,性能差异很快就会增加一些重要的东西。这可以很容易地使额外编译和堆栈帧管理的额外成本(过程不是免费进入的,尽管我们尽量保持它们便宜)在实际脚本中基本上无关紧要。

是的,Tcl 实际上对所有内容进行字节码编译。只是它经常在过程(类似上下文)之外生成次优字节码;在次优的极限情况下,字节码所做的只是将参数组装成一个列表,执行动态命令调用,然后路由结果。

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

【讨论】:

  • 另外,@Leon 的回答很好地说明了为什么这些东西很重要。
【解决方案2】:

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

without_proc.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"

with_proc.tcl

#!/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

【讨论】:

    猜你喜欢
    • 2012-07-11
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多