2011-10-31 110 views
4

我已经在形式化方法的领域中实现了许多TCL扩展(扩展在C中实现,但我不希望解决方案依赖于这个事实)。因此,我的工具的用户可以使用TCL进行原型算法。它们中的很多只是命令的线性列表(它们是强大的),例如:TCL中的扩展TCL解释器

my_read_file f 
my_do_something a b c 
my_do_something_else a b c 

现在,我对计时感兴趣。这是可能改剧本获得:

puts [time [my_read_file f] 1] 
puts [time [my_do_something a b c] 1] 
puts [time [my_do_something_else a b c] 1] 

取而代之的是我想定义程序xsource执行Tcl脚本和获取/写时序为我所有的命令。某种类型的分析器。主要思想如下:

set f [open [lindex $argv 0] r] 
set inputLine "" 
while {[gets $f line] >= 0} { 
    set d [expr [string length $line] - 1] 
    if { $d >= 0 } { 
    if { [string index $line 0] != "#" } { 
     if {[string index $line $d] == "\\"} { 
     set inputLine "$inputLine [string trimright [string range $line 0 [expr $d - 1]]]" 
     } else { 
     set inputLine "$inputLine $line" 
     set inputLine [string trimleft $inputLine] 
     puts $inputLine 
     puts [time {eval $inputLine} 1] 
     } 
     set inputLine "" 
    } 
    } 
} 

它适用于命令的线性列表,甚至允许在多行上使用注释和命令。但是,如果用户使用if语句,循环和过程定义,则会失败。你能提出一个更好的方法吗?它必须是纯粹的TCL脚本,尽可能少的扩展。

回答

5

做你要求的一种方法是使用execution traces。这里有一个脚本,可以做到这一点:

package require Tcl 8.5 

# The machinery for tracking command execution times; prints the time taken 
# upon termination of the command. More info is available too (e.g., did the 
# command have an exception) but isn't printed here. 
variable timerStack {} 
proc timerEnter {cmd op} { 
    variable timerStack 
    lappend timerStack [clock microseconds] 
} 
proc timerLeave {cmd code result op} { 
    variable timerStack 
    set now [clock microseconds] 
    set then [lindex $timerStack end] 
    set timerStack [lrange $timerStack 0 end-1] 
    # Remove this length check to print everything out; could be a lot! 
    # Alternatively, modify the comparison to print more stack frames. 
    if {[llength $timerStack] < 1} { 
     puts "[expr {$now-$then}]: $cmd" 
    } 
} 

# Add the magic! 
trace add execution source enterstep timerEnter 
trace add execution source leavestep timerLeave 
# And invoke the magic, magically 
source [set argv [lassign $argv argv0];set argv0] 
# Alternatively, if you don't want argument rewriting, just do: 
# source yourScript.tcl 

那么你还是这样称呼它(假设你已经把它放在一个名为timer.tcl文件):

tclsh8.5 timer.tcl yourScript.tcl 

请注意,此脚本具有相当多的开销,因为它抑制了许多通常使用的优化策略。对于使用自己的C代码来做真正肉食的用途来说,这并不重要,但是当它在Tcl中有很多循环时,您会注意到很多。

+0

这对我很有用,它非常优雅。这里唯一的缺点是报告不能局限于命令的子集(如果这很重要,那么GrAnd的解决方案就必须考虑)。 – meolic

+0

@meolic:您可以随时应用后期处理步骤。例如,将所有的日志信息转储到一个文件,然后只是grep的有趣的位。 –

+0

@DonalFellows关于抑制优化的最后一个注释,是否至少比较结果安全?即如果我有一个脚本并将其重构为另一个脚本,比较两者之间的时间安全性还是可能的,还是有可能优化反转结果? –

1

您可能需要查看命令“info complete”。它可以告诉你,从最常见的Tcl语法标记的角度来看,迄今为止你所累积的内容看起来是否完整。它将处理可能分布在多条物理线路上的命令输入。

+0

用于改善幼稚的实现。仍然不是解决问题的方法,我需要为循环中的每个命令进行计时。 – meolic

2

你可以包装你想测量的命令。并命名包装完全一样原来的(重命名原来的特效之前)。之后,当执行仪表命令时,它实际上执行包装器,该包装器执行原始程序并测量执行时间。下面的例子(Tcl 8.5)。

proc instrument {procs} { 
    set skip_procs {proc rename instrument puts time subst uplevel return} 
    foreach p $procs { 
    if {$p ni $skip_procs} { 
     uplevel [subst -nocommands { 
     rename $p __$p 
     proc $p {args} { 
      puts "$p: [time {set r [__$p {*}\$args]}]" 
      return \$r 
     } 
     }] 
    } 
    } 
} 

proc my_proc {a} { 
    set r 1 
    for {set i 1} {$i <= $a} {incr i} { 
    set r [expr {$r * $i}] 
    } 
    return $r 
} 

proc my_another_proc {a b} { 
    set r 0 
    for {set i $a} {$i <= $b} {incr i} { 
    incr r $i 
    } 
    return $r 
} 

instrument [info commands my_*] 

puts "100 = [my_proc 100]" 
puts "200 = [my_proc 100]" 
puts "100 - 200 = [my_another_proc 100 200]" 
+0

有趣的想法和相当复杂的实现(TCL极客确实喜欢它)。我注意到两个问题。首先,在调用'instrument'之前必须定义所有命令,并且如果之后添加了新命令,则必须再次调用instrument。更关键的是,我不知道如何撤消这种包装。我希望能够仅在有时而不是所有时间使用它。 – meolic

+0

要'uninstrument'只是'重命名__ $ p $ p',其中$ p - 是命令的名称。对于尚未定义的“仪器”程序,您可以'包装'proc命令,它将在定义时自动处理所有程序。它不适用于C++ procs(在这种情况下,你可以使用'load'命令来捕获它)。 :) – GrAnd