Object creation, deletion, and memory usage is ~10x worse in Tcl8.6.4/Itcl4.0.3 than in Tcl8.5.18/Itcl3.4.2.
Demo script:
package require Itcl namespace import itcl::* class A {} set i 0 puts [time {A $i; incr i} 10000] set i 0 puts [time {delete object $i; incr i} 10000]
Results:
+-------+------------+------------+-----------+ | Ver | Create | Delete | Memory | +-------+------------+------------+-----------+ | 3.4.2 | 6.0635 µs | 3.4106 µs | 6.724 MB | | 4.0.3 | 50.0183 µs | 40.6675 µs | 86.556 MB | +-------+------------+------------+-----------+ | ratio | 8.2490 | 11.9238 | 12.872 | +-------+------------+------------+-----------+
Ticket http://core.tcl.tk/itcl/tktview?name=04470f9067 improved the speed immensely, but the current inefficiency may still hinder any future projects using Itcl.
Telling me clearly that multiple Tcl threads are involved. I think that's what I needed to know. Thanks. Will try to use that to craft the right demo.
I have a test script now. Appears to be something in a method
that is leaking.
Just committed to trunk a patch that plugs all leaks I could find when Itcl is used over the complete lifetime of an interp. Let me know how it works for you, please.
a few more patches fixing flaws discovered along the way.
Is the performance also improved?
Related
Bugs: #271
I don't have any reason to believe that the changes since the
release of Itcl 4.0.5 will have any measurable impact on performance.
These changes serve those who over time create many interps
(whether in separate threads or not) that use Itcl and then are
released. Plugging the leaks lets such programs continue without
the damaging effects of cumulative memory loss.
This is still a problem with Itcl 4.1.0. Itcl 3.4's object creation time is constant at about 2 µs, while Itcl 4.1.0's time increases linearly (see attachments). Is this going to be too difficult to fix based on Itcl 4's internal design?
On 10/27/2017 11:19 AM, Tim Tomkinson wrote:
Looks like object creation must still be doing something linear in
the number of existing objects. Any idea what that is?
--
| Don Porter Applied and Computational Mathematics Division |
| donald.porter@nist.gov Information Technology Laboratory |
| http://math.nist.gov/~DPorter/ NIST |
|__________|
It looks like the problem is in the Itcl_PreserveData() and Itcl_ReleaseData() functions in generic/itclUtil.c, which in turn call Tcl_Preserve() and Tcl_Release(). These functions use a linear search, which is why the time is increasing linearly. The Tcl documentation even states, "The mechanism is intended only for short-term use (i.e. while procedures are pending on the stack); it will not work efficiently as a mechanism for long-term reference counts."
Itcl 3.4 uses a hash table implementation, which explains why its time is constant. A comment in generic/itcl_util.c states:
Well this may not be too bad. The line that creates the trouble is:
https://core.tcl.tk/itcl/artifact/cf873c608adaa772?ln=272
Itcl_PreserveData() is crude and inefficient. It needs replacing
with an appropriate refcounting scheme directly on the ioPtr
argument.
Guess we had the same plans for a Sunday afternoon.
Will tackle this soon.
Improvement committed to trunk. See what you think.
ok, now synced.
That's a lot better! The creation time is now constant, but it's still significantly slower than 3.4.3. I'll look around some more to see if there's anything obvious.
BTW, the tclconfig directory seems to be missing from the trunk.
tclconfig is a separate project
core.tcl.tk/tclconfig/
See these instructions
http://core.tcl.tk/itcl/wiki?name=Tracking+Itcl+Sources
I don't think this can get much faster given its current design. There are a lot of new calls to the Tcl core that the old code didn't make (e.g. Tcl_NewObjectInstance, dictionary functions, etc.), which is where most of the time is spent. Too bad, but I guess that's the price of progress.
I've been tracking down performance issues with Itcl, and while 4.1.1 (and top of trunk) definitely improve things, there are a couple of simple patterns which exhibit degradation from Itcl 3.2
local object creation/deletion in Tcl proc: 4.1.1: 48micros 3.2: 11micros, ratio: ~4:1
call to member method via eval and itcl::code: 4.1.1 4.8micros, 3.2: 1micros, ratio: ~5:1
a simple singleton pattern: 4.1.1 13.7micros, 3.2: 2 micros. ratio: ~7:1
other patterns have about a 30-50% performance penalty
Output of 3.2 run:
Tcl version: 8.4
Itcl version: 3.2
978406 microseconds per iteration for 1000000 method calls, result: 1000000
970555 microseconds per iteration for 1000000 method calls, from within a namespace, result: 1000000
9212 microseconds per iteration for 10000 mutual calls
9867 microseconds per iteration for 10000 mutual calls of different classes
11 microseconds per iteration for 10000 creation of objects in a proc call
1 microseconds per iteration for 10000 method calls via itcl::code and uplevel
1 microseconds per iteration for 10000 method calls via itcl::code and eval
1 microseconds per iteration for 10000 proc calls across namespace
1 microseconds per iteration for 10000 method calls via itcl::code and eval
2 microseconds per iteration for 10000 method calls via itcl::code and uplevel
2 microseconds per iteration for 10000 method calls via itcl::code and uplevel #0
2 microseconds per iteration per each 10000 class calls that use singleton
Output of 4.1.1 run:
Tcl version: 8.6
Itcl version: 4.1.1
1307143 microseconds per iteration for 1000000 method calls, result: 1000000
1556553 microseconds per iteration for 1000000 method calls, from within a namespace, result: 1000000
13979 microseconds per iteration for 10000 mutual calls
13324 microseconds per iteration for 10000 mutual calls of different classes
48.9271 microseconds per iteration for 10000 creation of objects in a proc call
2.4827 microseconds per iteration for 10000 method calls via itcl::code and uplevel
4.8593 microseconds per iteration for 10000 method calls via itcl::code and eval
0.2884 microseconds per iteration for 10000 proc calls across namespace
1.7693 microseconds per iteration for 10000 method calls via itcl::code and eval
1.7845 microseconds per iteration for 10000 method calls via itcl::code and uplevel
1.7942 microseconds per iteration for 10000 method calls via itcl::code and uplevel #0
13.7799 microseconds per iteration per each 10000 class calls that use singleton
Actual script used to generate these numbers:
!/bin/sh
--Tcl-- the next line restarts using wish \
if {[info tclversion] == 8.6} {
if {[info exists ::env(USE_ITCL_34)]} {
set env(ITCL_LIBRARY) /wv/tjackson/downloads/86_itcl34_install/lib/itcl3.4
set auto_path [linsert $auto_path 0 /wv/tjackson/downloads/86_itcl34_install/lib/itcl3.4]
set auto_path [linsert $auto_path 0 /wv/tjackson/downloads/86_itcl34_install/lib/itk3.4]
set auto_path [linsert $auto_path 0 /wv/tjackson/downloads/86_itcl34_install/lib/iwidgets]
puts "itcl namespace has been created"
puts "created class Dummy and object $d"
} else {
if {! [info exists ::env(ITCL_410)]} {
puts "Itcl 4.1.1!!!!"
set ::env(ITCL_LIBRARY) /wv/tjackson/downloads/itcl-fossil/itcl/install/usr/local/lib/itcl4.1.1
set auto_path [linsert $auto_path 0 $::env(ITCL_LIBRARY)]
}
}
}
puts [package require Itcl]
itcl::class Test {
public proc Callme {}
public proc EntryPoint {}
}
itcl::body Test::Callme {} {
puts "In here"
}
itcl::body Test::EntryPoint {} {
Callme
}
Test::EntryPoint
puts "Tcl version: [info tclversion]"
puts "Itcl version: [package require Itcl]"
itcl::class SimpleClass {
constructor {} {
set count 0
}
private variable count 0
public method increment {} {
incr count
}
public method get {} {
return $count
}
}
proc test_simple {{count 10000}} {
set time [time {
set o [SimpleClass #auto]
for {set i 0} {$i < $count} {incr i} {
$o increment
}
}]
puts "$time for $count method calls, result: [$o get]"
}
test_simple 1000000
proc test_simple_from_namespace {} {
set time [time {
set ::o [namespace origin [SimpleClass #auto]]
namespace eval a_namespace {
for {set i 0} {$i < 1000000} {incr i} {
$::o increment
}
}
}]
puts "$time for 1000000 method calls, from within a namespace, result: [$::o get]"
}
test_simple_from_namespace
itcl::class MutualClass {
constructor {} {
}
public method call_other {count} {
if {$count < 0} {
return
}
incr count -1
$other call_other $count
}
private variable other {}
public method set_other {o} {
set other $o
}
}
proc test_mutual_calls_impl {o1 o2 count} {
$o1 set_other $o2
$o2 set_other $o1
set step 100
while {$count >= $step} {
$o1 call_other $step
incr count -$step
}
$o1 call_other $count
}
proc test_mutual_calls {{count 10000}} {
set o1 [namespace origin [MutualClass #auto]]
set o2 [namespace origin [MutualClass #auto]]
set time [time {test_mutual_calls_impl $o1 $o2 $count}]
puts "$time for $count mutual calls"
}
test_mutual_calls 10000
itcl::class OtherMutualClass {
constructor {} {
}
public method call_other {count} {
if {$count < 0} {
return
}
incr count -1
$other call_other $count
}
private variable other {}
public method set_other {o} {
set other $o
}
}
proc test_other_mutual_calls {{count 10000}} {
set o1 [namespace origin [MutualClass #auto]]
set o2 [namespace origin [OtherMutualClass #auto]]
set time [time {test_mutual_calls_impl $o1 $o2 $count}]
puts "$time for $count mutual calls of different classes"
}
test_other_mutual_calls 10000
proc test_object_construction_impl {} {
itcl::local SimpleClass #auto
}
proc noop {} {
}
proc test_object_construction {{count 10000}} {
set time_base [time {noop} $count]
set time [time {test_object_construction_impl} $count]
set base [lindex $time_base 0]
set actual [lindex $time 0]
puts "[format %g [expr {$actual-$base}]] microseconds per iteration for $count creation of objects in a proc call"
}
test_object_construction
itcl::class ComplexClass {
constructor {} {} {}
private variable m_called_priv 0
private method priv_call {} {
incr m_called_priv
}
public method how_many_calls {} {return $m_called_priv}
public method get_priv_method {} {
return [itcl::code $this priv_call]
}
}
proc test_method_call_via_itcl_code_impl_eval {call} {
eval $call
}
proc test_method_call_via_itcl_code_impl_uplevel {call} {
uplevel #0 $call
}
proc test_method_call_via_itcl_code {{count 10000}} {
set o [itcl::local ComplexClass #auto]
set thunk [$o get_priv_method]
set time [time {test_method_call_via_itcl_code_impl_uplevel $thunk} $count]
puts "$time for $count method calls via itcl::code and uplevel"
set time [time {test_method_call_via_itcl_code_impl_eval $thunk} $count]
puts "$time for $count method calls via itcl::code and eval"
}
test_method_call_via_itcl_code
namespace eval caller {}
namespace eval callee {}
proc callee::test_proc_call_across_namespace_impl {} {
expr {2+3}; # do something
}
proc caller::test_proc_call_across_namespace {{count 10000}} {
set time [time {callee::test_proc_call_across_namespace_impl} $count]
puts "$time for $count proc calls across namespace"
}
caller::test_proc_call_across_namespace
proc caller::test_method_calls_via_itcl_code_impl_from_ns {thunk count} {
set time [time {eval $thunk} $count]
puts "$time for $count method calls via itcl::code and eval"
set time [time {uplevel $thunk} $count]
puts "$time for $count method calls via itcl::code and uplevel"
set time [time {uplevel #0 $thunk} $count]
puts "$time for $count method calls via itcl::code and uplevel #0"
}
proc test_method_call_via_itcl_code_from_namespace {{count 10000}} {
set o [itcl::local ComplexClass #auto]
set thunk [$o get_priv_method]
caller::test_method_calls_via_itcl_code_impl_from_ns $thunk $count
}
test_method_call_via_itcl_code_from_namespace
proc caller::proc_calls_across_namespace {{count 10000}} {
set time [time {callee::test_method_call_via_itcl_code_impl_uplevel $thunk} $count]
puts "$time for $count method calls via itcl::code and uplevel"
set time [time {callee::test_method_call_via_itcl_code_impl_eval $thunk} $count]
puts "$time for $count method calls via itcl::code and eval"
}
itcl::class CWithProc {
public proc GetSingleton {}
public proc CallSomeMethod {}
private common m_singletonOptionsMgr
private method someMethod {}
private variable m_counter 0
}
itcl::body CWithProc::someMethod {} {
incr m_counter
}
proc CWithProc::CallSomeMethod {} {
set s [GetSingleton]
$s someMethod
}
proc CWithProc::GetSingleton {} {
if {[info exists m_singletonOptionsMgr]} {
return $m_singletonOptionsMgr
}
set m_singletonOptionsMgr [namespace origin [CWithProc #auto]]
return $m_singletonOptionsMgr
}
proc test_calls_to_singleton_via_class_proc {{count 10000}} {
set time [time {CWithProc::CallSomeMethod} $count]
puts "$time per each $count class calls that use singleton"
}
test_calls_to_singleton_via_class_proc
Demo script doesn't work:
invalid command name "GetSingleton"
while executing
"GetSingleton"
(procedure "CWithProc::CallSomeMethod" line 2)
invoked from within
"CWithProc::CallSomeMethod"
invoked from within
"time {CWithProc::CallSomeMethod} $count"
(procedure "test_calls_to_singleton_via_class_proc" line 2)
invoked from within
"test_calls_to_singleton_via_class_proc"
(file "/home/dgp/sf271.tcl" line 274)
Maybe we can try this with one focused, working example at a time?
Fair enough. Here's the singleton example - no code change, works for me with Tcl 8.67 and Itcl 4.1.1 (actually latest fossil version), and in 8.4 3.2
Note: GetSingleton is defined using [proc], which has always worked for us (Itcl 3.2 or 4.1) - but it does have problems with Itcl 3.4. It's fixed by changing that to [itcl::body]. Perhaps that's the issue you saw (Itcl 3.4?).
Anyway, output first, then script
Tcl 8.6.7 Itcl 4.1.1
12.9345 microseconds per iteration per each 10000 class calls that use singleton
Tcl 8.4.19 Itcl 3.2
3 microseconds per iteration per each 10000 class calls that use singleton
See attached singleton.itcl
Last edit: Trey Jackson 2018-06-01
Indeed these test scripts do not run in Itcl 3.4.4. That's what I'm willing to test.
So, where do I file a bug against 3.4.4? It works in 4.1.1, and 3.2, so it seems like a bug, no?
ok, I see. "proc: -> "itcl::body" makes it work ok.
I don't think it's a bug, no. I think it's intrustion into an implementation detail.
If there's documentation otherwise, let me know.