TCL 全局 vs. 可变访问速度
TCL global vs. variable access speed
在TCL中,大致有全局变量,可通过global varname
访问,或命名空间变量可通过namespace varname
命令访问。
我非常简单的访问时间检查对于 2,对于版本 tcl 8.5.
,variable
与 global
相比显示了微小但一致的运行时访问优势
我们是否可以期望 8.6 及更高版本会出现这种情况,至少目前如此?
tl;dr: 两者在速度上非常 差别不大,您应该不在意。以语义上正确的方式做事并保持原样。对于您的用例 正确 将击败任何在时间方面进行百分之几的修补。
让我们比较一下全局命名空间中的以下两个过程做同样的事情。请注意,我们正在比较的选项之间的差异权重将相对于实际代码被人为提高;这些是微基准测试,理应受到怀疑。
proc a {} {
global x y
expr {$x + $y}
}
proc b {} {
variable x
variable y
expr {$x + $y}
}
好的,计时信息(您的绝对计时 将 因硬件而异):
% set x 123
123
% set y 456
456
% a
579
% b
579
% time { a } 10000
0.5720533 microseconds per iteration
% time { b } 10000
0.5756787999999999 microseconds per iteration
所以是的,b
比 a
稍慢。让我们看看字节码看看为什么:
% tcl::unsupported::disassemble proc a
ByteCode 0x0x10280fe10, refCt 1, epoch 15, interp 0x0x100829a10 (epoch 15)
Source "\n global x y\n expr {$x + $y"...
Cmds 2, src 35, inst 35, litObjs 4, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x102822c10, refCt 1, args 0, compiled locals 2
slot 0, scalar, "x"
slot 1, scalar, "y"
Commands 2:
1: pc 0-19, src 5-14 2: pc 20-33, src 20-33
Command 1: "global x y"...
(0) push1 0 # "::"
(2) push1 1 # "x"
(4) nsupvar %v0 # var "x"
(9) push1 2 # "y"
(11) nsupvar %v1 # var "y"
(16) pop
(17) nop
(18) nop
(19) nop
Command 2: "expr {$x + $y}"...
(20) startCommand +14 1 # next cmd at pc 34, 1 cmds start here
(29) loadScalar1 %v0 # var "x"
(31) loadScalar1 %v1 # var "y"
(33) add
(34) done
% tcl::unsupported::disassemble proc b
ByteCode 0x0x10280fa10, refCt 1, epoch 15, interp 0x0x100829a10 (epoch 15)
Source "\n variable x\n variable y\n expr {$x + $"...
Cmds 3, src 50, inst 44, litObjs 3, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x102822a90, refCt 1, args 0, compiled locals 2
slot 0, scalar, "x"
slot 1, scalar, "y"
Commands 3:
1: pc 0-9, src 5-14 2: pc 10-28, src 20-29
3: pc 29-42, src 35-48
Command 1: "variable x"...
(0) push1 0 # "x"
(2) variable %v0 # var "x"
(7) nop
(8) nop
(9) nop
Command 2: "variable y"...
(10) startCommand +18 1 # next cmd at pc 28, 1 cmds start here
(19) push1 2 # "y"
(21) variable %v1 # var "y"
(26) push1 1 # ""
(28) pop
Command 3: "expr {$x + $y}"...
(29) startCommand +14 1 # next cmd at pc 43, 1 cmds start here
(38) loadScalar1 %v0 # var "x"
(40) loadScalar1 %v1 # var "y"
(42) add
(43) done
确切的字节码序列可能与您的 Tcl 版本不同;这完全是实施的一部分,而不是官方界面。但是,我们可以看到,对于 global
,操作码序列涉及命名空间句柄的单个 push1
和命令实现中的两个 nsupvar
,以及两个 variable
调用时,名称空间未被推送,但 variable
操作码被使用了两次,并且两者之间有一个额外的 startCommand
。 startCommand
是一个混乱的基础设施,它有抑制一些小优化的副作用,所以这可能是原因。
由于两者之间的差异如此之小,找出性能差异的真正所在将需要像 cachegrind 这样的仪器……
顺便说一句,尝试将性能与此进行比较:
proc c {} {
namespace upvar :: x x y y
expr {$x + $y}
}
它有一个 与上面的 a
过程完全相同的 字节码序列…
在TCL中,大致有全局变量,可通过global varname
访问,或命名空间变量可通过namespace varname
命令访问。
我非常简单的访问时间检查对于 2,对于版本 tcl 8.5.
,variable
与 global
相比显示了微小但一致的运行时访问优势
我们是否可以期望 8.6 及更高版本会出现这种情况,至少目前如此?
tl;dr: 两者在速度上非常 差别不大,您应该不在意。以语义上正确的方式做事并保持原样。对于您的用例 正确 将击败任何在时间方面进行百分之几的修补。
让我们比较一下全局命名空间中的以下两个过程做同样的事情。请注意,我们正在比较的选项之间的差异权重将相对于实际代码被人为提高;这些是微基准测试,理应受到怀疑。
proc a {} {
global x y
expr {$x + $y}
}
proc b {} {
variable x
variable y
expr {$x + $y}
}
好的,计时信息(您的绝对计时 将 因硬件而异):
% set x 123
123
% set y 456
456
% a
579
% b
579
% time { a } 10000
0.5720533 microseconds per iteration
% time { b } 10000
0.5756787999999999 microseconds per iteration
所以是的,b
比 a
稍慢。让我们看看字节码看看为什么:
% tcl::unsupported::disassemble proc a
ByteCode 0x0x10280fe10, refCt 1, epoch 15, interp 0x0x100829a10 (epoch 15)
Source "\n global x y\n expr {$x + $y"...
Cmds 2, src 35, inst 35, litObjs 4, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x102822c10, refCt 1, args 0, compiled locals 2
slot 0, scalar, "x"
slot 1, scalar, "y"
Commands 2:
1: pc 0-19, src 5-14 2: pc 20-33, src 20-33
Command 1: "global x y"...
(0) push1 0 # "::"
(2) push1 1 # "x"
(4) nsupvar %v0 # var "x"
(9) push1 2 # "y"
(11) nsupvar %v1 # var "y"
(16) pop
(17) nop
(18) nop
(19) nop
Command 2: "expr {$x + $y}"...
(20) startCommand +14 1 # next cmd at pc 34, 1 cmds start here
(29) loadScalar1 %v0 # var "x"
(31) loadScalar1 %v1 # var "y"
(33) add
(34) done
% tcl::unsupported::disassemble proc b
ByteCode 0x0x10280fa10, refCt 1, epoch 15, interp 0x0x100829a10 (epoch 15)
Source "\n variable x\n variable y\n expr {$x + $"...
Cmds 3, src 50, inst 44, litObjs 3, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x102822a90, refCt 1, args 0, compiled locals 2
slot 0, scalar, "x"
slot 1, scalar, "y"
Commands 3:
1: pc 0-9, src 5-14 2: pc 10-28, src 20-29
3: pc 29-42, src 35-48
Command 1: "variable x"...
(0) push1 0 # "x"
(2) variable %v0 # var "x"
(7) nop
(8) nop
(9) nop
Command 2: "variable y"...
(10) startCommand +18 1 # next cmd at pc 28, 1 cmds start here
(19) push1 2 # "y"
(21) variable %v1 # var "y"
(26) push1 1 # ""
(28) pop
Command 3: "expr {$x + $y}"...
(29) startCommand +14 1 # next cmd at pc 43, 1 cmds start here
(38) loadScalar1 %v0 # var "x"
(40) loadScalar1 %v1 # var "y"
(42) add
(43) done
确切的字节码序列可能与您的 Tcl 版本不同;这完全是实施的一部分,而不是官方界面。但是,我们可以看到,对于 global
,操作码序列涉及命名空间句柄的单个 push1
和命令实现中的两个 nsupvar
,以及两个 variable
调用时,名称空间未被推送,但 variable
操作码被使用了两次,并且两者之间有一个额外的 startCommand
。 startCommand
是一个混乱的基础设施,它有抑制一些小优化的副作用,所以这可能是原因。
由于两者之间的差异如此之小,找出性能差异的真正所在将需要像 cachegrind 这样的仪器……
顺便说一句,尝试将性能与此进行比较:
proc c {} {
namespace upvar :: x x y y
expr {$x + $y}
}
它有一个 与上面的 a
过程完全相同的 字节码序列…