From simon.urbanek at r-project.org  Sun Jan  3 02:04:13 2010
From: simon.urbanek at r-project.org (Simon Urbanek)
Date: Sat, 2 Jan 2010 20:04:13 -0500
Subject: [Rd] iterative list rm [Was: R-devel Digest, Vol 83, Issue 2]
In-Reply-To: <4B3FCBA0.40908@dbmail.com>
References: <mailman.11.1262430004.8711.r-devel@r-project.org>	<4B3F7625.1080806@gmail.com>
	<4B3F7ACD.2050106@stats.uwo.ca>	<4B3F7F8D.8060701@gmail.com>	<B38057DF-8452-41FC-9D46-B67FBD58E01C@r-project.org>	<4B3FA43B.1070707@stats.uwo.ca>
	<4B3FA9BB.90302@gmail.com> <4B3FC4C0.6060604@stats.uwo.ca>
	<4B3FCBA0.40908@dbmail.com>
Message-ID: <A8BE4876-8448-44C1-B0D5-76358AC4BE73@r-project.org>


On Jan 2, 2010, at 5:41 PM, Romain Francois wrote:

> On 01/02/2010 11:12 PM, Duncan Murdoch wrote:
>> 
>> On 02/01/2010 3:16 PM, Laurent Gautier wrote:
>>> On 1/2/10 8:53 PM, Duncan Murdoch wrote:
>>>> Simon Urbanek wrote:
>>>>> On Jan 2, 2010, at 12:17 PM, Laurent Gautier wrote:
>>>>> 
>>>>>> On 1/2/10 5:56 PM, Duncan Murdoch wrote:
>>>>>>> On 02/01/2010 11:36 AM, Laurent Gautier wrote:
>>>>>>>> [Disclaimer: what is below reflects my understanding from reading
>>>>>>>> the
>>>>>>>> R source, others will correct where deemed necessary]
>>>>>>>> 
>>>>>>>> On 1/2/10 12:00 PM, r-devel-request at r-project.org wrote:
>>>>>> (...)
>>>>>> 
>>>>>>>>> I'd also be interested if there is some ideas on the relative
>>>>>>>>> efficiency
>>>>>>>>> of the preserve/release mechanism compared to PROTECT/UNPROTECT.
>>>>>>>> PROTECT/UNPROTECT is trading granularity for speed. It is a stack
>>>>>>>> with
>>>>>>>> only tow operations possible:
>>>>>>>> - push 1 object into the stack
>>>>>>>> - pull (unprotect) N last objects from the stack
>>>>>>> UNPROTECT_PTR is also possible, which does a linear search through
>>>>>>> the
>>>>>>> stack and unprotects something possibly deep within it. There is also
>>>>>>> REPROTECT which allows you to replace an entry within the stack.
>>>>>>> 
>>>>>>> I would guess that UNPROTECT_PTR is more efficient than
>>>>>>> RecursiveRelease
>>>>>>> because it doesn't use so much stack space when it needs to go deep
>>>>>>> into
>>>>>>> the stack to release, but it is possible the compiler recognizes the
>>>>>>> tail recursion and RecursiveRelease is implemented efficiently. In
>>>>>>> that
>>>>>>> case it could be more efficient than UNPROTECT_PTR, which has to move
>>>>>>> all the other entries down to fill the newly vacated space. Really
>>>>>>> the
>>>>>>> only reliable way to answer efficiency questions like this is to try
>>>>>>> both ways and see which works better in your application.
>>>>>> Thanks. I did not know about UNPROTECT_PTR.
>>>>>> I had concerns over the stack usage, but so far it did not prove too
>>>>>> much of a problem. Still, why isn't the tail recursion explicitly
>>>>>> made an iteration then ? This would take the "may be the compiler
>>>>>> figures it out, may be not" variable out of the equation.
>>>>>> 
>>>>> Careful - the protection stack (bookkeeping by R) has nothing to do
>>>>> with the C function call stack hence it has nothing to do with the
>>>>> compiler. The protection stack is global so usually you don't run out
>>>>> of it unless something goes horribly wrong (=infinite loop).
>>>> I think Laurent was referring to RecursiveRelease, which could use a lot
>>>> of C stack if you choose to release something that is deep in the list,
>>>> since it checks the head, and if that doesn't match, calls itself again
>>>> on the rest of the list. (I checked, and at least one version of gcc
>>>> doesn't recognize the tail recursion: it really does generate a
>>>> recursive call.)
>>>> 
>>>> Laurent asked why it isn't optimized to avoid the recursion: I think the
>>>> answer is simply because it is so rarely used that nobody has bothered.
>>> 
>>> Yes, I was referring to RecursiveRelease. Sorry if this was not clear.
>>> 
>>> What are the chances for a patch to be accepted ? At first sight(*),
>>> making that tail recursion an iterative function is not a major
>>> undertaking, and reviewing the patch be fairly straightforward... but
>>> I can always use that time otherwise if the answer to the question is
>>> "nil".
>> 
>> I don't think I would want to review such a patch (I don't know the
>> memory manager well, I don't know that there is really a case where it
>> matters enough to be worth doing), so I'd say if you don't get a message
>> from a core member volunteering to do so, you should assume it won't be
>> accepted. But that doesn't mean you shouldn't write the code for your
>> own internal use and edification, and if you can put together a demo
>> that shows it really makes a big difference in a realistic situation,
>> you might get a different response.
>> 
>> Duncan Murdoch
> 
> From what I understand, this has little to do with the memory manager, and resumes to the simple problem "how to remove an object from a list".
> 
> Something like this, using the amazing inline and inspect packages:
> 
> require( inline )
> require( inspect )
> 
> remover <- cfunction(signature( list = "language", object = "environment" ), '
> 	if( !isNull( list ) ){
> 		SEXP x = list ;
> 		SEXP y ;
> 		while( CAR(x) != object && CADR(x) != R_NilValue ){
> 			y = x ;
> 			x = CDR(x) ;
> 		}
> 		if( CAR(x) == object ) SETCDR(y, CDR(x) ) ;
> 	}
> 	return list ;

That blows up if the object is the first in the list BTW. You probably meant more something like

static SEXP ReleaseObj(SEXP object, SEXP list) {
	if (!isNull(list)) {
		if (CAR(list) != object) {
			SEXP c = list;
			while (!isNull(CDR(c))) {
				if (CADR(c) == object) {
					CDR(c) = CDDR(c);
					break;
				}
				c = CDR(c);
			}
		} else return CDR(list);
	}
	return list;
}
		
(For non-core replace CDR(c) with SETCDR(c) although that goes through the write barrier).

Cheers,
Simon


> ', Rcpp=FALSE, verbose=FALSE )
> 
> e <- new.env()
> call <- call( "foo", e, e, 1:10, 3 )
> call
> # inspect( call )
> result <- remover( call ,e )
> result
> # inspect( result )
> 
> gives this :
> 
> foo(10, <environment>, 0, <environment>, 1) 
> @0x9f4e0d0 06 LANGSXP [NAM(2)] 
>  @0x9f4e204 01 SYMSXP [] "foo" 
>  @0xa907b78 14 REALSXP [NAM(2)] (len=1, tl=1763713056) 
>  @0x9f4d564 04 ENVSXP [NAM(1)] 
>  FRAME: 
>    @0x9e94a10 00 NILSXP [MARK,NAM(2)] 
>  ENCLOS:
>    @0x9eb7b4c 04 ENVSXP [MARK,NAM(2),GP(0x8000)]
>  HASHTAB:
>    @0x9e94a10 00 NILSXP [MARK,NAM(2)]
>  @0xa907b58 14 REALSXP [NAM(2)] (len=1, tl=1936941344)
>  @0x9f4d564 04 ENVSXP [NAM(1)]
>  FRAME:
>    @0x9e94a10 00 NILSXP [MARK,NAM(2)]
>  ENCLOS:
>    @0x9eb7b4c 04 ENVSXP [MARK,NAM(2),GP(0x8000)]
>  HASHTAB:
>    @0x9e94a10 00 NILSXP [MARK,NAM(2)]
>  @0xa907b38 14 REALSXP [NAM(2)] (len=1, tl=543908709)
> NULL
> ==================
> foo(10, 0, <environment>, 1)
> @0x9f4e0d0 06 LANGSXP [NAM(2)]
>  @0x9f4e204 01 SYMSXP [] "foo"
>  @0xa907b78 14 REALSXP [NAM(2)] (len=1, tl=1763713056)
>  @0xa907b58 14 REALSXP [NAM(2)] (len=1, tl=1936941344)
>  @0x9f4d564 04 ENVSXP [NAM(2)]
>  FRAME:
>    @0x9e94a10 00 NILSXP [MARK,NAM(2)]
>  ENCLOS:
>    @0x9eb7b4c 04 ENVSXP [MARK,NAM(2),GP(0x8000)]
>  HASHTAB:
>    @0x9e94a10 00 NILSXP [MARK,NAM(2)]
>  @0xa907b38 14 REALSXP [NAM(2)] (len=1, tl=543908709)
> NULL
> 
> 
> 
> so it boils down to this as a replacement to RecursiveRelease :
> 
> /**
> * Removes the first instance of object with the list
> */
> static SEXP RemoveFromList( SEXP object, SEXP list){
> 	if( !isNull( list ) ){
> 		SEXP x = list ;
> 		SEXP y ;
> 		while( CAR(x) != object && CADR(x) != R_NilValue ){
> 			y = x ;
> 			x = CDR(x) ;
> 		}
> 		if( CAR(x) == object ) SETCDR(y, CDR(x) ) ;
> 	}
> 	return list ;
> }
> 
> 
> Romain
> 
> 
> -- 
> Romain Francois
> Professional R Enthusiast
> +33(0) 6 28 91 30 30
> http://romainfrancois.blog.free.fr
> |- http://tr.im/IW9B : C++ exceptions at the R level
> |- http://tr.im/IlMh : CPP package: exposing C++ objects
> `- http://tr.im/HlX9 : new package : bibtex
> 
> 
> 
> 



[Rd] iterative list rm [Was: R-devel Digest, Vol 83, Issue 2]
Simon Urbanek