Ralf
Ralf

Reputation: 1295

accelerate Tcl eval

I'm currently writing a Tcl-based tool for symbolic matrix manipulation, but the code is getting slow. I'm looking for ways to accelerate my Tcl code (Tcl version 8.6).

I have one suspicion. My code builds lists with a command name as the first element and command arguments as the following elements (this comes from emulating an object-oriented approach). I use eval to invoke these commands (and this is done often in the recursive processing). I read at https://wiki.tcl-lang.org/page/eval and https://wiki.tcl-lang.org/page/Tcl+Performance that eval may be slow.

I have three questions:

  1. What would be the fastest way to invoke a command from a list with command name and parameters which is constructed just beforehand?

  2. Would it accelerate the code to separate the command name myCmd and the parameter list myPar and invoke the command with [$myCmd {*}$myPar] instead (suggested at https://stackoverflow.com/a/27619692/3852630)?

  3. Is the trick with if 1 instead of eval still promising in 8.6?

Thanks a lot for your help!

Upvotes: 0

Views: 336

Answers (2)

glenn jackman
glenn jackman

Reputation: 246942

A note about K and unsharing objects

Avoid copying data in memory. Change

set mylist [linsert $mylist 0 some new content]

to

set mylist [linsert $mylist[set mylist ""] 0 some new content]

This dereferences the value of the variable and then sets the variable to the empty string. This reduces the variable's reference count.

See also https://stackoverflow.com/a/64117854/7552

Upvotes: 2

Donal Fellows
Donal Fellows

Reputation: 137627

Above all, don't assume: time it to be sure. Be aware when timing things that repeatedly running a thing may change the time it takes to run it (as caches warm up). Think carefully about what you want to actually get the speed of.

The eval command is usually slow, but not in all cases. If you give it a list that you've constructed (e.g., with list or linsert or lappend or…) then it's fairly fast as it can avoid reparsing the input; it knows, but only in that case, that it can skip straight to dispatching to the command implementation. The other case that is fast is when you give it a value that was previously given to eval; the bytecode is already built and cached. These notes also apply with uplevel.

Doing $myCmd {*}$myParameters is fairly fast too; that's bytecoded into “assemble the words on the Tcl operand stack and do the right command dispatch” which is very close to what it would be for an arbitrary user command anyway (which very rarely have direct bytecode implementations).

I'd expect things with if 1 to be very quick in some cases and very slow in others; it forces full compilation, so if things can be cached well then that will be fast and if things can't it will be slow. And if you're just calling a command, it won't make much difference at all at best. The cases where it wins are when the thing being called is itself a bytecoded command and where you can cache things correctly.

If you're dealing with an ordinary command (e.g., a procedure, or one of Tcl's commands that touch the OS), I'd go with option 2: $myCmd {*}$myParameters or variants on it. It's about as fast as you're going to get. But I would not do:

set myParameters [linsert $myOriginalValues 0 "literal1" [cmdOutput2] $value3]
$myCmd {*}$myParameters

That's ridiculous. This is clearer and cleaner and faster:

$myCmd "literal1" [cmdOutput2] $value3 {*}$myOriginalValues

Part of the point of expansion syntax ({*}) is that you don't need to do complex argument marshalling, and that's good because complexity is hard to get right all the time.

Upvotes: 2

Related Questions