6

我目前正在进行一个性能至关重要的项目。以下是我对这个问题提出的一些问题。

问题1

我的项目涉及很多。boost::shared_ptr我知道在运行时使用创建共享指针boost::make_shared很慢,因为它需要跟踪引用有很多开销。我想知道如果已经创建了 boost 共享指针,那么这两个语句会有什么相同的性能,或者一个比另一个更快。如果常规指针更快并且我已经拥有共享指针,我有哪些选项可以调用共享指针指向的方法?

 statement1: sharedptr->someMethod();  //here the pointer is a shared ptr created by boost::make_shared
 statement2: regularptr->someMethod(); //here the pointer is a regular one made with new

问题2

我有一个快速调用的实例方法,std::vector<std::string>每次都会在堆栈上创建一个。我决定将该向量指针存储在静态 std::map (ie)std::map<std::String,std::vector<std::string>*>中。如果键的映射中不存在向量(可能是方法的名称)。有效的矢量地址被创建并添加到地图中。所以我的问题是“是否值得在地图上搜索矢量地址并返回一个有效地址,而不是像在堆栈上创建一个一样std::vector<std::string> somevector。我也想了解一下查找的性能std::map

任何有关这些问题的想法将不胜感激。

4

4 回答 4

13

回答问题#1

如果常规指针更快并且我已经有了共享指针,我有哪些选项可以调用共享指针指向的方法?

operator->boost::shared_ptr 有断言

typename boost::detail::sp_member_access< T >::type operator-> () const 
{
    BOOST_ASSERT( px != 0 );
    return px;
}

因此,首先,请确保您已NDEBUG定义(通常在发布版本中它是自动完成的):

#define NDEBUG

我在取消引用boost::shared_ptr和原始指针之间进行了汇编程序比较:

template<int tag,typename T>
NOINLINE void test(const T &p)
{
    volatile auto anti_opti=0;
    ASM_MARKER<tag+0>();
    anti_opti = p->data;
    anti_opti = p->data;
    ASM_MARKER<tag+1>();
    (void)anti_opti;
}

test<1000>(new Foo);

ASMtest什么时候的代码TFoo*(不要害怕,我在diff下面):

_Z4testILi1000EP3FooEvRKT0_:
.LFB4088:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movq %rdi, %rbx
subq $16, %rsp
.cfi_def_cfa_offset 32
movl $0, 12(%rsp)
call _Z10ASM_MARKERILi1000EEvv
movq (%rbx), %rax
movl (%rax), %eax
movl %eax, 12(%rsp)
movl %eax, 12(%rsp)
call _Z10ASM_MARKERILi1001EEvv
movl 12(%rsp), %eax
addq $16, %rsp
.cfi_def_cfa_offset 16
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc

test<2000>(boost::make_shared<Foo>());

ASMtest什么时候的代码Tboost::shared_ptr<Foo>

_Z4testILi2000EN5boost10shared_ptrI3FooEEEvRKT0_:
.LFB4090:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movq %rdi, %rbx
subq $16, %rsp
.cfi_def_cfa_offset 32
movl $0, 12(%rsp)
call _Z10ASM_MARKERILi2000EEvv
movq (%rbx), %rax
movl (%rax), %eax
movl %eax, 12(%rsp)
movl %eax, 12(%rsp)
call _Z10ASM_MARKERILi2001EEvv
movl 12(%rsp), %eax
addq $16, %rsp
.cfi_def_cfa_offset 16
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc

这是diff -U 0 foo_p.asm shared_ptr_foo_p.asm命令的输出:

--- foo_p.asm   Fri Apr 12 10:38:05 2013
+++ shared_ptr_foo_p.asm        Fri Apr 12 10:37:52 2013
@@ -1,2 +1,2 @@
-_Z4testILi1000EP3FooEvRKT0_:
-.LFB4088:
+_Z4testILi2000EN5boost10shared_ptrI3FooEEEvRKT0_:
+.LFB4090:
@@ -11 +11 @@
-call _Z10ASM_MARKERILi1000EEvv
+call _Z10ASM_MARKERILi2000EEvv
@@ -16 +16 @@
-call _Z10ASM_MARKERILi1001EEvv
+call _Z10ASM_MARKERILi2001EEvv

如您所见,区别仅在于函数签名和tag非类型模板参数值,其余代码为IDENTICAL.


一般来说 -shared_ptr非常昂贵 - 它的引用计数在线程之间同步(通常通过原子操作)。如果您要boost::intrusive_ptr改用,那么您可以实现自己的increment/decrement不使用线程同步,这将加快引用计数。

如果你负担得起使用unique_ptr或移动语义(通过Boost.Move或 C++11)——那么就不会有任何引用计数——它会更快。

带 ASM 输出的现场演示

#define NDEBUG

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>

#define NOINLINE __attribute__ ((noinline))

template<int>
NOINLINE void ASM_MARKER()
{
    volatile auto anti_opti = 11;
    (void)anti_opti;
}

struct Foo
{
    int data;
};

template<int tag,typename T>
NOINLINE void test(const T &p)
{
    volatile auto anti_opti=0;
    ASM_MARKER<tag+0>();
    anti_opti = p->data;
    anti_opti = p->data;
    ASM_MARKER<tag+1>();
    (void)anti_opti;
}

int main()
{
    {
        auto p = new Foo;
        test<1000>(p);
        delete p;
    }
    {
        test<2000>(boost::make_shared<Foo>());
    }
}

回答问题#2

我有一个快速调用的实例方法,每次都会在堆栈上创建一个 std::vector 。

通常,尝试重用vector's 容量以防止代价高昂的重新分配是个好主意。例如最好替换:

{
    for(/*...*/)
    {
        std::vector<value> temp;
        // do work on temp
    }
}

和:

{
    std::vector<value> temp;
    for(/*...*/)
    {
        // do work on temp
        temp.clear();
    }
}

但是由于类型的原因,std::map<std::string,std::vector<std::string>*>您似乎正在尝试执行某种记忆

正如已经建议的那样,您可以尝试使用具有std::mapO ( 1)平均和O(N)最坏情况复杂度的查找/插入以及更好的局部性/紧凑性,而不是O(ln(N))查找/插入 (缓存友好)。boost::unordered_mapstd::unordered_map

另外,考虑尝试Boost.Flyweight

元是小型句柄类,授予对共享公共数据的持续访问权限,因此允许在合理的内存限制内管理大量实体。Boost.Flyweight通过提供类模板flyweight可以轻松使用这种常见的编程习惯,它充当const T的替代品。

于 2013-04-12T05:58:25.997 回答
4

对于问题1:

主要的性能提升可以在架构设计、使用的算法中实现,而低级别的关注点也只有在高级设计强大时才重要。让我们来回答您的问题,常规指针性能高于 shared_ptr。但是你看到的不使用 shared_ptr 的开销也更多,这增加了长期维护代码的成本。在性能关键的应用程序中必须避免冗余对象的创建和销毁。在这种情况下,shared_ptr 起着重要的作用,它通过减少释放资源的开销来跨线程共享公共对象。是的,由于引用计数、分配(对象、计数器、删除器)等原因,共享指针比常规指针消耗更多时间。您可以通过防止不必要的复制来使 shared_ptr 更快。将其用作引用(shared_ptr const&)。

问题2

如果想使用 shared_ptr 对象的重用池,您可以更好地研究对象池设计模式方法。 http://en.wikipedia.org/wiki/Object_pool_pattern

于 2013-04-12T05:50:29.350 回答
2

问题一:

我在我的项目中广泛使用共享指针,但我不想使用shared_ptr<T>. 它需要一个与自身分开分配的堆对象T,因此内存分配开销增加了一倍,内存使用量的增加取决于运行时库的实现。intrusive_ptr效率更高,但有一个关键问题让我感到厌烦,那就是函数调用:

void Foo(intrusive_ptr<T> x) {...}

每次调用 Foo 时,参数 x 的引用计数必须以相对昂贵的原子增量递增,然后在退出时递减。但这是多余的,因为您通常可以假设调用者已经拥有对 x 的引用,并且该引用在调用期间有效。调用者可能还没有引用,但以调用者的引用始终有效的方式编写代码并不难。

因此,我更喜欢使用我自己的智能指针类,它与 intrusive_ptr 相同,只是它隐式地与 T* 相互转换。然后我总是声明我的方法采用普通指针,避免不必要的引用计数:

void Foo(T* x) {...}

事实证明,这种方法在我的项目中效果很好,但老实说,我从未真正测量过它带来的性能差异。

此外,尽可能使用 auto_ptr (C++03) 或 unique_ptr (C++11)。

问题2:

我不明白您为什么要考虑使用 std::map。首先,hash_map 会更快(只要它不是 VS2008/2010 中的 VC++ Dinkumware 实现,这里有详细信息),其次,如果每个方法只需要一个向量,为什么不使用类型的静态变量std::vector<std::string>

如果每次调用该方法时都必须在哈希表中查找向量,我的猜测是与每次创建新向量相比,您将节省很少或根本没有时间。如果您在 std::map 中查找向量,则需要更长的时间。

于 2013-04-12T06:21:15.163 回答
1

Q1:只看实现:

T * operator-> () const // never throws
{
    BOOST_ASSERT(px != 0);
    return px;
}

显然,它返回一个成员变量并且不即时计算任何东西,因此性能将与取消引用普通指针一样快(受编译器优化的常见怪癖/未优化构建的性能总是可以预期的糟糕 - 不值得考虑)。

Q2:“是否值得搜索一个map地址vector并返回一个有效地址,而不是像在堆栈上创建一个一样std::vector<std::string> somevector。我还想知道关于性能的想法std::map::find。”

是否值得取决于必须在 中复制的数据量,在vector较小程度上取决于 中的节点数量,map被比较的键中公共前缀的长度等。一如既往,如果你在乎,基准。不过,一般来说,如果向量包含大量数据(或者该数据的再生速度很慢),我希望答案是肯定的。 std::map是一个平衡二叉树,所以通常你期望在 O(log2N) 中查找,其中 N 是当前元素的数量(即size())。

您也可以使用哈希表 - 它给出 O(1) 对于大量元素来说会更快,但不可能说出阈值在哪里。性能仍然取决于您在密钥上使用的散列函数的昂贵性,它们的长度(像微软这样的一些散列实现std::hash只包含沿着被散列的字符串间隔的最多 10 个字符,因此所花费的时间有一个上限,但冲突的可能性更大) ,哈希表冲突处理方法(例如,用于搜索替代存储桶的置换列表与替代哈希函数与从存储桶链接的容器),以及冲突倾向本身。

于 2013-04-12T06:01:51.433 回答