我一直在研究在嵌入式Linux(ARM)上运行的C ++服务器应用程序的性能。服务器主处理循环的伪代码是 -
for i = 1 to 1000
Process item i
Sleep for 20 ms
一个项目的处理大约需要2ms。 "睡眠"这里真的是要求Poco图书馆做一个" tryWait"在一个事件上。如果事件被触发(它从未在我的测试中)或时间到期,它将返回。我不知道这相当于什么系统调用。虽然我们要求一个2ms的块,但结果大约是20ms。我可以忍受 - 那不是问题。睡眠只是一种人为的延迟,因此过程中的其他线程不会被饿死。
循环大约需要24秒才能完成1000项。
问题是,我们改变了睡眠的使用方式,以便我们有更多的控制权。我的意思是 - 2ms处理延迟20ms并不能让我们做很多处理。将此新参数设置为某个值后,它会执行类似的操作 -
For i = 1 to 1000
Process item i
if i % 50 == 0 then sleep for 1000ms
这是粗略的代码,实际上睡眠的数量略有不同,它恰好以24秒的周期运行以完成所有项目 - 就像之前一样。
因此,我们在相同的时间内完成相同数量的处理。
问题1 - 报告原始代码的CPU使用率约为1%(它略有不同但是平均值)并且报告的新代码的CPU使用率为约5%。我认为它们应该是一样的。
也许这个CPU报告并不准确,所以我想我会同时对一个大文本文件进行排序,看看我们的服务器放慢了多少。这是一个CPU绑定的进程(根据顶部的CPU使用率为98%)。结果很奇怪。使用旧代码,当我们的服务器运行时,对文件进行排序所需的时间会增加21%。
问题2 - 如果服务器只使用1%的CPU,那么排序所用的时间几乎不一样吗?
此外,完成所有项目所需的时间不会改变 - 无论是否运行排序,它仍然是24秒。
然后我尝试了新代码,它只减慢了大约12%的排序速度,但现在需要花费大约40%才能完成它必须处理的所有项目。
问题3 - 为什么引入人为延迟的两种方式会导致不同的结果。似乎服务器更频繁地睡眠但是时间最短的服务器正在获得更多的优先权。
我在最后一个理论上有一个半理论 - 无论用什么系统调用来进行"睡眠"在时间结束时切换回服务器进程。这使得该过程在时间片上定期进行另一次咬合。
任何帮助表示赞赏。我怀疑我只是没有正确地理解它并且事情比我想象的要复杂得多。如果需要,我可以提供更多细节。
感谢。
更新:用usleep(2000)替换了tryWait(2) - 没有变化。实际上,sched_yield()也是如此。
答案 0 :(得分:0)
我至少可以回答问题1和问题2(因为它们是同一个问题)。
在尝试实际服务器代码中的各种选项后,我们得出结论,来自操作系统的CPU报告不正确。这是非常好的结果,所以为了确保,我写了一个独立的程序,它不使用Poco或我们的任何代码。只是简单的Linux系统调用和标准C ++功能。它实现了上面的伪代码。处理被紧密循环替换,仅检查经过的时间以查看2ms是否已启动。睡觉是适当的睡眠。
小型测试程序显示完全相同的问题。即,执行相同数量的处理但是分割调用休眠函数的方式,会产生非常不同的CPU使用结果。在测试程序的情况下,使用1000个20ms睡眠时报告的CPU使用率为0.0078秒,而当使用较少频率的1000ms睡眠时报告的CPU使用率为1.96875。完成的处理量是相同的。
在Linux PC上运行测试没有显示问题。两种睡眠方式都产生了完全相同的CPU使用率。
我们的嵌入式系统显然存在一个问题,以及当进程频繁出现时测量CPU时间的方式(sched_yeild而不是睡眠会出现同样的问题)。
更新:这是代码。 RunLoop是主要位的完成位置 -
int sleepCount;
double getCPUTime( )
{
clockid_t id = CLOCK_PROCESS_CPUTIME_ID;
struct timespec ts;
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
return -1;
}
double GetElapsedMilliseconds(const timeval& startTime)
{
timeval endTime;
gettimeofday(&endTime, NULL);
double elapsedTime = (endTime.tv_sec - startTime.tv_sec) * 1000.0; // sec to ms
elapsedTime += (endTime.tv_usec - startTime.tv_usec) / 1000.0; // us to ms
return elapsedTime;
}
void SleepMilliseconds(int milliseconds)
{
timeval startTime;
gettimeofday(&startTime, NULL);
usleep(milliseconds * 1000);
double elapsedMilliseconds = GetElapsedMilliseconds(startTime);
if (elapsedMilliseconds > milliseconds + 0.3)
std::cout << "Sleep took longer than it should " << elapsedMilliseconds;
sleepCount++;
}
void DoSomeProcessingForAnItem()
{
timeval startTime;
gettimeofday(&startTime, NULL);
double processingTimeMilliseconds = 2.0;
double elapsedMilliseconds;
do
{
elapsedMilliseconds = GetElapsedMilliseconds(startTime);
} while (elapsedMilliseconds <= processingTimeMilliseconds);
if (elapsedMilliseconds > processingTimeMilliseconds + 0.1)
std::cout << "Processing took longer than it should " << elapsedMilliseconds;
}
void RunLoop(bool longSleep)
{
int numberOfItems = 1000;
timeval startTime;
gettimeofday(&startTime, NULL);
timeval startMainLoopTime;
gettimeofday(&startMainLoopTime, NULL);
for (int i = 0; i < numberOfItems; i++)
{
DoSomeProcessingForAnItem();
double elapsedMilliseconds = GetElapsedMilliseconds(startTime);
if (elapsedMilliseconds > 100)
{
std::cout << "Item count = " << i << "\n";
if (longSleep)
{
SleepMilliseconds(1000);
}
gettimeofday(&startTime, NULL);
}
if (longSleep == false)
{
// Does 1000 * 20 ms sleeps.
SleepMilliseconds(20);
}
}
double elapsedMilliseconds = GetElapsedMilliseconds(startMainLoopTime);
std::cout << "Main loop took " << elapsedMilliseconds / 1000 <<" seconds\n";
}
void DoTest(bool longSleep)
{
timeval startTime;
gettimeofday(&startTime, NULL);
double startCPUtime = getCPUTime();
sleepCount = 0;
int runLoopCount = 1;
for (int i = 0; i < runLoopCount; i++)
{
RunLoop(longSleep);
std::cout << "**** Done one loop of processing ****\n";
}
double endCPUtime = getCPUTime();
std::cout << "Elapsed time is " <<GetElapsedMilliseconds(startTime) / 1000 << " seconds\n";
std::cout << "CPU time used is " << endCPUtime - startCPUtime << " seconds\n";
std::cout << "Sleep count " << sleepCount << "\n";
}
void testLong()
{
std::cout << "Running testLong\n";
DoTest(true);
}
void testShort()
{
std::cout << "Running testShort\n";
DoTest(false);
}