1

在表演之前人们把我的脑袋扯下来:是的,在问这个之前我已经做了分析:)

我再次查看我的容器类型之一,虽然我有一个可行的解决方案,但性能很差,因为缓存的每种类型的项目都会导致堆上的单独分配(这当然很昂贵)。

基于对我的程序输入的静态分析,我想出了一种方法来了解所有可能放入我正在传递的缓存对象中的对象所需的总大小。基本上,我有一个可以在给定缓存对象中构造的对象列表,所以我知道我可能需要缓存的大小是多少,但不是在编译时——仅在运行时。

基本上,我想做的是boost::make_shared——获取一个内存块,并shared_ptr在同一个内存块中构造位以及受控对象。

我不必担心保留复制行为,因为缓存对象是不可复制的,并且由客户端通过指针传递(它通常存储在 aptr_vector或 a之类的东西中std::auto_ptr)。

但是,我不熟悉如何实现这样的容器,即如何遵循对齐限制等。

在伪代码中,我想做的是:

//I know a lot of what's in here is not portable -- I need to run only on x86
//and x64 machines. Yes, this couple of classes looks hacky, but I'd rather
//have one hacky class than a whole programfull :)

class CacheRegistrar
{
    //Blah blah
public:
    //Figures out what objects will be in the cache, etc
    const std::vector<std::size_t>& GetRequiredObjectSizes() const;
    //Other stuff...
    template <typename T>
    void RegisterCacheObject();
    template <typename T>
    std::size_t GetObjectIndex() const;
    // etc.
};

class CacheObject;

std::auto_ptr<CacheObject> CacheObjectFactory(const CacheRegistrar& registrar)
{
    //Pretend this is in a CPP file and therefore CacheObject is defined...
    const std::vector<size_t>& sizes(registrar.GetRequiredObjectSizes());
    std::size_t sumOfCache = std::accumulate(sizes.begin(), sizes.end());
    sumOfCache += sizeof(CacheObject);
    boost::scoped_array<char> buffer(new char[] sumOfCache);
    CacheObject *obj = new (reinterpret_cast<void *>(buffer.get())) CacheObject;
    buffer.release(); //PSEUDOCODE (boost::scoped_array has no release member);
    return std::auto_ptr<CacheObject>(obj); //Nothrow
}

class CacheObject
{
    CacheRegistrar *registrar; //Set by my constructor
public:
    template<typename T>
    T& Get()
    {
        char * startOfCache = reinterpret_cast<char *>(this) + 
            sizeof(CacheObject);
        char * cacheItem = startOfCache + registrar->GetObjectIndex<T>();
        return *reinterpret_cast<T*>(cacheItem);
    }
};

我的一般概念在这里合理吗?有没有更好的方法来做到这一点?

4

3 回答 3

3

但首先,请阅读Andrei Alexandrescu的这篇文章,了解他认为他应该在那一章中写的内容——一种使用堆层构建堆的方法(真的是你的)。我使用堆层来构建HoardDieHardDieHarder,以及在我们的 OOPLSA 2002 论文Reconsidering Custom Memory Allocation中使用的自定义分配器,在开始创建自定义分配器之前您也应该阅读这篇文章。

于 2011-01-19T00:09:51.040 回答
1

查看 Loki 小对象分配器。

没有产生任何直接以人为本的文档的快速谷歌搜索。有 DOxygen 生成的文档,但不是特别容易理解。然而,设计和实现记录在 Andrei Alexandrescu 的“Modern C++ Design”中。

如果您只想对给定类的对象进行有效回收,请考虑一个简单的空闲列表——可能是原始存储块的空闲列表。

干杯&hth.,

于 2011-01-18T23:57:52.437 回答
0

我看到的关键问题是返回一个

自动指针

用于以非默认方式分配的内存。您可以通过定义一个合适的重载删除来解决这个问题,但最好将您自己的销毁函数定义为工厂的一部分。如果这样做,您还将本地化 Cache 类中的内存管理,从而使您可以更自由地提高该类的本地性能。当然,使用智能指针来控制内存管理是个好主意;您需要做的是定义自己的分配器并定义一个 smart_ptr 来使用它。

作为参考,管理自定义分配的另一种方法是定义自定义 new 运算符。即这种事情:

struct Cache
{
    void* allocate(size_t size)
    {
        size_t blockSize = sizeof(size_t) + size;
        // Placeholder: do what ever appropriate to blocks of size 'blockSize'
        return malloc(blockSize);
    }
    void destroy(void* p)
    {
        size_t* block = reinterpret_cast<size_t*>(p);
        size_t blockSize = *block;
        // Placeholder: do what ever appropriate to blocks of size 'blockSize'
        free(p);
    }

};
Cache cache;


void* operator new (size_t size, Cache& cache )
{
    return cache.allocate(size);
}

struct CacheObject 
{
    void operator delete(void* p)
    {
        cache.destroy(p);
    }
};


CacheObject* co = new (cache) CacheObject;
于 2011-01-19T00:09:20.577 回答