上下文切换通常发生在调用函数和执行函数之间吗?

Does context switching usually happen between calling a function, and executing it?

所以我一直在研究一个复杂应用程序(由数百名程序员编写)的源代码。除此之外,我还创建了一些时间检查函数,以及合适的数据结构来测量主循环不同部分的执行周期,运行 对这些测量结果进行了一些分析。

这是一个有助于解释的伪代码:

main()

{

TimeSlicingSystem::AddTimeSlice(0);

FunctionA();

TimeSlicingSystem::AddTimeSlice(3);

FuncitonB();

TimeSlicingSystem::AddTimeSlice(6);

PrintTimeSlicingValues();

}



void FunctionA()

{

TimeSlicingSystem::AddTimeSlice(1);

//...

TimeSlicingSystem::AddTimeSlice(2);

}



FuncitonB()

{

TimeSlicingSystem::AddTimeSlice(4);

//...

TimeSlicingSystem::AddTimeSlice(5);

}



PrintTimeSlicingValues()

{

//Prints the different between each slice, and the slice before it,

//starting from slice number 1.

}

大多数测量都非常合理,例如,为局部变量赋值的成本不到一微秒。大多数功能会在几微秒内从头到尾执行,很少会达到一毫秒。

然后我 运行 进行了半小时左右的一些测试,我发现了一些我不太理解的 st运行ge 结果。某些函数将被调用,并且在测量从调用函数的那一刻('calling' 代码中的最后一行)到 'called' 函数中的第一行将花费很长时间,最多一个 30 毫秒的周期。这是在一个循环中发生的,否则该循环将在不到 8 毫秒的时间内完成一个完整的迭代。

为了得到它的图片,在我包含的伪代码中,测量了切片编号 0 和切片编号 1 之间的时间段,或者切片编号 3 和切片编号 4 之间的时间。这就是我所指的那种时期。它是调用一个函数和 运行在被调用函数中的第一行之间的测量时间。

问题A。此行为可能是由于 OS 的线程或进程切换造成的吗?调用一个函数是一个独特的漏洞吗?我正在处理的 OS 是 Windows 10.

有趣的是,在 'calling' 代码问题中调用后函数中从来没有最后一行返回到第一行(从切片编号 2 到 3 或从 5 到 6 的句点伪代码)!并且所有测量值始终小于 5 微秒。

问题B。无论如何,这可能是由于我使用的时间测量方法造成的吗?由于时钟差异,不同内核之间的切换是否会暗示上下文切换比实际慢? (尽管到目前为止我还没有发现一个负增量时间,这似乎完全驳斥了这个假设)。同样,我正在处理的 OS 是 Windows 10.

我的时间测量函数看起来是这样的:

FORCEINLINE double Seconds()

{

Windows::LARGE_INTEGER Cycles;

Windows::QueryPerformanceCounter(&Cycles);

// add big number to make bugs apparent where return value is being passed to float

return Cycles.QuadPart * GetSecondsPerCycle() + 16777216.0;

}

QuestionA. Could this behavior be due to thread, or process switching by the OS?

是的。线程切换随时可能发生(例如,当设备发送 IRQ 导致另一个更高优先级的线程解除阻塞并立即抢占您的线程时),这 can/will 会导致您的线程出现意外的时间延迟。

Does calling a function is a uniquely vulnerable spot to that?

调用您自己的函数并没有什么特别之处使它们特别容易受到攻击。如果函数涉及内核的 API 线程切换的可能性更大,有些事情(例如调用“sleep()”)几乎可以保证导致线程切换。

还有与虚拟内存管理的潜在交互 - 通常情况下(例如您的可执行文件、您的代码、您的数据)使用“内存映射文件”,第一次访问它可能会导致 OS 获取来自磁盘的代码或数据(并且您的线程可以被阻塞,直到它想要的代码或数据从磁盘到达);很少使用的代码或数据也可以发送到 swap space 并需要获取。

QuestionB. Could this be, in any way, due to the time measurement method I am using?

在实践中,Windows' QueryPerformanceCounter() 很可能是用 RDTSC 指令实现的(假设 80x86 CPU/s)并且根本不涉及内核,对于现代硬件,这很可能是单原子的。理论上 Windows 可以模仿 RDTSC and/or 以另一种方式实现 QueryPerformanceCounter() 以防止安全问题(时序边信道),正如英特尔已经推荐了大约 30 年,但这不太可能(现代操作系统,包括但不限于 Windows,往往更关心性能而不是安全);理论上你的 hardware/CPU 可能太老了(大约 10 岁以上)以至于 Windows 必须以不同的方式实现 QueryPerformanceCounter(),或者你可以使用其他一些 CPU(例如 ARM 而不是 80x86)。

换句话说;您使用的时间测量方法不太可能(但并非不可能)导致任何计时问题。