[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