【问题标题】:A pooled async object for better performance一个池化的异步对象以获得更好的性能
【发布时间】:2017-06-09 13:09:47
【问题描述】:

我阅读了一些关于 std::async 的内容,但遗憾的是,我对迄今为止所学和尝试的内容感到有些失望。

现在我“实现”了一个带有后台线程池的小型 std::async 替换器,以尝试我在空闲时间正在开发的游戏。异步替换器既不美观,也不优雅或优化,但如果您将它与 std::launch::async 标志一起使用,它仍然优于 std::async。

代码非常少:

namespace util
{
    template <class R, class T> std::future<R> async(const T& task)
    {
        std::shared_ptr<std::promise<R> > result = std::make_shared<std::promise<R> >();

        //threadctrl::run is a call which runs tasks on an engine-global thread pool, which get's injected on initialization
        threadctrl::run(std::bind([](std::shared_ptr<std::promise<R> > result, const T& task)->void
        {
            try
            {
                result->set_value(task());
            }
            catch (...)
            {
                //not handled here
            }

        }, result, std::ref(task)));

        return result->get_future();
    }
}

一个简单的用法示例如下所示:

int main()
{
    std::future<int> result = util::async<int>(std::bind(give5));

    //Do some other work

    std::cout << result.get();

    return 0;
}

我的问题是:是否有充分的理由在游戏环境中更喜欢原始的 std::async 实现而不是这种方法,或者这样就可以了?

我的最小实现的性能要好得多,我只使用/需要这个非常苗条的接口,因为我不会使用延迟异步,所以对我来说非常方便。

在带有 i5 4570 和 4gb 内存的 windows 7 32 位下,我得到以下结果:

每个 util::async 任务 0.001 毫秒(仅限调度)

每个 std::async 任务 0.006 毫秒(仅限调度)

【问题讨论】:

  • 如果你是基于windows的,你知道并发运行时和并行模式库吗?它们提供了该领域 C++ 标准库功能的超集。
  • 此外,像这样的微基准数据可能会引起很多怀疑,尤其是在您没有提供用于生成它们的代码的情况下。
  • 嗯,我用不同的迷你游戏在游戏过程中调整了日程安排时间。谢谢,我去查图书馆,没听说过... :)
  • 但是如果你好奇的话,我尝试了异步计算空间入侵者(比如我正在编码的游戏和侧卷轴射击游戏)中的物理事件(不是因为我认为我会获得很大的提升来自并行性,但为了玩弄它)。这里描述了基本的尝试:mango2go.wordpress.com/2017/05/06/a-simple-collision-approach,只是每个产生的碰撞事件都会启动一个异步任务来计算物理。我添加了一个计数器,并将我在某些游戏过程中通过计数器收集的异步启动所花费的总时间除以。

标签: c++ multithreading c++11 asynchronous


【解决方案1】:

std::async 被禁止使用踏板池;它必须将启动的任务“视为”它们在单独的线程中运行。因此线程本地存储被清除,其中 100 个正在运行或被阻塞的线程如果不竞争,则不应阻塞新的线程,等等。

您的游泳池不必做这些事情,因此可以更有效率。

请注意,在 Windows 上,std::async 实际上违反了这些假设。我已经阅读了 2017 年修复此问题的计划,但 GM 没有修复此问题。

您发布的代码很糟糕,因为它有一个悬空引用,因此您可能不应该为此目的使用您自己的手写代码。编写线程代码很难。你会弄错很多;如果你在一个简短的 sn-p 中出现了这么大的错误,想想你的线程池实现中有多少错误?

我使用的是thisthread_pool,它不需要全局线程池。当我有一个可以使用大量并行性的任务时,我会给它一个线程池,并在其上运行任务。

这确实意味着,如果同时运行两个需要大量并行性的不同任务,它们就会竞争。但我喜欢周围没有单身人士。您可以改为公开一个单例线程池。

请注意,thread_pool 中的任务需要避免阻塞。为此,您可能希望为您的 thread_pool 任务提供获得延续的能力,或者为此类操作提供使用多余线程访问“高阻塞预期”池的能力。

【讨论】:

  • 好的,首先,谢谢我不知道异步规则会阻止它使用线程池,但我知道我应该避免阻塞任务。全局线程池是引擎全局的,这意味着它存储在引擎中,为了简单起见,它可以从任何软件访问。在我看来,为异步创建一个单独的池不是选项,因为我希望在代码中的一个特定点处理线程。我很感兴趣我的代码有什么特别不好的地方,因为我想改进它(好吧,悬空的参考,但还有更多吗?)
  • @mango 那将是代码审查。但是您的代码不必要地在堆上分配了一个承诺,可能是因为您的运行程序需要可复制的对象。绑定是个坏主意,使用 lambdas 或知道它们只运行一次的函数对象。
  • en.cppreference.com/w/cpp/thread/async: “模板函数 async 异步运行函数 f(可能在一个单独的线程中,它可能是线程池的一部分)......” 没有禁止在线程池上运行根据这个。
  • @0kcats 是露骨的而不是规范的。
  • @Yakk-AdamNevraumont 谢谢,我现在看得更清楚了。
猜你喜欢
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多