防止c++中的单例缓存丢失

Preventing Singleton Cache Miss in C++

本文关键字:缓存 单例 c++ 防止      更新时间:2023-10-16

是否有办法防止使用单例对象时缓存丢失?下面是我当前的单例实现:

SingletonObject.h

#pragma once
class SingletonObject
{
public:
    static SingletonObject* SingletonObject();
    static void SingletonObject();
private:
    static SingletonObject* sSingletonObject;
    SingletonObject();
    ~SingletonObject();
};

SingletonObject.cpp

#include "SingletonObject.h"
SingletonObject* SingletonObject::sSingletonObject = NULL;
SingletonObject:: SingletonObject()
{
}
SingletonObject::~ SingletonObject()
{
}
SingletonObject* SingletonObject::GetSingleton()
{
    if (sSingletonObject == NULL) // cache miss
    {
        sSingletonObject = new SingletonObject();
    }
    return sSingletonObject;  
}
void SingletonObject::DestroySingleton()
{
    delete sSingletonObject; 
    sSingletonObject = NULL;
}

是否有更好的方法来防止缓存丢失?这是不使用单例模式的另一个原因吗?


Update:事实证明,它实际上与缓存无关,而是为堆栈展开和GetSingleton()调用中的条件检查生成的代码。通过显式地创建和销毁单例(而不是按需创建),并为静态实例创建一个访问器,我能够避免大量的开销,并注意到分析中的显著加速。

SingletonObject.h

#pragma once
class SingletonObject {
public:
    static void CreateSingleton();
    static void DestroySingleton();
    static inline SingletonObject* GetSingleton() { return sInstance; }
private:
    static SingletonObject* sInstance;
    SingletonObject();
}

SingletonObject.cpp

#include "SingletonObject.h"
void SingletonObject::CreateSingleton() {
    if (sInstance == NULL)
        sInstance = new SingletonObject();`
}
void SingletonObject::DestroySingleton() {
    delete(sInstance);
    sInstance = NULL;
}

这是人们在性能优化道路上遇到的一个非常具体的问题。你确定一路都到了吗?我问的原因是,如果你访问你的单例足够频繁,对象的指针将留在缓存中。如果它不在缓存中,那么您访问对象的频率就不够高,这意味着您并不真正需要它,因此,将指针(或对象)预取到缓存中只会从您在现实中更经常使用的对象中窃取宝贵的缓存空间——从长远来看,这甚至可能会损害性能。根据我的理解,要解决您目前遇到的问题,您必须经过以下步骤:

  1. 配置你的应用程序,找出static SingletonObject* SingletonObject();函数真的是一个热点(>10%的总时间花在执行这个函数上)
  2. 使用基于事件的采样收集器(如Intel VTune)对应用程序进行配置,以找出缓存丢失对该函数的执行时间负责。这是可以避免的。它可能只是你对函数的调用次数(做一个调用计数)。
  3. 并指出,指针不在缓存(哪缓存的方式?L1, L2还是LLC?L1和L2非常小,访问L2的延迟约为10个周期,因此L1丢失不是一个大问题),您需要检查代码以找出原因。这意味着您将查看在调用static SingletonObject* SingletonObject();之间访问的数据量,并检查是否所有这些访问都是必要的。如果是,那么这就是合理的缓存丢失,您对此无能为力。如果不是,那么尽可能减少你的工作集,然后重新运行分析器(步骤2)。
  4. 只有当你完成了1-3,你仍然看到访问Singleton对象的缓存丢失,你看到这会损害性能,只有那时你把_mm_prefetch()调用在你的代码之前访问Singleton对象。
  5. 然后再次执行1-3(至少是第1步),以确保第4步提高了性能,而不是损害它,这可能会污染您选择的缓存级别。

不,在你的整个程序中,如果没有更多的知识,就不会有一个对单例指针的引用,然后它可以使用该指针和它将要引用的对象来启动L1/L2缓存。

这种技术叫做预取。


cf: http://portal.acm.org/citation.cfm?id=279529