我目前正处于一个项目的中间,其中性能是至关重要的。以下是我对这个问题的一些疑问。
问题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
我有一个实例方法(s),它被快速调用,每次在堆栈上创建一个std::vector<std::string>
。我决定将矢量指针存储在静态std::map(即std::map<std::String,std::vector<std::string>*>
)中。如果键的映射中不存在向量(可以是方法名)。创建有效的矢量地址并将其添加到映射中。所以我的问题是"是否值得在映射中搜索向量地址并返回有效地址,而不是像std::vector<std::string> somevector
那样在堆栈上创建一个。"我还想对std::map
find的性能有一个想法。
如果有任何关于这些问题的想法,我将不胜感激。
回答问# 1
如果普通指针更快,我已经有了共享指针,为了调用共享指针指向的方法,我有什么选择?
boost::shared_ptr
中的operator->
有断言:
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);
test
的ASM
代码当T
是Foo*
是(不要害怕,我下面有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>());
T
为boost::shared_ptr<Foo>
时test
的ASM
code:
_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)。移动或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
的容量以防止代价高昂的重新分配是一个好主意。例如:
{
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::map
具有O(ln(N))查找/插入,您可以尝试使用boost::unordered_map
/std::unordered_map
,它具有O(1)平均值和O(N)最坏情况的查找/插入复杂度,以及更好的局域性/紧凑性(缓存友好)。
另外,考虑尝试Boost。轻量级选手:
Flyweights是小型句柄类,授予对共享公共数据的恒定访问,从而允许在合理的内存限制内管理大量实体。提振。Flyweight通过提供类模板 Flyweight使使用这种常见的编程风格变得容易,它充当const T的临时替代品。
For Question1:
主要的性能增益可以在架构设计中实现,使用算法,而低层关注也只有在高层设计强大时才重要。让我们回到你的问题,常规指针的性能比shared_ptr更高。但是,不使用shared_ptr的开销也更多,这增加了长期运行时维护代码的成本。在性能关键型应用程序中必须避免冗余的对象创建和销毁。在这种情况下,shared_ptr在跨线程共享公共对象方面起着重要作用通过减少释放资源的开销。是的,共享指针比普通指针消耗更多的时间,因为重计数,分配(对象,计数器,删除器)等。您可以通过防止不必要的复制来提高shared_ptr的速度。使用它作为ref(shared_ptr const&)。此外,如果你不需要跨线程共享资源,不要使用shared_ptr,在这种情况下,常规ptr会提供更好的性能。
问题2
如果想使用重用池的shared_ptr对象,可以更好地研究对象池设计模式的方法。http://en.wikipedia.org/wiki/Object_pool_pattern
问题1:
我在我的项目中广泛使用共享指针,但我不想使用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会更快(只要它不是vc++ Dinkumware在VS2008/2010中的实现,细节在这里某处),其次,如果每个方法只需要一个向量,为什么不使用std::vector<std::string>
类型的静态变量?
如果每次调用该方法时都必须在哈希表中查找向量,我的猜测是,与每次创建新向量相比,您将节省很少或根本没有时间。如果在std::map中查找vector对象,则需要更长的时间。
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个字符,因此所花费的时间有上限,但更有可能发生冲突),哈希表冲突处理方法(例如,置换列表搜索备选桶、备选哈希函数、从桶链出的容器),以及碰撞倾向性。