I could replicate the actual code but I think the pseudo version will
do. Its typical I think.
INT32 GetMyData (ClientData clientdata, Tcl_Interp *interp, INT32 argc, const CHAR *argv[])
Loop over N data elements:
Tcl_Obj *pSubList = Tcl_NewListObj(0,NULL);
Now put Time and a Float into pSubList using two calls to
TclListObjAppendElement(interp, pSubList,....);
//copy the sublist as a single element to the output list:
Tcl_ListObjAppendElement(interp, pOutList, pSubList);
EndLoop
Tcl_SetObjResult(interp, pOutList);
return TCL_OK;
Works like a charm, I get { {time1}{data1} {time2}{data2}...
Any C-programmer not terribly familiar with the Tcl API should
immediately be concerned about the repeated Tcl_NewListObj(). Who
cleans up ?
Having gained more understanding, perhaps some one can judge the
following statements:
1. An AppendElement API call doesn't extend the size of the pOutList
- it merely make a reference to the pSubList in pOutList. For that
reason, the AppendElement call increments the ref counter on
pSubList - it is now 'shared' and needs to stay alive.
2. The AppendElement API does *not* increment pOutList ref counter
because that list is not shared anywhere. In fact the call will fail
it is ever sees pOutList with a non-zero reference counter
3. The Tcl_SetObjResult(interp, pOutList); increments the Ref
counter of pOutlist because the interpreter needs these data and it
should not be cleaned up.
Now for the final statement. Lets say I call my C function in a
loop:
While { 1 } {
set MyData [GetMyData ]
}
4. The Tcl interpreters will recursively Free the memory for ALL the
lists objects created in a previous call to GetMyData upon executing
the next one when $MyData is over-written. Right ? Otherwise we
have a memory leak.
Let me know if (1)..(4) are correct. The bottom line is that I dont
really need to worry about the ref counts of "temporary" lists (they
aren't temporary) or what happens with my pOutList.
Hello,
I spent a painful weekend trying to make more sensible Tcl list from data we generate in a C extension and to understand whether or not I am creating a memory leak. The return to the user is a List of List - pairs of time and ADC data in fact.
I could replicate the actual code but I think the pseudo version will do. Its typical I think.
INT32 GetMyData (ClientData clientdata, Tcl_Interp *interp, INT32 argc, const CHAR *argv[])
Loop over N data elements:
Tcl_Obj *pSubList = Tcl_NewListObj(0,NULL);
//copy the sublist as a single element to the output list:
Any C-programmer not terribly familiar with the Tcl API should immediately be concerned about the repeated Tcl_NewListObj(). Who cleans up ?
1. An AppendElement API call doesn't extend the size of the pOutList - it merely make a reference to the pSubList in pOutList. For that reason, the AppendElement call increments the ref counter on pSubList - it is now 'shared' and needs to stay alive.
2. The AppendElement API does *not* increment pOutList ref counter because that list is not shared anywhere. In fact the call will fail it is ever sees pOutList with a non-zero reference counter
3. The Tcl_SetObjResult(interp, pOutList); increments the Ref counter of pOutlist because the interpreter needs these data and it should not be cleaned up.
Now for the final statement. Lets say I call my C function in a loop:
While { 1 } {
set MyData [GetMyData ]
}
4. The Tcl interpreters will recursively Free the memory for ALL the lists objects created in a previous call to GetMyData upon executing the next one when $MyData is over-written. Right ? Otherwise we have a memory leak.
Hello,
I spent a painful weekend trying to make more sensible Tcl list from data we generate in a C extension and to understand whether or not I am creating a memory leak. The return to the user is a List of List - pairs of time and ADC data in fact.
I could replicate the actual code but I think the pseudo version will do. Its typical I think.
INT32 GetMyData (ClientData clientdata, Tcl_Interp *interp, INT32 argc, const CHAR *argv[])
Loop over N data elements:
Tcl_Obj *pSubList = Tcl_NewListObj(0,NULL);
Now put Time and a Float into pSubList using two calls to
TclListObjAppendElement(interp, pSubList,....);
//copy the sublist as a single element to the output list:
Tcl_ListObjAppendElement(interp, pOutList, pSubList);
EndLoop
Tcl_SetObjResult(interp, pOutList);
return TCL_OK;
Works like a charm, I get { {time1}{data1} {time2}{data2}...
Any C-programmer not terribly familiar with the Tcl API should immediately be concerned about the repeated Tcl_NewListObj(). Who cleans up ?
I made mistaken attempts to decrement the ref counter on pSubList at the end of the loop - resulting in various seg faults.
Having gained more understanding, perhaps some one can judge the following statements:
1. An AppendElement API call doesn't extend the size of the pOutList - it merely make a reference to the pSubList in pOutList. For that reason, the AppendElement call increments the ref counter on pSubList - it is now 'shared' and needs to stay alive.
2. The AppendElement API does *not* increment pOutList ref counter because that list is not shared anywhere. In fact the call will fail it is ever sees pOutList with a non-zero reference counter
3. The Tcl_SetObjResult(interp, pOutList); increments the Ref counter of pOutlist because the interpreter needs these data and it should not be cleaned up.
Now for the final statement. Lets say I call my C function in a loop:
While { 1 } {
set MyData [GetMyData ]
}
4. The Tcl interpreters will recursively Free the memory for ALL the lists objects created in a previous call to GetMyData upon executing the next one when $MyData is over-written. Right ? Otherwise we have a memory leak.
Let me know if (1)..(4) are correct. The bottom line is that I dont really need to worry about the ref counts of "temporary" lists (they aren't temporary) or what happens with my pOutList.
Much appreciated
Gertjan
On Monday, October 24, 2022 at 9:34:52 PM UTC+2, Gertjan wrote:
Hello,
I spent a painful weekend trying to make more sensible Tcl list from data we generate in a C extension and to understand whether or not I am creating a memory leak. The return to the user is a List of List - pairs of time and ADC data in fact.
I could replicate the actual code but I think the pseudo version will do. Its typical I think.
INT32 GetMyData (ClientData clientdata, Tcl_Interp *interp, INT32 argc, const CHAR *argv[])
Loop over N data elements:"The new list value returned by Tcl_NewListObj has reference count zero."; it will be incremented only in Tcl_ListObjAppendElement.
Tcl_Obj *pSubList = Tcl_NewListObj(0,NULL);
This also means, you must free it explicitly if you return prematurely.
About the same idea applies to the inner elements and the outer list.
Side-note: you could initialize the size with two, i.e. (2, NULL). "If objv is NULL, the resulting list contains 0 elements, with reserved space in an internal representation for objc more elements (to avoid its reallocation later)."
//copy the sublist as a single element to the output list:You have mentioned shared objects (copy on edit), i.e. "copy" is not exactly correct here.
Any C-programmer not terribly familiar with the Tcl API should immediately be concerned about the repeated Tcl_NewListObj(). Who cleans up ?Tcl, when the ref count drops to zero.
1. An AppendElement API call doesn't extend the size of the pOutList - it merely make a reference to the pSubList in pOutList. For that reason, the AppendElement call increments the ref counter on pSubList - it is now 'shared' and needs to stay alive.It extends the size (by the definition of its semantics), but it does not create a string rep for the extended list. The consequence is correct anyway.
2. The AppendElement API does *not* increment pOutList ref counter because that list is not shared anywhere. In fact the call will fail it is ever sees pOutList with a non-zero reference counterYes, it does not touch the ref count, but I assume (don't know exactly) it is the caller's responsibility to not call it on shared lists (i.e. I assume no error on C level).
3. The Tcl_SetObjResult(interp, pOutList); increments the Ref counter of pOutlist because the interpreter needs these data and it should not be cleaned up.Yes, increment from 0 to 1 in this case.
Now for the final statement. Lets say I call my C function in a loop:
While { 1 } {
set MyData [GetMyData ]
}
4. The Tcl interpreters will recursively Free the memory for ALL the lists objects created in a previous call to GetMyData upon executing the next one when $MyData is over-written. Right ? Otherwise we have a memory leak.Not just _list_ objects. And replace free with decrement the ref count, i.e. you are not leaking unless the inner elements are leaking (in the part that is not part of your pseudo code).
Slight inconsistencies in my language aside, I think I am on the right track and thank you all for confirming. I have a feeling a bit of basic knowledge like this should find it self at the top of the API documentation. The Wiki has some pages on dec/inc ref counters but it doesn't clarify the fundamentals: objects become shared, appends do not "expand/copy" and this is why if you do any operations like 'appending' list A to list B, then to modify list A lafter you need to duplicate it first.
i
There is a further consequence here. If in a script you have a very large list A and append/concat to list B, there is almost no CPU overhead. But then if you modify list A later, there must be significant overhead as list A now must be duplicated. i.e.
set A { some very large list}
lappend B {*}$A
lappend A {"hello world"}
there is almost no CPU overhead for the first lappend, which adds a massive list A to B. But a single item addition on the 2nd lappend is quite costly. Correct ?
* Robert Heller <heller@deepsoft.com>
| > 2. The AppendElement API does *not* increment pOutList ref counter because
| > that list is not shared anywhere. In fact the call will fail it is ever sees
| > pOutList with a non-zero reference counter
| Yes.
Nitpick: [...] In fact the call will fail it is ever sees pOutList with
a reference counter *greater than 1* (i.e. a shared list obj).
R'
From: Harald Oehlmann <wortkarg3@yahoo.com>
Date: Tue Oct 25 10:07:16 GMT 2022
Subject: Understanding Tcl memory management of list created in C
On Monday, October 24, 2022 at 11:20:29 PM UTC+2, Gertjan wrote:inc ref counters but it doesn't clarify the fundamentals: objects become shared, appends do not "expand/copy" and this is why if you do any operations like 'appending' list A to list B, then to modify list A lafter you need to duplicate it first.
Slight inconsistencies in my language aside, I think I am on the right track and thank you all for confirming. I have a feeling a bit of basic knowledge like this should find it self at the top of the API documentation. The Wiki has some pages on dec/
e.i
There is a further consequence here. If in a script you have a very large list A and append/concat to list B, there is almost no CPU overhead. But then if you modify list A later, there must be significant overhead as list A now must be duplicated. i.
Indeed, knowing the internals can speed up scripts (or the other way round, you might encounter unexpectedly slow sections if you were not aware of some internals). Shimmering (i.e. switching between representations) is one example - not the one thatyou are after, but a simple one:
Creating a long list by appending items in a (tight) loop is fast in Tcl until you log the list in every iteration. File/console operation is the expected factor, but actually creating the string representation is an unexpected one (in a language thatemphasizes "everything is a string"). On the other hand, this makes Tcl great imho - be flexible while coding, be fast if possible.
set A { some very large list}This requires at least:
lappend B {*}$A
- realloc in B
- memcopy pointers from A to end of B
- ref incr on all elements of A
lappend A {"hello world"}
This might not even require a realloc, because Tcl allocates internal list presentation progressively.
It duplicates the object in A only if it is shared, which is not the case here.
Side-note: [lindex $A end] will be a string including the quotation marks; this might or might not be expected.
there is almost no CPU overhead for the first lappend, which adds a massive list A to B. But a single item addition on the 2nd lappend is quite costly. Correct ?I'd say the first operation is more expensive than the second one.
Btw, ::tcl::unsupported::representation is quite nice to learn about internals, but an interactive session might thwart your efforts in two ways:
1. it creates string representations to display results
2. it might hold copies of parameters in the command history
1 or 2).
Am 26.10.2022 um 15:31 schrieb Ralf Fassel:> Note how the refcount did
not change (don't ask me why it is *3* and not
1 or 2).That is due to the history package, active in interactive mode.
The history package makes copies of each command, which increments the ref-count of all objects.
% set A { some very large list};
some very large list
% lappend B {*}$A
some very large list
% ::tcl::unsupported::representation $A
value is a list with a refcount of 4, object pointer at 008767C0,
internal representation 03EB5D78:00000000, string representation " some
very la..."
% history clear ; ::tcl::unsupported::representation $A
value is a list with a refcount of 2, object pointer at 008767C0,
internal representation 03EB5D78:00000000, string representation " some
very la..."
Another surprising way of refcount increment is, if an object is the
result of a command and thus copied to the interpreter result field.
Harald
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 495 |
Nodes: | 16 (3 / 13) |
Uptime: | 41:48:46 |
Calls: | 9,745 |
Calls today: | 5 |
Files: | 13,742 |
Messages: | 6,183,861 |