Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Awkward conversion to string from double in simple script

94 views
Skip to first unread message

Sebastian Biały

unread,
Dec 6, 2018, 3:20:41 AM12/6/18
to
Hello.

There is simple loop executed under TCL8.5 (8.6 is out of question now
since I do not have working port of my application):

proc ifact1loop count {
for {set i 0} {$i < $count} {incr i} {
set x 1.0
for {set j 1} {$j <= 100} {incr j} {
set x [expr $x * $j]
}
}
}

I just trying to understand why it is evaluating slow (about 2x) in by
case comparing to typical tcl speed. TCL is embedded inside C/C++
application and does nothing more than Tcl_GlobalEvalObj( ... ). TCL
engine is compiled by me, however debug is not enabled, this is pure
optimized release code.

To my suprise about 50% of time can be found inside function that
converts double to string. This conversion is invoked from INST_CONCAT1
byte code instruction. I do not understand why one needs to convert it
to string and where is this concatenation used? Is it way "expr"
internals expects arguments? As single string?

I already did look around place that emits this optcode but without luck.

Why this simple script converts doubles to string *all* the time wasting
cpu? I was almost sure that whole idea of Obj in tcl was about not
converting to string.

Andreas Leitgeb

unread,
Dec 6, 2018, 3:28:16 AM12/6/18
to
Sebastian Biały <he...@poczta.onet.pl> wrote:
> proc ifact1loop count {
> for {set i 0} {$i < $count} {incr i} {
> set x 1.0
> for {set j 1} {$j <= 100} {incr j} {
> set x [expr $x * $j]

make that:
set x [expr {$x * $j}]

> }
> }
> }

PS: I expect there'll be more replies about the deeper
reasons, but this is a quick one to fix your problem.

Sebastian Biały

unread,
Dec 6, 2018, 3:36:34 AM12/6/18
to
On 2018-12-06 09:28, Andreas Leitgeb wrote:
> make that:
> set x [expr {$x * $j}]

No no no :)

It is not about why this script is broken. It is question why this
script is 2x slower on MY version of TCL compiled from sources vs
provided by someone other in closed source application using tcl 8.5.

I hoping that this is only missed compile configuration flag.

stefan

unread,
Dec 6, 2018, 4:04:21 AM12/6/18
to
On Thursday, December 6, 2018 at 9:36:34 AM UTC+1, Sebastian Biały wrote:
> On 2018-12-06 09:28, Andreas Leitgeb wrote:
> > make that:
> > set x [expr {$x * $j}]
>
> No no no :)
>
> It is not about why this script is broken. It is question why this
> script is 2x slower on MY version of TCL compiled from sources vs
> provided by someone other in closed source application using tcl 8.5.

What is YOUR self-compiled version of Tcl exactly (patch level, configure flags)? What is SOMEONE's version of Tcl exactly?

Sebastian Biały

unread,
Dec 6, 2018, 4:12:58 AM12/6/18
to
On 2018-12-06 10:04, stefan wrote:
> What is YOUR self-compiled version of Tcl exactly (patch level, configure flags)?

Will try to extract it.

> What is SOMEONE's version of Tcl exactly?

No idea. Closed source. Probably 8.5.12, same as mine.

heinrichmartin

unread,
Dec 6, 2018, 4:15:49 AM12/6/18
to
On Thursday, December 6, 2018 at 9:20:41 AM UTC+1, Sebastian Biały wrote:
> To my suprise about 50% of time can be found inside function that
> converts double to string. This conversion is invoked from INST_CONCAT1
> byte code instruction. I do not understand why one needs to convert it
> to string and where is this concatenation used? Is it way "expr"
> internals expects arguments? As single string?

Andreas gave the answer to this. expr sees "1.0" "*" "1"; it concatenates[1], then evaluates. For {$x * $j} there should not be any INST_CONCAT1.

[1] https://core.tcl.tk/tips/doc/trunk/tip/526.md

Rich

unread,
Dec 6, 2018, 7:11:38 AM12/6/18
to
Sebastian Bia?y <he...@poczta.onet.pl> wrote:
> Hello.
>
> There is simple loop executed under TCL8.5 (8.6 is out of question now
> since I do not have working port of my application):
>
> proc ifact1loop count {
> for {set i 0} {$i < $count} {incr i} {
> set x 1.0
> for {set j 1} {$j <= 100} {incr j} {
> set x [expr $x * $j]
> }
> }
> }
>
> Why this simple script converts doubles to string *all* the time
> wasting cpu?

Because you wrote it to always convert to string then perform a concat.

This: [expr $x * $j], as documented by expr (emphasis added):

man expr:
**Concatenates args** (adding separator spaces between them),
evaluates the result as a Tcl expression, and returns the value.

Expr, when it is finally called, see's three arguments on its argv, and
so it calls concatenate internally on the three args to get a single
string. In order to "concatenate" the string representation of $x and
$j are needed, so concatenate obtains the string version of $x and $j,
performs a concat with those two strings, and the star, then returns
the result to expr to be interpreted as a math expression. That is the
source of your "converts doubles to string *all* the time" question.

> I was almost sure that whole idea of Obj in tcl was about not
> converting to string.

It is, but in order to prevent the conversion to string, you have to
give expr a single argument that is itself an unsubstituted string.
That is why it is always recommended to "brace your expressions" [1].
When you give expr this: [expr {$x * $j}] what expr receives is the
string "$x * $j". Since it is one arg, it does no 'concat', and it
(expr) performs the variable accesses, so it asks $x for a numeric rep
(preventing conversion to string if $x already has a numeric rep cached
and the numeric rep is current). Same for $j. Then expr performs a
multiply on values that are already numeric, returning a tclobj that is
itself numeric.


[1] https://wiki.tcl-lang.org/page/Brace+your+expr-essions

Sebastian Biały

unread,
Dec 6, 2018, 7:41:48 AM12/6/18
to
On 2018-12-06 13:11, Rich wrote:
> Because you wrote it to always convert to string then perform a concat.

Yes, we already know that. However it is not the point. The point is
that closed source software with same tcl version can do it 2x faster.
No idea why.

Ale to be honest, version with {} is 3x faster than mine.

heinrichmartin

unread,
Dec 6, 2018, 8:04:21 AM12/6/18
to
On Thursday, December 6, 2018 at 1:41:48 PM UTC+1, Sebastian Biały wrote:
> The point is
> that closed source software with same tcl version can do it 2x faster.
> No idea why.

A common mistake when profiling is to include parsing and compiling. Have you crosschecked that you are looking at the same conditions in both cases?

Bytecode available? No traces?

PS:
> Yes, we already know that.

Don't become desperate. These are the posts I recall, when someone tells others to "not use google groups for c.l.t." or "use the right client to read newsgroups". (And yes, I felt invisible several time already.)

Ralf Fassel

unread,
Dec 6, 2018, 8:09:00 AM12/6/18
to
* Sebastian Biały <he...@poczta.onet.pl>
| The point is that closed source software with same tcl version can do
| it 2x faster. No idea why.

Different compiler, different compiler optimizations, different
whatever. Slowdown of factor 2 smells much like "compiled with
optimizations turned off", but basically there's no way to tell but
asking the provider of the closed source software how they compiled TCL.

R'

Sebastian Biały

unread,
Dec 6, 2018, 8:18:48 AM12/6/18
to
Actually my vanilla version is slower x2. And I double checked this,
there is no debug of any kid included in my version.

Sebastian Biały

unread,
Dec 6, 2018, 8:28:01 AM12/6/18
to
On 2018-12-06 14:04, heinrichmartin wrote:
> A common mistake when profiling is to include parsing and compiling. Have you crosschecked that you are looking at the same conditions in both cases?

I'm counting Tcl_*Eval* procedure time. This is exactly what is visible
for user as waiting time. If there is any compilation inside it does not
matter for the user. User of my app is only interested in fast tcl
runtime embedded in my app.

> Bytecode available? No traces?

No, this is closed source, without heavy hacking I do not see any way of
guessing what is going on. My thesis is now that I cannot compile tcl
for some reason with similar performance (some research already done, no
success). Although C/C++ compiler seems to be similar if not the same.

Also it might be possible that other software uses some kind of
pre-compilator for tcl, embedded also in code. No idea.

>> Yes, we already know that.
> Don't become desperate.

Not yet ;) Just a bit tired of explaining details 4x in a row ;)
Actually I'm >20 years on groups for now, I think I know how to handle
this lost dark corner of internet ;)

Rich

unread,
Dec 6, 2018, 10:25:27 AM12/6/18
to
Sebastian Bia?y <he...@poczta.onet.pl> wrote:
> On 2018-12-06 13:11, Rich wrote:
>> Because you wrote it to always convert to string then perform a concat.
>
> Yes, we already know that. However it is not the point. The point is
> that closed source software with same tcl version can do it 2x faster.
> No idea why.

Please look back to your original posting (to which I replied). Please
point out where, in that post, you refer to "that closed source
software with same tcl version can do it 2x faster".

I see nothing there that refers to "some other closed source software"
being faster.

> Ale to be honest, version with {} is 3x faster than mine.

As for why some other Tcl interpreter can process it faster, no idea.
Are you sure the 'other Tcl interpreter' is actually processing the
identical code?

Rich

unread,
Dec 6, 2018, 10:28:58 AM12/6/18
to
Sebastian Bia?y <he...@poczta.onet.pl> wrote:
> On 2018-12-06 14:08, Ralf Fassel wrote:
>> * Sebastian Bia?y<he...@poczta.onet.pl>
Yes, but Ralf's point is this:

Closed source version: compiled with Intels C compiler with full
optimizations turned on.

Your version: compiled with X (where X is gcc, llvm, etc.) where X was
not asked to optimize (or is not doing the same optimizations).

The above situation could easily produce a 2x difference.

Sebastian Biały

unread,
Dec 7, 2018, 2:04:55 AM12/7/18
to
On 2018-12-06 16:25, Rich wrote:
> Are you sure the 'other Tcl interpreter' is actually processing the
> identical code?

Same *source* code, yes. Exactly the same file.

heinrichmartin

unread,
Dec 7, 2018, 3:17:00 AM12/7/18
to
On Thursday, December 6, 2018 at 2:28:01 PM UTC+1, Sebastian Biały wrote:
> On 2018-12-06 14:04, heinrichmartin wrote:
> > A common mistake when profiling is to include parsing and compiling. Have you crosschecked that you are looking at the same conditions in both cases?
>
> I'm counting Tcl_*Eval* procedure time. This is exactly what is visible
> for user as waiting time. If there is any compilation inside it does not
> matter for the user. User of my app is only interested in fast tcl
> runtime embedded in my app.
>
> > Bytecode available? No traces?
>
> No, this is closed source, without heavy hacking I do not see any way of

I thought of Tcl byte-code. So my input was whether you are counting Tcl's parsing and bytecode generation in one case, but not the other.

Note that this happens implicitly. And efficiency varies, e.g. see TCL_ALLOW_INLINE_COMPILATION.

> Also it might be possible that other software uses some kind of
> pre-compilator for tcl, embedded also in code. No idea.

This is default behavior in Tcl. [::tcl::unsupported::disassemble proc ifact1loop]

Ralf Fassel

unread,
Dec 7, 2018, 5:31:15 AM12/7/18
to
* Sebastian Biały <he...@poczta.onet.pl>
It's not sufficient to have "no debug", it is also the optimizations
turned ON which matter:

% cat t.c
#include <stdio.h>

int main() {
int i=0,j=0;

while (i++ < 1000000000) {
j += i;
}
printf ("j is %d after %d steps\n", j, i);
return 0;
}

No optimizations:
% gcc -o t t.c
% time ./t > /dev/null

real 0m2.096s
user 0m2.096s
sys 0m0.000s

Basic optimizations:
% gcc -O2 -o t t.c
% time ./t > /dev/null

real 0m0.694s
user 0m0.694s
sys 0m0.000s

More optimizations:
% gcc -O3 -o t t.c
% time ./t > /dev/null

real 0m0.268s
user 0m0.268s
sys 0m0.000s

HTH
R'

Andreas Leitgeb

unread,
Dec 7, 2018, 7:11:17 AM12/7/18
to
Sebastian Biały <he...@poczta.onet.pl> wrote:
> Ale to be honest, version with {} is 3x faster than mine.

In my quick tests (running your proc with an argument of 1000,
the braced version ran some orders of magnitude faster: not just
3x but more like 150x faster.

two...@gmail.com

unread,
Dec 7, 2018, 11:35:16 PM12/7/18
to
There is a new TIP to reject [expr] with more than one argument, so why can't there be a way for tcl to just add the { }'s around the arguments to [expr] if it's not already braced.

I'm sure there's some corner cases where a programmer might take advantage of the concatenation currently going on, but if we're looking to break that anyway, why not just do the bracing for the programmer automatically.

Wasn't the purpose of the existing implementation to relax the syntax somewhat so expressions wouldn't need to be braced, but to pretty much act as though they were? There shouldn't be such a performance hit for something so syntactically simple. Programmers shouldn't have to deal with such trivial matters, especially if it involves orders of magnitude in performance.

Modern compilers do many things to enhance performance today. If a compiler can rewrite the code for loops, say when the programmer needlessly recomputes the same expression inside a loop, surely the tcl compiler should be able to simply add braces around arguments.

Rich

unread,
Dec 8, 2018, 12:47:20 AM12/8/18
to
two...@gmail.com wrote:
> There is a new TIP to reject [expr] with more than one argument, so
> why can't there be a way for tcl to just add the { }'s around the
> arguments to [expr] if it's not already braced.

That would require a radical change to the way the Tcl parser parses a
script into words prior to executing it. For the multi-argument case,
the parser does word separation, and variable substitution, long before
'expr' is ever called. So any attempt to force multi-argument 'expr'
to work the same as single argument expr by "adding braces" would
require a complete rework of the parser, and the potential for
collateral damage (i.e., unexpected results elsewhere) in doing that is
extremely high.

The braces are instructions from the script author to the parser to
take the next piece of the script as a literal and pass it on to the
command being called. That's how expr can then resolve the variables
itself, and not have to cause shimmering of tcl_obj's to/from string
reps, by getting its parameter unchanged by the main parser.

Ralf Fassel

unread,
Dec 8, 2018, 10:34:17 AM12/8/18
to
* two...@gmail.com
| [...] so why can't there be a way for tcl to just add the { }'s around
| the arguments to [expr] if it's not already braced.

# sometimes you dont want braces
set arg1 1
set arg2 1
set op "+"

# this_works
expr $arg1 $op $arg2
2

# this doesnt
expr {$arg1 $op $arg2}
missing operator at _@_
in expression "$arg1 _@_$op $arg2"

The parser can't blindly change existing code w/o breaking it...

R'

Robert Heller

unread,
Dec 8, 2018, 1:31:19 PM12/8/18
to
I expect that

expr "$arg1 $op $arg2"

will work, as will:

expr "\$arg1 $op \$arg2"

or

expr [list $arg1 $op $arg2]

etc.

So, even if a future version of expr loses the multiple argument form, it is
still prossible to get the effective functionality.

>
> R'
>

--
Robert Heller -- 978-544-6933
Deepwoods Software -- Custom Software Services
http://www.deepsoft.com/ -- Linux Administration Services
hel...@deepsoft.com -- Webhosting Services

0 new messages