在这里,我将重点关注自我降级的自定义应用程序(无需对线程的快速性进行一般性讨论)。
我在Java上有MPI应用程序,它使用迭代方法解决了一些问题。应用程序的示意图如下所示,我们称之为MyProcess(n),其中“n”是进程数:
double[] myArray = new double[M*K];
for(int iter = 0;iter<iterationCount;++iter)
{
//some communication between processes
//main loop
for(M)
for(K)
{
//linear sequence of arithmetical instructions
}
//some communication between processes
}
为了提高性能,我决定使用Java线程(我们称之为MyThreads(n))。代码几乎相同 - myArray变成矩阵,其中每行包含适当线程的数组。
double[][] myArray = new double[threadNumber][M*K];
public void run()
{
for(int iter = 0;iter<iterationCount;++iter)
{
//some synchronization primitives
//main loop
for(M)
for(K)
{
//linear sequence of arithmetical instructions
counter++;
}
// some synchronization primitives
}
}
使用Executors.newFixedThreadPool(threadNumber)创建和启动的线程。
问题在于,对于MyProcess(n),我们获得了足够的性能(在[1,8]中为n),如果MyThreads(n)性能本质上下降(在我的系统上因子为n)。
硬件:英特尔(R)Xeon(R)CPU X5355(2个处理器,每个4核)
Java版本:1.5(使用d32选项)。
起初我认为在线程上有不同的工作负载,但是,变量“计数器”显示,不同运行的MyThreads(n)之间的迭代次数([1,8]中的n)是相同的。
并且它不是同步错误,因为我有临时注释所有同步原语。
任何建议/想法将不胜感激。
感谢。
答案 0 :(得分:0)
我在你的代码中看到了两个问题。
首先是缓存问题。既然你试图在多线程/进程中这样做,我会假设你的M * K结果很多;然后当你做
double[][] myArray = new double[threadNumber][M*K];
你实际上是在创建一个大小为threadNumber的双指针数组;每个都指向一个大小为M * K的双数组。这里有趣的一点是,数组的threadNumber数不一定分配到同一块内存中。它们只是双指针,可以在JVM堆中的任何位置分配。因此,当多个线程运行时,您可能会遇到大量缓存未命中,并且最终会多次读取内存,最终会降低程序速度。
如果以上是根本原因,您可以尝试扩大JVM堆大小,然后执行
double[] myArray = new double[threadNumber * M * K];
让线程在同一个数组的不同段上运行。你应该能够更好地看到性能。
其次是同步问题。请注意,double(或任何原始)数组不是volatile。因此,不保证您在1个线程上的结果对其他线程可见。如果您使用同步块,则可以解决此问题,因为同步的副作用是确保跨线程的可见性;如果没有,当您正在读取和写入数组时,请始终确保使用Unsafe.putXXXVolatile()和Unsafe.getXXXVolatile(),以便您可以对数组执行volatile操作。
为了更进一步,还可以使用Unsafe创建连续的内存段,您可以使用它来保存数据结构并获得更好的性能。在你的情况下,我认为1)已经做到了。