Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

general purpose subroutines 1

Status
Not open for further replies.

Bong

Programmer
Dec 22, 1999
2,063
US
Greeting.
So far this forum has been very helpful so let me press my luck.

I have several instances of a sub-routine library for various analytical purposes. So, for instance, I have a "vector operator" library with things like cross-product, dot-product, magnitude, etc. I have never been quite sure the best way to implement them so that they are truly general purpose. The way I decided on is as in the following (cross product) example:

#returns a list (a la "array get") of alternating index/value for the crossproduct array
# to use: (eg) set alist [crossP vect1 vect2]. Note: use the vector names (no $)
# then, "array set {array name} $alist"
proc crossP {a b} {
upvar $a x
upvar $b y
set z(1) [expr $x(2)*$y(3) - $x(3)*$y(2)]
set z(2) [expr $x(3)*$y(1) - $x(1)*$y(3)]
set z(3) [expr $x(1)*$y(2) - $x(2)*$y(1)]
return [array get z]
}


My question is: is there a more efficient way to do this? Bob Rashkin
rrashkin@csc.com
 
Depends on what you mean by "more efficient." And how much time you've already got invested in the approach you're taking (as well as the amount of code you'd need to change if you changed the API). If it works fine the way it is, you might want to leave it almost untouched. But if it's running a bit too slow, I do have some tips for speeding things up, which I'll get to in a minute.

But how would I handle this situation if I were tackling it fresh? Well, my first step always is to see if someone else has already done the work for me by creating a Tcl extension or other "drop-in" piece of code. Whenever possible, I prefer to let someone else deal with maintenance hassles. :) The Tcler's Wiki, (or equivalently, is a great place to start your search, as is the Tcl Developer's Xchange, although that site is starting to suffer a bit of link rot.

In this case, the Wiki provided information on tcllib, the standard Tcl library, which includes a basic math package (described at but it provides only basic functions like max, min, and simple statistics.

More applicable to your needs, though, is La, The Hume Linear Algebra Tcl Package. The full description and download is available at La is a pure Tcl extension, which is good because it is completely platform-independent and easy to install, and which is bad because it can be slow for lots of manipulation of big matrices.

A disadvantage in your case of moving to an extension like La is that the API is difference from what you've developed. Is it worth the hassle of reimplementing your application(s)? Perhaps not. So, let's take a look at your approach with your sample procedure.

One of the first things I noticed is that you defined your procedure to accept two arrays as arguments (passing them by reference using upvar), but return a list. If possible, I would probably design a library so that I could maintain a consistent data format, representing data as lists only or as arrays only. Personally, I tend to prefer lists in a case like this because they are "first-class objects" in Tcl: I can pass their values to and from procedures, copy the data into other variables, and otherwise manipulate them easily without needing to turn to upvar tricks in my code. Arrays are useful, and are still a little faster than lists for data access in most cases, but aren't "first-class objects," so you need to use upvar to pass them to and from procedures, and there's no efficient way to copy an array (array get/array set is your only option).

If you wanted to modify your procedure so that you could return an array directly, you would just need to add a third parameter to your proc declaration to pass the name of a third array variable, and then use another upvar to access it from within your procedure. For example:

Code:
proc crossP {input1 input2 output} {
    upvar $input1 x
    upvar $input2 y
    upvar $output z
    # Overwrite output if it already exists
    catch {unset z}
    set z(1) [expr $x(2)*$y(3) - $x(3)*$y(2)]
    set z(2) [expr $x(3)*$y(1) - $x(1)*$y(3)]
    set z(3) [expr $x(1)*$y(2) - $x(2)*$y(1)]
}

crossP a b result

Note that this still works even if the output array variable doesn't exist when you call your procedure. upvar ensures that the variable will be created in the proper scope.

What about making the implementation you have more efficient? Well, I still have a suggestion even there...

When using expr, always quote your argument in {}. expr does its own round of variable and command substitutions (in addition to the round of substitutions performed by the Tcl interpreter). Quoting your math expression in {} prevents Tcl from doing the substitutions and lets expr take care of it instead. So we've cut the overhead of one round of substitutions. Also, expr is able to parse and compile more efficiently an expression quoted with {} versus an unquoted expression, just because the way the parser is written. The difference is dramatic, sometimes an order of magnitude depending on how complex the expression is. (There are various technical reasons why expr performs this extra round of substitutions, which we won't get into here.) There are very few instances where you don't want to quote the expression with {}. I think you'll be able to figure them out if you ever encounter them on your own.

So here's a sample implementation that makes only that change:

Code:
proc crossP2 {a b} {
    upvar $a x
    upvar $b y
    set z(1) [expr {$x(2)*$y(3) - $x(3)*$y(2)}]
    set z(2) [expr {$x(3)*$y(1) - $x(1)*$y(3)}]
    set z(3) [expr {$x(1)*$y(2) - $x(2)*$y(1)}]
    return [array get z]
}

Let's run some timing experiments to see how much this improved performance:

[tt]% array set a {1 414.2 2 -3.1415 3 7.6}
% array set b {1 -395.96 2 -0.76 3 219.15}
% time {crossP a b} 10000
253 microseconds per iteration
% time {crossP2 a b} 10000
48 microseconds per iteration[/tt]

Of course, if you're doing really intensive number crunching, Tcl might be too slow. In which case, you might need to implement your procedures in C, but it's always a bit of a hassle to mix languages like that, and it makes your code less portable.

I hope you found this helpful. And if I may include a bit of a personal plug here: If people have found my contributions to this forum useful, I would certainly appreciate any referrals you could provide. Avia offers not only training in Tcl/Tk and Expect, but also consulting services (short- and the long-term). So if you've benefited from the few tips and tricks I've managed to post here in my spare time, just imagine what it would be like to get my undivided attention for a few days. :)

Thanks. - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Ken,
Thank you very much. That was just the sort of insight I was hoping for. I'll be sure to refer people to you should the opportunity arise but it doesn't seem to very often. Bob Rashkin
rrashkin@csc.com
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top