我正在编写一个在Nehalem处理器上运行的多线程java应用程序。但是我有一个问题,从4个线程开始,我几乎看不到我的应用程序中的加速。
我做了一些简单的测试。我创建了一个线程,它只分配一个大数组并访问数组中的随机条目。因此,当我运行多个线程时,运行时间不应该改变(假设我没有超过可用CPU核心数)。但我观察到的是,运行1或2个线程的时间几乎相同,但运行4或8个线程的速度要慢得多。因此,在尝试解决我的应用程序中的算法和同步问题之前,我想找出可以实现的最大可能的并行化。
我使用了-XX:+UseNUMA
JVM选项,因此数组应该在相应线程附近的内存中分配。
P.S。如果线程正在进行简单的数学计算,那么4个甚至8个线程都没有时间丢失,所以我得出结论,当线程访问内存时我遇到了一些问题。
感谢任何帮助或想法,谢谢。
修改的
谢谢大家的回复。我看到我没有足够好地解释自己。
在尝试消除我的应用程序中的同步问题之前,我做了一个简单的测试,检查可以实现的最佳并行化。代码如下:
public class TestMultiThreadingArrayAccess {
private final static int arrSize = 40000000;
private class SimpleLoop extends Thread {
public void run() {
int array[] = new int[arrSize];
for (long i = 0; i < arrSize * 10; i++) {
array[(int) ((i * i) % arrSize)]++; // randomize a bit the access to the array
}
long sum = 0;
for (int i = 0; i < arrSize; i++)
sum += array[i];
}
}
public static void main(String[] args) {
TestMultiThreadingArrayAccess test = new TestMultiThreadingArrayAccess();
for (int threadsNumber : new int[] { 1, 2, 4, 8 }) {
Statistics timer = new Statistics("Executing " + threadsNumber+ " threads"); // Statistics is a simple helper class that measures the times
timer.start();
test.doTest(threadsNumber);
timer.stop();
System.out.println(timer.toString());
}
}
public void doTest(int threadsNumber) {
Thread threads[] = new Thread[threadsNumber];
for (int i = 0; i < threads.length; i++) {
threads[i] = new SimpleLoop();
threads[i].start();
}
for (int i = 0; i < threads.length; i++)
try {
threads[i].join();
} catch (InterruptedException e) {
};
}
}
因此,您可以看到此minitest中根本没有同步,并且数组的分配也在线程内部,因此它应该放在可以快速访问的内存块中。此代码中也没有内存争用。仍有4个线程的运行时间下降了30%,8个线程运行速度慢了两倍。当你从代码中我只是等到所有线程完成他们的工作,并且因为他们的工作是独立的线程数不应该影响执行所花费的总时间。
在机器上安装了2个四核超线程Nehalem处理器(共16个CPU),所以有8个线程,每个都可以专门捕获它的CPU。
当我尝试用较小的数组(20K条目)运行此测试时,4个线程的执行时间的下降为7%,8个线程 - 14%,这是令人满意的。但是当我尝试在大型阵列(40M条目)上随机访问时,运行时间会急剧增加,所以我认为存在大块内存(因为它们不适合高速缓存存储器?)的问题是在非 - 有效的方式。
有什么想法可以解决这个问题吗?
希望这能更好地澄清问题,再次感谢。
答案 0 :(得分:2)
测试中的瓶颈是cpu到内存带宽。即使本地内存可用,它也会被一些线程共享。 (内存是节点的本地存储器,而不是特定内核。)一旦CPU可以轻松超过上述测试之类的简单循环的可用带宽,因此在这种测试中增加线程不会提高性能,并且会恶化性能由于缓存一致性恶化。
只是一个完整性测试,您是否也使用并行收集器? -XX:+UseParallelGC
。 UseNUMA只在那时生效。
答案 1 :(得分:1)
不知道你究竟在做什么以及你试图解决的问题是什么。看起来你的代码周围有很多同步,因为它可能是不够可扩展的主要原因。一旦它使您的应用程序几乎连续,过度同步会导致任何加速减慢。所以我给你的建议是检查你的实现并尝试解决这个问题。
ADD。
在您添加了正在执行的操作的实现之后。性能降级可以通过大量大容量内存访问来解释。一旦你运行的所有线程你,他们需要的不是缓存数据访问内存控制器,因为它们在不同的CPU的运行,内存控制器防止CPU的同时从这样做,这意味着在每个高速缓存未命中的硬件水平同步。在你的情况下,它几乎相同,就像你运行10个不同的独立程序。我想如果你将推出10(您可以通过任何大量取代10)复制了你的网页浏览器,例如,你会看到同样的效果,但是这并不意味着浏览器的实现是无效的,你只是在创造了巨大的负担电脑记忆。
答案 2 :(得分:0)
正如Artem所说,你可能有不必要的同步。但我首先要确定事实。你描述的应用程序真的运行得慢吗?
以下是有关该主题的深刻文章:http://codeidol.com/java/java-concurrency/Testing-Concurrent-Programs/Avoiding-Performance-Testing-Pitfalls/
编写有用的微基准测试实际上非常困难,尤其是在处理并发代码时。例如,您可以使用“死代码消除”,其中编译器优化您认为正在执行的代码。垃圾收集运行时也很难猜到。 Hotspot的运行时优化也使测量更加困难。如果是线程,则需要考虑用于创建它们的时间。因此,您可能需要使用“CyclicBarrier”等来进行精确测量。这样的事情..
话虽如此,如果您正在阅读的话,我发现您在访问内存时会遇到问题。如果您可以发布代码,我们可以帮助您更好...
答案 3 :(得分:0)
有两个明显的潜在问题浮现在脑海中。
答案 4 :(得分:0)
除了并发问题之外,最可能的原因是内存缓存争用。
如果所有线程都在访问同一个存储区,那么当您想要访问它时,很可能是其他处理器内存缓存。
如果存储是“只读”,您可以为每个线程提供自己的副本,这将允许JVM&amp;处理器以优化内存容量。
答案 5 :(得分:0)
我根据我发布的文章中的建议修改了你的测试。在我的2核机器上(这就是我现在所有的)结果似乎是合理的(注意我为每个线程号运行了2次测试):
也许你可以试试这个? (请注意,我必须略微修改您的测试(请参阅注释),因为在我糟糕的硬件上运行需要很长时间)
另请注意,我使用-server
选项运行此测试。
Test with threadNum 1 took 2095717473 ns
Test with threadNum 1 took 2121744523 ns
Test with threadNum 2 took 2489853040 ns
Test with threadNum 2 took 2465152974 ns
Test with threadNum 4 took 5044335803 ns
Test with threadNum 4 took 5041235688 ns
Test with threadNum 8 took 10279012556 ns
Test with threadNum 8 took 10347970483 ns
代码:
import java.util.concurrent.*;
public class Test{
private final static int arrSize = 20000000;
public static void main(String[] args) throws Exception {
int[] nums = {1,1,2,2,4,4,8,8};//allow hotspot optimization
for (int threadNum : nums) {
final CyclicBarrier gate = new CyclicBarrier(threadNum+1);
final CountDownLatch latch = new CountDownLatch(threadNum);
ExecutorService exec = Executors.newFixedThreadPool(threadNum);
for(int i=0; i<threadNum; i++){
Runnable test =
new Runnable(){
public void run() {
try{
gate.await();
}catch(Exception e){
throw new RuntimeException(e);
}
int array[] = new int[arrSize];
//arrSize * 10 took very long to run so made it
// just arrSize.
for (long i = 0; i < arrSize; i++) {
array[(int) ((i * i) % arrSize)]++;
}//for
long sum = 0;
for (int i = 0; i < arrSize; i++){
sum += array[i];
}
if(new Object().hashCode()==sum){
System.out.println("oh");
}//if
latch.countDown();
}//run
};//test
exec.execute(test);
}//for
gate.await();
long start = System.nanoTime();
latch.await();
long finish = System.nanoTime();
System.out.println("Test with threadNum " +
threadNum +" took " + (finish-start) + " ns ");
exec.shutdown();
exec.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS);
}//for
}//main
}//Test