我们首次在带有>的机器上测试我们的软件。 12个核心用于扩展,我们在添加第12个线程后遇到了令人讨厌的性能下降。在花了几天时间之后,我们对接下来要尝试的内容感到难过。
测试系统是双Opteron 6174(2x12内核),内存为16 GB,Windows Server 2008 R2。
基本上,性能从10到12个线程达到峰值,然后从悬崖上掉下来,很快就会以大约4个线程的速度执行工作。下降相当陡峭,并且通过16到20个线程,它在吞吐量方面达到了底部。我们已经使用运行多个线程的单个进程和运行单个线程的多个进程测试了两者 - 结果几乎相同。处理相当占用内存,并且需要大量磁盘。
我们相当确定这是一个内存瓶颈,但我们不相信这是一个缓存问题。证据如下:
我们认为这可能是this paper中描述的问题的证据,但是我们发现将每个工作线程/进程固定到特定核心并没有改善结果(如果有的话,性能得到了一点点差)。
这就是我们所处的位置。关于这个瓶颈的确切原因或我们如何避免它的任何想法?
答案 0 :(得分:2)
我不确定我是否完全理解这些问题,以便我可以为您提供解决方案,但根据您的解释,我可能会有一些可能有帮助的替代观点。
我在C中编程,因此对我有用的内容可能不适用于您的情况。
你的处理器有12MB的L3和6MB的L2很大,但在我看来它们很少够大!
您可能正在使用rdtsc对各个部分进行计时。当我使用它时,我有一个统计结构,我从执行代码的不同部分发送测量结果。观察的平均值,最小值,最大值和数量是显而易见的,但标准偏差的位置也可以帮助您确定是否应该研究大的最大值。只需要在需要读出时计算标准偏差:在此之前它可以存储在其组件中(n,和x,和x ^ 2)。除非您计时非常短的序列,否则可以省略前面的同步指令。确保量化时间开销,只要能够将其排除为无关紧要。
当我编写多线程时,我尝试将每个核心/线程的任务尽可能地设置为“内存有限”。通过内存限制,我的意思是不做需要不必要的内存访问的事情。不必要的内存访问通常意味着尽可能多的内联代码和尽可能多的操作系统访问。对我而言,操作系统在调用它的内存工作量方面是一个很大的未知数,因此我尝试将调用保持在最低限度。以同样的方式,但通常影响程度较小,我试图避免调用应用程序功能:如果必须调用它们,我宁愿他们不会调用很多其他东西。
以同样的方式我最小化内存分配:如果我需要几个,我将它们一起添加到一个然后将一个大的分配细分为更小的分配。这将有助于以后的分配,因为在找到返回的块之前,它们需要循环通过更少的块。我只在绝对必要时阻止初始化。
我还尝试通过内联来减少代码大小。当移动/设置小块内存时,我更喜欢使用基于rep movsb和rep stosb的内在函数,而不是调用memcopy / memset,这些内存通常都是针对较大的块进行优化而不是特别限制。
我最近才开始使用自旋锁,但我实现它们使它们成为内联(任何东西都比调用操作系统更好!)。我认为操作系统替代方案是关键部分,虽然它们是快速本地自旋锁更快。由于它们执行额外的处理,因此意味着它们阻止在此期间执行应用程序处理。这是实施:
inline void spinlock_init (SPINLOCK *slp)
{
slp->lock_part=0;
}
inline char spinlock_failed (SPINLOCK *slp)
{
return (char) __xchg (&slp->lock_part,1);
}
或更详细(但不过分):
inline char spinlock_failed (SPINLOCK *slp)
{
if (__xchg (&slp->lock_part,1)==1) return 1;
slp->count_part=1;
return 0;
}
并发布
inline void spinlock_leave (SPINLOCK *slp)
{
slp->lock_part=0;
}
或者
inline void spinlock_leave (SPINLOCK *slp)
{
if (slp->count_part==0) __breakpoint ();
if (--slp->count_part==0) slp->lock_part=0;
}
计数部分是我从嵌入式(和其他编程)带来的东西,用于处理嵌套中断。
我也是IOCP的忠实粉丝,因为他们在处理IO事件和线程方面的效率,但您的描述并未表明您的应用程序是否可以使用它们。在任何情况下,你似乎都在节约它们,这很好。
答案 1 :(得分:1)
要解决您的要点:
1)如果您有12个内核,100%使用率和12个内核空闲,那么您的总CPU使用率将为50%。如果您的同步是自旋锁,那么即使没有完成有用的工作,您的线程仍然会使CPU饱和。
2)跳过
3)我同意你的结论。在将来,您应该知道Perfmon有一个计数器:Process \ Page Faults / sec,可以验证这一点。
4)如果您没有ntoskrnl的私有符号,CodeAnalyst可能无法在其配置文件中告诉您正确的函数名称。相反,它只能指向它具有符号的最近函数。您可以使用CodeAnalyst获取配置文件的堆栈跟踪吗?这可以帮助您确定线程执行哪些操作来驱动内核使用。
此外,我在Microsoft的前团队提供了许多性能分析工具和指南here,包括在CPU配置文件上获取堆栈跟踪。