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

Howard Hinnant hhinnant at apple.com
Mon Dec 15 15:34:31 PST 2008


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. :-)

-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