先说下关于讨论这个问题的起因,是因为ARK框架中都是基于Plugin和Module的,所以所有跟业务相关的内容都是会继承自AFIModule/AFIPlugin,而这两个类中都会有一个Update函数,用来做必要时候的循环。因为原来的习惯不好,导致大家都习惯于每个Module中都会加一个空的Update函数。

因为有同学猜想这种会影响部分性能,所以我个人实测了下这种继承的虚函数执行的时间,

测试环境: Windows10 x64 + VS2017 + i7-6700 @ 3.4GHz + 16GB

编译模式: Release

测试代码如下:

class base
{
public:
    virtual int func() = 0;

    virtual bool update()
    {
        return true;
    }
};

class base_a : public base
{
public:
    virtual int func()
    {
        return 1;
    }

    virtual bool update()
    {
        return true;
    }
};

class base_b : public base
{
public:
    virtual int func()
    {
        return 2;
    }

    virtual bool update()
    {
        return true;
    }
};

class base_c : public base
{
public:
    virtual int func()
    {
        return 2;
    }

    virtual bool update()
    {
        return true;
    }
};

int64_t get_time()
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

int main()
{
    base* a = new base_a();
    base* b = new base_b();
    base* c = new base_c();

    int count = 10000000;
    std::cout << "Test 1 empty function run " << count << " times" << std::endl;

    int64_t start = get_time();
    std::cout << "Start: " << start << std::endl;
    for(int i = 0; i < 10000000; ++i)
    {
        a->update();
    }
    int64_t end = get_time();
    std::cout << "End: " << end << std::endl;
    std::cout << "elapsed: " << end - start << " ms" << std::endl;

    std::cout << "\n\nTest 9 empty function run "<< count << " times" << std::endl;
    start = get_time();
    std::cout << "Start: " << start << std::endl;
    for(int i = 0; i < 10000000; ++i)
    {
        a->update();
        b->update();
        c->update();
    }

    end = get_time();
    std::cout << "End: " << end << std::endl;
    std::cout << "elapsed: " << end - start << " ms" << std::endl;

    return 0;
}

测试结果如下:

结果

测试的结果1个虚函数和3个虚函数循环1000W次,差不多是3倍。虽然从图上看起来也就不到50ms,但是如果有很多个模块的update函数下,是会拖慢一定性能的。
于是才有了和群内同学飞翔讨论的结果,讨论的比较天马行空,并且很多同学没有参与进来QQ群组电话,本文来总结下讨论结果。

我想的是如何把update减少空转,修改方法为通过注册的方式把Update函数在Module构造的时候注册进去,这样外面的大循环只会run需要的update函数列表。
飞翔的意思是作为框架层面的开发,框架考虑的是所有update平等,而不用限制开发同学的使用和习惯。通过策略来限制update的调用,讨论了一种可以动态均衡负载并且可人工参与的柔性策略。

关于上述的柔性策略,详见飞翔实现的定时器:https://github.com/freeeyes/TimerDispatch

而Ark因为不涉及多线程的业务运行,所以采用了update和基类update比较函数地址的方式来处理,代码如下:

    template<typename classBaseName, typename className>
    void  RegisterModule()
    {
        assert((std::is_base_of<AFIModule, classBaseName>::value));
        assert((std::is_base_of<classBaseName, className>::value));
        AFIModule* pRegModuleName = ARK_NEW className(pPluginManager);
        pRegModuleName->strName = typeid(classBaseName).name();
        pPluginManager->AddModule(typeid(classBaseName).name(), pRegModuleName);
        mxModules.AddElement(typeid(classBaseName).name(), pRegModuleName);
#if ARK_PLATFORM == PLATFORM_WIN
        if ((&className::Update != &AFIModule::Update))
#else
        AFIModule base;
        bool (AFIModule::*mfp)() = &AFIModule::Update;
        bool (className::*child_mfp)() = &className::Update;
        void* base_update_mfp = (void*)(base.*mfp);
        void* derived_update_mfp = (void*)(static_cast<className*>(pRegModuleName)->*child_mfp);
        if (base_update_mfp == derived_update_mfp)
#endif
        {
            mxModulesUpdates.AddElement(typeid(classBaseName).name(), pRegModuleName);
        }
    }

通过函数地址的比较,则可以实现自主实现update函数的情况下,自动注册进插件内的update函数列表里。

3 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

67 − = 63

This site uses Akismet to reduce spam. Learn how your comment data is processed.