unique_ptr vs类实例作为成员变量 [英] unique_ptr vs class instance as member variable

查看:266
本文介绍了unique_ptr vs类实例作为成员变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有一个类 SomeClass ,它保存对这些数据进行操作的一些数据和方法。它必须创建一些参数,如:

There is a class SomeClass which holds some data and methods that operates on this data. And it must be created with some arguments like:

SomeClass(int some_val, float another_val);

还有另一个类,例如 Manager 其中包括 SomeClass ,并大量使用其方法。

There is another class, say Manager, which includes SomeClass, and heavily uses its methods.

locale,cache hits等),将 SomeClass 的对象声明为 Manager 的成员,并在 Manager 的构造函数或声明SomeClass的对象为unique_ptr?

So, what would be better in terms of performance (data locality, cache hits, etc.), declare object of SomeClass as member of Manager and use member initialization in Manager's constructor or declare object of SomeClass as unique_ptr?

class Manager
{    
public:    
    Manager() : some(5, 3.0f) {}

private:
    SomeClass some;    
};

class Manager
{
public:
    Manager();

private:
    std::unique_ptr<SomeClass> some;
}


推荐答案



很可能,在访问子对象的运行时效率方面没有差别。但是使用指针可能会因为几个原因而变慢(见下面的细节)。

Short answer

Most likely, there is no difference in runtime efficiency of accessing your subobject. But using pointer can be slower for several reasons (see details below).

此外,还有一些其他的事情你应该记住:

Moreover, there are several other things you should remember:


  1. 当使用指针时,通常需要为子对象单独分配/ deallocate内存,这需要一些时间(
  2. 当使用指针时,你可以在不复制的情况下便宜地移动子对象。

说到编译时,指针优于普通成员。对于plain成员,您不能删除 SomeClass 声明中 Manager 声明的依赖关系。有了指针,你可以用向前的声明。

Speaking of compile times, pointer is better than plain member. With plain member, you cannot remove dependency of Manager declaration on SomeClass declaration. With pointers, you can do it with forward declaration. Less dependencies may result is less build times.

我想提供有关性能的更多详细信息的子对象访问。我认为使用指针可能比使用普通成员慢几种原因:

I'd like to provide more details about performance of subobject accesses. I think that using pointer can be slower than using plain member for several reasons:


  1. 数据位置(和缓存性能)可能更好与平原成员。您通常一起访问 Manager SomeClass 的数据,并且plain成员保证靠近其他数据,而heap分配可能会将对象和子对象放置得彼此远离。

  2. 使用指针意味着一个层次的间接。要获取普通成员的地址,可以简单地添加一个编译时常量偏移fo对象地址(通常与其他汇编指令合并)。当使用指针时,你必须从成员指针中额外读取一个字,以获得指向子对象的实际指针。请参见 Q1
  3. https://en.wikipedia.org/wiki/Pointer_aliasingrel =nofollow>别名可能是最重要的问题。如果你使用普通成员,那么编译器可以假设:你的子对象完全在内存中的对象内,并且不与你的对象的其他成员重叠。当使用指针时,编译器通常不能假设像这样:你的子对象可能会与您的对象及其成员重叠。因此,编译器必须生成更多无用的加载/存储操作,因为它认为某些值可能会改变。
  1. Data locality (and cache performance) is likely to be better with plain member. You usually access data of Manager and SomeClass together, and plain member is guaranteed to be near other data, while heap allocations may place object and subobject far from each other.
  2. Using pointer means one more level of indirection. To get address of a plain member, you can simply add a compile-time constant offset fo object address (which is often merged with other assembly instruction). When using pointer, you have to additionally read a word from the member pointer to get actual pointer to subobject. See Q1 and Q2 for more details.
  3. Aliasing is perhaps the most important issue. If you are using plain member, then compiler can assume that: your subobject lies fully within your object in memory, and it does not overlap with other members of your object. When using pointer, compiler often cannot assume anything like this: you subobject may overlap with your object and its members. As a result, compiler has to generate more useless load/store operations, because it thinks that some values may change.

上一个问题的示例(完整代码为此处):

Here is an example for the last issue (full code is here):

struct IntValue {
    int x;
    IntValue(int x) : x(x) {}
};
class MyClass_Ptr {
    unique_ptr<IntValue> a, b, c;
public:
    void Compute() {
        a->x += b->x + c->x;
        b->x += a->x + c->x;
        c->x += a->x + b->x;
    }
};

显然,存储子对象 a b c 。我已经测量了花费在对单个对象的 Compute 方法的十亿次调用中花费的时间。下面是具有不同配置的结果:

Clearly, it is stupid to store subobjects a, b, c by pointers. I've measured time spent in one billion calls of Compute method for a single object. Here are results with different configurations:

2.3 sec:    plain member (MinGW 5.1.0)
2.0 sec:    plain member (MSVC 2013)
4.3 sec:    unique_ptr   (MinGW 5.1.0)
9.3 sec:    unique_ptr   (MSVC 2013)

在每种情况下查看最内层循环的生成的程序集时,很容易理解为什么时间如此不同:

When looking at the generated assembly for innermost loop in each case, it is easy to understand why the times are so different:

;;; plain member (GCC)
lea edx, [rcx+rax]   ; well-optimized code: only additions on registers
add r8d, edx         ; all 6 additions present (no CSE optimization)
lea edx, [r8+rax]    ; ('lea' instruction is also addition BTW)
add ecx, edx
lea edx, [r8+rcx]
add eax, edx
sub r9d, 1
jne .L3

;;; plain member (MSVC)
add ecx, r8d  ; well-optimized code: only additions on registers
add edx, ecx  ; 5 additions instead of 6 due to a common subexpression eliminated
add ecx, edx
add r8d, edx
add r8d, ecx
dec r9
jne SHORT $LL6@main

;;; unique_ptr (GCC)
add eax, DWORD PTR [rcx]   ; slow code: a lot of memory accesses
add eax, DWORD PTR [rdx]   ; each addition loads value from memory
mov DWORD PTR [rdx], eax   ; each sum is stored to memory
add eax, DWORD PTR [r8]    ; compiler is afraid that some values may be at same address
add eax, DWORD PTR [rcx]
mov DWORD PTR [rcx], eax
add eax, DWORD PTR [rdx]
add eax, DWORD PTR [r8]
sub r9d, 1
mov DWORD PTR [r8], eax
jne .L4

;;; unique_ptr (MSVC)
mov r9, QWORD PTR [rbx]       ; awful code: 15 loads, 3 stores
mov rcx, QWORD PTR [rbx+8]    ; compiler thinks that values may share 
mov rdx, QWORD PTR [rbx+16]   ;   same address with pointers to values!
mov r8d, DWORD PTR [rcx]
add r8d, DWORD PTR [rdx]
add DWORD PTR [r9], r8d
mov r8, QWORD PTR [rbx+8]
mov rcx, QWORD PTR [rbx]      ; load value of 'a' pointer from memory
mov rax, QWORD PTR [rbx+16]
mov edx, DWORD PTR [rcx]      ; load value of 'a->x' from memory
add edx, DWORD PTR [rax]      ; add the 'c->x' value
add DWORD PTR [r8], edx       ; add sum 'a->x + c->x' to 'b->x'
mov r9, QWORD PTR [rbx+16]
mov rax, QWORD PTR [rbx]      ; load value of 'a' pointer again =)
mov rdx, QWORD PTR [rbx+8]
mov r8d, DWORD PTR [rax]
add r8d, DWORD PTR [rdx]
add DWORD PTR [r9], r8d
dec rsi
jne SHORT $LL3@main

这篇关于unique_ptr vs类实例作为成员变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆