如何正确实现异步计算的等待?

时间:2018-02-15 22:31:47

标签: performance theory

我有点麻烦,我要求提示。我在Windows平台上,以下列方式进行计算:

int input = 0;
int output; // junk bytes here
while(true) {
    async_enqueue_upload(input); // completes instantly, but transfer will take 10us
    async_enqueue_calculate(); // completes instantly, but computation will take 80us
    async_enqueue_download(output); // completes instantly, but transfer will take 10us
    sync_wait_finish(); // must wait while output is fully calculated, and there is no junk
    input = process(output); // i cannot launch next step without doing it on the host.
}

我问的是wait_finish()的事情。我必须等待所有设备完成,结合所有结果并以某种方式处理数据并上传新部分,这是基于先前的计算步骤。我需要在每个步骤之间同步数据,因此我无法并行化步骤。我知道,这不是一个非常有效的案例。所以我们继续提问。

我在wait_finish()中有两种检查完成的方法。首先是让线程进入睡眠状态,直到它被完成事件唤醒:

while( !is_completed() )
    Sleep(1);

它的性能非常低,因为实际计算需要100us,并且最小的Windows sheduler时间步长为1ms,因此它的性能降低了不到10倍。

第二种方法是在空无限循环中检查完成:

while( !is_completed() )
    {} // do_nothing();

它具有10倍的良好计算性能。但它也是不合适的解决方案,因为它使得完整的cpu核心利用率,绝对无用的工作。如何让cpu“睡觉”到我需要的时间? (每一步都有相同的工作量)

如果计算时间对于主动旋转等待来说太大,但与sheduler时间步长相比太小,这种情况通常会如何解决?还有相关的子问题 - 如何在linux上做到这一点?

1 个答案:

答案 0 :(得分:0)

幸运的是,我已经成功地找到了答案。简而言之 - 我应该使用linux。

我的调查显示以下内容。在Windows上,ntdll中有隐藏功能,NtDelayExecution()。它不是通过SDK公开的,但可以通过以下方式加载:

static int(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (int(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtDelayExecution");

它允许在100ns周期内设置睡眠间隔。然而,即使不能很好地运作,如以下基准所示:

SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); // requires Admin privellegies
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
uint64_t hpf = qpf(); // QueryPerformanceFrequency()
uint64_t s0 = qpc(); // QueryPerformanceCounter()
uint64_t n = 0;
while (1) {     
    sleep_precise(1); // NtDelayExecution(-1); waits one 100-nanosecond interval
    auto s1 = qpc();
    n++;
    auto passed = s1 - s0;
    if (passed >= hpf) {
        std::cout << "freq=" << (n * hpf / passed) << " hz\n";
        s0 = s1;
        n = 0;
    }
}

产生的东西低于2000赫兹的循环速率,结果因字符串而异。这导致我转向windows线程切换sheduler,这完全不适合实时任务。它的最小间隔为0.5ms(+开销)。顺便问一下,有谁知道如何调整这个值?

接下来是linux问题,它能做什么?所以我用buildroot的方法构建了自定义微内核4.14,并测试了那里的基准代码。我将qpc()替换为clock_gettime()数据,时间为CLOCK_MONOTONIC,而qpf()只返回秒内的纳秒数和sleep_precise()刚刚调用的clock_nanosleep() }。我未能找出CLOCK_MONOTONICCLOCK_REALTIME之间的区别。

我非常惊讶,开箱即可获得18.4khz的频率,这非常稳定。虽然我测试了几个间隔,但我发现我可以将环路设置为几乎任何频率高达18.4khz,但实际测量的等待时间结果也不同于我要求的1.6倍。例如,如果我要求睡100美元它实际上睡眠约160 us,给出~6.25 khz频率。系统上没有其他任何东西,只有内核,busybox和这个测试。我不是一个经验丰富的linux用户,我仍然想知道如何将其调整为更加实时和确定性。我可以将频率推得更高吗?