请考虑以下代码:
class A
{
B* b; // an A object owns a B object
A() : b(NULL) { } // we don't know what b will be when constructing A
void calledVeryOften(…)
{
if (b)
delete b;
b = new B(param1, param2, param3, param4);
}
};
我的目标:我需要最大化性能,在这种情况下,这意味着最小化内存分配量。
这里显而易见的事情是将B* b;
更改为B b;
。我发现这种方法存在两个问题:
b
。由于我不知道b
将是什么,这意味着我需要将虚拟值传递给B的构造函数。哪个,IMO,很难看。calledVeryOften()
中,我必须执行以下操作:b = B(…)
,这有两个原因:
b
的析构函数不会被调用。b
,然后将调用临时实例的析构函数。可以避免复制和析构函数调用。更糟糕的是,调用析构函数很可能导致不良行为。那么我有什么办法可以避免使用new
?请记住:
答案 0 :(得分:8)
只需保留b所需的内存(通过池或手动),并在每次删除/ new时重复使用,而不是每次重新分配。
示例:
class A
{
B* b; // an A object owns a B object
bool initialized;
public:
A() : b( malloc( sizeof(B) ) ), initialized(false) { } // We reserve memory for b
~A() { if(initialized) destroy(); free(b); } // release memory only once we don't use it anymore
void calledVeryOften(…)
{
if (initialized)
destroy();
create();
}
private:
void destroy() { b->~B(); initialized = false; } // hand call to the destructor
void create( param1, param2, param3, param4 )
{
b = new (b) B( param1, param2, param3, param4 ); // in place new : only construct, don't allocate but use the memory that the provided pointer point to
initialized = true;
}
};
在某些情况下,Pool或ObjectPool可以更好地实现相同的想法。
构造/销毁成本将仅依赖于B类的构造函数和析构函数。
答案 1 :(得分:8)
我喜欢Klaim的回答,所以我写得很快。我没有声称完美正确,但它对我来说看起来很不错。 (即,它唯一的测试是下面的样本main
)
这是一个通用的懒惰初始化程序。对象的空间分配一次,对象从null开始。然后,您可以create
覆盖以前的对象,而无需新的内存分配。
它实现了所有必要的构造函数,析构函数,复制/赋值,交换,yadda-yadda。你走了:
#include <cassert>
#include <new>
template <typename T>
class lazy_object
{
public:
// types
typedef T value_type;
typedef const T const_value_type;
typedef value_type& reference;
typedef const_value_type& const_reference;
typedef value_type* pointer;
typedef const_value_type* const_pointer;
// creation
lazy_object(void) :
mObject(0),
mBuffer(::operator new(sizeof(T)))
{
}
lazy_object(const lazy_object& pRhs) :
mObject(0),
mBuffer(::operator new(sizeof(T)))
{
if (pRhs.exists())
{
mObject = new (buffer()) T(pRhs.get());
}
}
lazy_object& operator=(lazy_object pRhs)
{
pRhs.swap(*this);
return *this;
}
~lazy_object(void)
{
destroy();
::operator delete(mBuffer);
}
// need to make multiple versions of this.
// variadic templates/Boost.PreProccesor
// would help immensely. For now, I give
// two, but it's easy to make more.
void create(void)
{
destroy();
mObject = new (buffer()) T();
}
template <typename A1>
void create(const A1 pA1)
{
destroy();
mObject = new (buffer()) T(pA1);
}
void destroy(void)
{
if (exists())
{
mObject->~T();
mObject = 0;
}
}
void swap(lazy_object& pRhs)
{
std::swap(mObject, pRhs.mObject);
std::swap(mBuffer, pRhs.mBuffer);
}
// access
reference get(void)
{
return *get_ptr();
}
const_reference get(void) const
{
return *get_ptr();
}
pointer get_ptr(void)
{
assert(exists());
return mObject;
}
const_pointer get_ptr(void) const
{
assert(exists());
return mObject;
}
void* buffer(void)
{
return mBuffer;
}
// query
const bool exists(void) const
{
return mObject != 0;
}
private:
// members
pointer mObject;
void* mBuffer;
};
// explicit swaps for generality
template <typename T>
void swap(lazy_object<T>& pLhs, lazy_object<T>& pRhs)
{
pLhs.swap(pRhs);
}
// if the above code is in a namespace, don't put this in it!
// specializations in global namespace std are allowed.
namespace std
{
template <typename T>
void swap(lazy_object<T>& pLhs, lazy_object<T>& pRhs)
{
pLhs.swap(pRhs);
}
}
// test use
#include <iostream>
int main(void)
{
// basic usage
lazy_object<int> i;
i.create();
i.get() = 5;
std::cout << i.get() << std::endl;
// asserts (not created yet)
lazy_object<double> d;
std::cout << d.get() << std::endl;
}
在您的情况下,只需在班级中创建一个成员:lazy_object<B>
即可完成。没有手动发布或制作复制构造函数,析构函数等。一切都在你漂亮的小型可重用类中得到了解决。 :)
删除了对vector的需要,应该节省一些空间和什么不是。
这使用aligned_storage
和alignment_of
来使用堆栈而不是堆。我使用boost,但TR1和C ++ 0x都存在此功能。我们失去了复制的能力,因此无法交换。
#include <boost/type_traits/aligned_storage.hpp>
#include <cassert>
#include <new>
template <typename T>
class lazy_object_stack
{
public:
// types
typedef T value_type;
typedef const T const_value_type;
typedef value_type& reference;
typedef const_value_type& const_reference;
typedef value_type* pointer;
typedef const_value_type* const_pointer;
// creation
lazy_object_stack(void) :
mObject(0)
{
}
~lazy_object_stack(void)
{
destroy();
}
// need to make multiple versions of this.
// variadic templates/Boost.PreProccesor
// would help immensely. For now, I give
// two, but it's easy to make more.
void create(void)
{
destroy();
mObject = new (buffer()) T();
}
template <typename A1>
void create(const A1 pA1)
{
destroy();
mObject = new (buffer()) T(pA1);
}
void destroy(void)
{
if (exists())
{
mObject->~T();
mObject = 0;
}
}
// access
reference get(void)
{
return *get_ptr();
}
const_reference get(void) const
{
return *get_ptr();
}
pointer get_ptr(void)
{
assert(exists());
return mObject;
}
const_pointer get_ptr(void) const
{
assert(exists());
return mObject;
}
void* buffer(void)
{
return mBuffer.address();
}
// query
const bool exists(void) const
{
return mObject != 0;
}
private:
// types
typedef boost::aligned_storage<sizeof(T),
boost::alignment_of<T>::value> storage_type;
// members
pointer mObject;
storage_type mBuffer;
// non-copyable
lazy_object_stack(const lazy_object_stack& pRhs);
lazy_object_stack& operator=(lazy_object_stack pRhs);
};
// test use
#include <iostream>
int main(void)
{
// basic usage
lazy_object_stack<int> i;
i.create();
i.get() = 5;
std::cout << i.get() << std::endl;
// asserts (not created yet)
lazy_object_stack<double> d;
std::cout << d.get() << std::endl;
}
我们走了。
答案 2 :(得分:5)
如何为B分配一次内存(或使用最大可能的变体)并使用placement new?
A会存储char memB[sizeof(BiggestB)];
和B*
。当然,您需要手动调用析构函数,但不会分配/取消分配内存。
void* p = memB;
B* b = new(p) SomeB();
...
b->~B(); // explicit destructor call when needed.
答案 3 :(得分:3)
如果B
正确实现了其复制赋值运算符,则b = B(...)
不应调用b
上的任何析构函数。这是解决问题最明显的方法。
但是,如果B
无法正确“默认”初始化,则可以执行此类操作。我只会推荐这种方法作为最后的手段,因为它很难安全。未经测试,很可能是角落案例异常错误:
// Used to clean up raw memory of construction of B fails
struct PlacementHelper
{
PlacementHelper() : placement(NULL)
{
}
~PlacementHelper()
{
operator delete(placement);
}
void* placement;
};
void calledVeryOften(....)
{
PlacementHelper hp;
if (b == NULL)
{
hp.placement = operator new(sizeof(B));
}
else
{
hp.placement = b;
b->~B();
b = NULL; // We can't let b be non-null but point at an invalid B
}
// If construction throws, hp will clean up the raw memory
b = new (placement) B(param1, param2, param3, param4);
// Stop hp from cleaning up; b points at a valid object
hp.placement = NULL;
}
答案 4 :(得分:3)
快速测试Martin York的断言,即这是一个过早的优化,并且新/删除的优化远远超出了仅仅是程序员改进的能力。显然,提问者必须计算他自己的代码,看看是否避免使用新的/删除帮助他,但在我看来,对于某些类和使用它会产生很大的不同:
#include <iostream>
#include <vector>
int g_construct = 0;
int g_destruct = 0;
struct A {
std::vector<int> vec;
A (int a, int b) : vec((a*b) % 2) { ++g_construct; }
~A() {
++g_destruct;
}
};
int main() {
const int times = 10*1000*1000;
#if DYNAMIC
std::cout << "dynamic\n";
A *x = new A(1,3);
for (int i = 0; i < times; ++i) {
delete x;
x = new A(i,3);
}
#else
std::cout << "automatic\n";
char x[sizeof(A)];
A* yzz = new (x) A(1,3);
for (int i = 0; i < times; ++i) {
yzz->~A();
new (x) A(i,3);
}
#endif
std::cout << g_construct << " constructors and " << g_destruct << " destructors\n";
}
$ g++ allocperf.cpp -oallocperf -O3 -DDYNAMIC=0 -g && time ./allocperf
automatic
10000001 constructors and 10000000 destructors
real 0m7.718s
user 0m7.671s
sys 0m0.030s
$ g++ allocperf.cpp -oallocperf -O3 -DDYNAMIC=1 -g && time ./allocperf
dynamic
10000001 constructors and 10000000 destructors
real 0m15.188s
user 0m15.077s
sys 0m0.047s
这大致与我的预期相同:GMan风格(破坏/放置新)代码需要两倍的时间,并且大概是分配的两倍。如果A的向量成员被替换为int,那么GMan样式的代码只需要几分之一秒。这是GCC 3。
$ g++-4 allocperf.cpp -oallocperf -O3 -DDYNAMIC=1 -g && time ./allocperf
dynamic
10000001 constructors and 10000000 destructors
real 0m5.969s
user 0m5.905s
sys 0m0.030s
$ g++-4 allocperf.cpp -oallocperf -O3 -DDYNAMIC=0 -g && time ./allocperf
automatic
10000001 constructors and 10000000 destructors
real 0m2.047s
user 0m1.983s
sys 0m0.000s
但是我不太确定:现在删除/新版本需要破坏/放置新版本的三倍。
[编辑:我想我已经弄明白了 - GCC 4在0大小的向量上更快,实际上从两个版本的代码中减去了一个恒定的时间。将(a*b)%2
更改为(a*b)%2+1
会恢复2:1的时间比率,而3.7s则为7.5]
请注意,我没有采取任何特殊步骤来正确对齐堆栈数组,但打印地址显示它是16对齐的。
此外,-g不会影响时间。在我看着objdump检查-O3没有完全移除循环后,我意外地离开了它。这个指针叫做yzz,因为搜索“y”并没有像我希望的那样好。但是我没有它就重新运行。
答案 5 :(得分:1)
您确定内存分配是您认为的瓶颈吗? B的构造函数是否很快?
如果内存分配是真正的问题,那么在这里放置新的或一些其他解决方案可能会有所帮助。
如果param [1..4]的类型和范围合理,并且B构造函数“重”,您可能还会考虑使用缓存的B集。这假设您实际上允许有多个例如,它不会以资源为前提。
答案 6 :(得分:1)
像其他人已经建议的那样:尝试安置新的..
这是一个完整的例子:
#include <new>
#include <stdio.h>
class B
{
public:
int dummy;
B (int arg)
{
dummy = arg;
printf ("C'Tor called\n");
}
~B ()
{
printf ("D'tor called\n");
}
};
void called_often (B * arg)
{
// call D'tor without freeing memory:
arg->~B();
// call C'tor without allocating memory:
arg = new(arg) B(10);
}
int main (int argc, char **args)
{
B test(1);
called_often (&test);
}
答案 7 :(得分:0)
我会在这里boost::scoped_ptr:
class A: boost::noncopyable
{
typedef boost::scoped_ptr<B> b_ptr;
b_ptr pb_;
public:
A() : pb_() {}
void calledVeryOften( /*…*/ )
{
pb_.reset( new B( params )); // old instance deallocated
// safely use *pb_ as reference to instance of B
}
};
不需要手工制作的析构函数,A
是不可复制的,因为它应该在您的原始代码中,而不是在复制/赋值时泄漏内存。
答案 8 :(得分:0)
A() : b(new B()) { }
void calledVeryOften(…)
{
b->setValues(param1, param2, param3, param4);
}
(或单独设置,因为您无法访问B
类 - 这些值执行有mutator-methods,对吗?)
答案 9 :(得分:0)
只需拥有一堆以前使用过的Bs,然后重复使用它们。