[cfe-commits] Performance of OwningPtr/move()...

steve naroff snaroff at apple.com
Mon Dec 15 15:44:25 PST 2008


On Dec 15, 2008, at 6:34 PM, Howard Hinnant wrote:

> Hi Steve,
>
> Sorry for the slow response.  Been on plane all day and just now  
> sitting down to mail.
>
> As a rule when people give me code I can compile and play with  
> (thanks much!!) I don't reply until I've actually compiled and  
> played with it. However in my bleary-eyed state my judgement is  
> erring and I'm going to shoot from the hip anyway. :-)
>
> It looks to me like the main costs of these two samples are the  
> calls to new and delete:
>
> file           calls to new           calls to delete
> howard.cpp      20,000,000                   0
> howard2.cpp     20,000,000                20,000,000
>
> It appears to me that howard is twice as fast as howard2 because it  
> has half the calls into the new/delete library.  If I'm missing  
> something in this inspection, I'm sure someone will let me know  
> pretty fast. :-)
>

You are correct. When I add the explicit deletes, I get the following:

[steve-naroffs-imac-2:llvm/tools/clang] snaroff% time ./h1
1.911u 0.016s 0:01.94 98.9%	0+0k 0+0io 0pf+0w
[steve-naroffs-imac-2:llvm/tools/clang] snaroff% time ./h2
2.155u 0.008s 0:02.17 99.0%	0+0k 0+0io 0pf+0w

I knew that "howard.cpp" wasn't doing the deletes, however I assumed  
the "system" time accounted for that (my bad).

I'm really surprised that calling delete adds so much to "user" time.

So it looks like to overhead is closer to 10-15% for this small  
example (which is nothing to be alarmed about:-).

Thanks for the response,

snaroff

> -Howard
>
> On Dec 15, 2008, at 11:13 AM, steve naroff wrote:
>
>> Hey Howard,
>>
>> I've forwarded two simplistic modifications to your example.
>>
>> For this contrived example, the "abstraction penalty" appears to be  
>> 2x when compiled with -O2 (see user time below).
>>
>> Although this is a contrived example, the cost seems significant.
>>
>> What's your perspective?
>>
>> snaroff
>>
>> [steve-naroffs-imac-2:llvm/tools/clang] snaroff% cat howard.cpp
>> #include <iostream>
>> #include "OwningPtr.h"
>>
>> int* source(int i)
>> {
>>  llvm::OwningPtr<int> p(new int(i));
>>  return p.take();
>> }
>>
>> void binOp(int* a, int *b)
>> {
>>  int c = *a + *b;
>> }
>>
>> int main()
>> {
>>  for (int i = 0; i < 10000000; i++) {
>>    binOp(source(i), source(i+1));
>>  }
>> }
>>
>> [steve-naroffs-imac-2:llvm/tools/clang] snaroff% c++ howard.cpp -o  
>> h1 -O2
>> [steve-naroffs-imac-2:llvm/tools/clang] snaroff% cat howard2.cpp
>> #include <iostream>
>> #include "OwningPtr.h"
>>
>> llvm::OwningPtr<int> source(int i)
>> {
>> llvm::OwningPtr<int> p(new int(i));
>> return move(p);
>> }
>>
>> void binOp(llvm::OwningPtr<int> a, llvm::OwningPtr<int> b)
>> {
>> int c = *a + *b;
>> }
>>
>> int main()
>> {
>>  for (int i = 0; i < 10000000; i++) {
>>    binOp(source(i), source(i+1));
>>  }
>> }
>>
>> [steve-naroffs-imac-2:llvm/tools/clang] snaroff% c++ howard2.cpp -o  
>> h2 -O2
>> [steve-naroffs-imac-2:llvm/tools/clang] snaroff% time ./h1
>> 1.090u 0.290s 0:01.39 99.2%	0+0k 0+0io 0pf+0w
>> [steve-naroffs-imac-2:llvm/tools/clang] snaroff% time ./h2
>> 2.143u 0.006s 0:02.15 99.5%	0+0k 0+0io 0pf+0w
>>
>> On Dec 13, 2008, at 4:21 PM, Howard Hinnant wrote:
>>
>>> In an effort to "de-mystify" the subject around this "smart  
>>> pointer" application I would like to take include/llvm/ADT/ 
>>> OwningPtr.h as example code.  I've enclosed a modified OwningPtr.h  
>>> just as an example, not as a proposed change.  I encourage  
>>> everyone to do a diff between the enclosure and the current  
>>> OwningPtr.h (the diff is small & boilerplate).
>>>
>>> Consider this very simplistic sample code which is written to the  
>>> current OwningPtr.h interface:
>>>
>>> #include <iostream>
>>> #include "OwningPtr.h"
>>>
>>> int* source(int i)
>>> {
>>>  llvm::OwningPtr<int> p(new int(i));
>>>  std::cout << "source: " << *p << '\n';
>>>  return p.take();
>>> }
>>>
>>> void sink1(int* i)
>>> {
>>>  llvm::OwningPtr<int> p(i);
>>>  std::cout << "sink1: " << *p << '\n';
>>> }
>>>
>>> void sink2(int* i)
>>> {
>>>  std::cout << "sink2: " << *i << '\n';
>>> }
>>>
>>> int main()
>>> {
>>>  sink1(source(1));
>>>  sink2(source(2));
>>> }
>>>
>>> There is a "source" function which creates an OwningPtr, but does  
>>> not return an OwningPtr because OwningPtr isn't copy  
>>> constructible.  It instead returns a pointer to the data (just an  
>>> int here, likely an Expr in clang).
>>>
>>> There are two clients: sink1 and sink2.  The clients have similar  
>>> signatures, but different semantics.  sink1 takes ownership of the  
>>> pointer.  sink2 does not.  sink2 may be this way by design, or  
>>> this could be coding error.
>>>
>>> The example above compiles, and prints out:
>>>
>>> source: 1
>>> sink1: 1
>>> source: 2
>>> sink2: 2
>>>
>>> Because I ran it with a memory-leak checker, it also printed out:
>>>
>>> Leaking memory (8 bytes) at 0xb010
>>> Total memory leaked is 8 bytes
>>>
>>> Now consider substituting in the modified OwningPtr.h:
>>>
>>> source: 1
>>> sink1: 1
>>> source: 2
>>> sink2: 2
>>> Leaking memory (8 bytes) at 0xb010
>>> Total memory leaked is 8 bytes
>>>
>>> well, same problem.
>>>
>>> Note:  This modification won't alter existing behavior.  However  
>>> it enables precautions to be taken to make the code safer.  The  
>>> first precaution to be made is to alter "source" to return an  
>>> OwningPtr.  Before we couldn't do this.  Now we can:
>>>
>>> llvm::OwningPtr<int> source(int i)
>>> {
>>>  llvm::OwningPtr<int> p(new int(i));
>>>  std::cout << "source: " << *p << '\n';
>>>  return move(p);
>>> }
>>>
>>> Compile time errors:
>>>
>>> test.cpp: In function ‘int main()’:
>>> test.cpp:52: error: cannot convert ‘llvm::OwningPtr<int>’ to  
>>> ‘int*’ for argument ‘1’ to ‘void sink1(int*)’
>>> test.cpp:53: error: cannot convert ‘llvm::OwningPtr<int>’ to  
>>> ‘int*’ for argument ‘1’ to ‘void sink2(int*)’
>>>
>>> The compiler is now using the type system to alert the programmer  
>>> to possible memory leaks.  The first error is pointing to:
>>>
>>>  sink1(source(1));
>>>
>>> Ok, OwningPtr (correctly) will not implicitly convert to a raw  
>>> pointer.  We note that sink1 was designed to accept ownership of  
>>> the pointer so we modify it like so:
>>>
>>> void sink1(llvm::OwningPtr<int> p)
>>> {
>>>  std::cout << "sink1: " << *p << '\n';
>>> }
>>>
>>> Note that we can now use OwningPtr to pass by value to functions.   
>>> This is a pattern, like auto_ptr, that indicates to the client  
>>> that sink1 is accepting ownership of the referenced resource.
>>>
>>> The second error refers to:
>>>
>>>  sink2(source(2));
>>>
>>> Same problem.  We must now decide if sink2 is correct in not  
>>> claiming ownership of the resource, or if it is simply buggy.  If  
>>> it is a bug, we fix it the same way we fixed sink1.  So let's  
>>> assume that sink2 is correct as is.  We have to change our call  
>>> site then:
>>>
>>>  sink2(source(2).get());
>>>
>>> Now the sample compiles and prints out:
>>>
>>> source: 1
>>> sink1: 1
>>> source: 2
>>> sink2: 2
>>>
>>> There are no memory leaks.  By eliminating the use of "take()", we  
>>> have greatly reduced our exposure to leaks because unowned raw  
>>> pointers are never passed around.  Yet we can still pass in  
>>> (owned) raw pointers (as in the sink2 example) when the API calls  
>>> for it.
>>>
>>> Portability:  All compilers that enforce the rule that temporaries  
>>> can not bind to non-const references will compile this code.  And  
>>> there is a workaround for VC2005 (/Za off) which allows this  
>>> example to compile and behave identically.
>>>
>>> On VC2005 (/Za off), this modified OwningPtr will have  
>>> approximately the behavior of std::auto_ptr.  Everywhere else it  
>>> will refuse to move from lvalues with copy syntax:
>>>
>>> OwningPtr<int> p(new int(1));
>>> sink1(p);  // compile time error
>>>
>>> Summary:  This change allows us to use the type system to help  
>>> enforce resource ownership.  Exposure to unowned pointers (such as  
>>> what take() returns) is minimized but still allowed.
>>>
>>> For reference here is the complete altered example:
>>>
>>> #include <iostream>
>>> #include "OwningPtr.h"
>>>
>>> llvm::OwningPtr<int> source(int i)
>>> {
>>>  llvm::OwningPtr<int> p(new int(i));
>>>  std::cout << "source: " << *p << '\n';
>>>  return move(p);
>>> }
>>>
>>> void sink1(llvm::OwningPtr<int> p)  // owns int*
>>> {
>>>  std::cout << "sink1: " << *p << '\n';
>>> }
>>>
>>> void sink2(int* i)  // doesn't own int*
>>> {
>>>  std::cout << "sink2: " << *i << '\n';
>>> }
>>>
>>> int main()
>>> {
>>>  sink1(source(1));
>>>  sink2(source(2).get());
>>> }
>>>
>>> -Howard
>>> <OwningPtr.h>
>>> _______________________________________________
>>> cfe-commits mailing list
>>> cfe-commits at cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits
>>
>





More information about the cfe-commits mailing list