我正在编写一个在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) {
            };
    }
}
. 因此,如您所看到的,在此MiniSt中没有同步,并且还有数组的分配在线内部,因此它应该将其放置在可以快速访问的内存的块中。此代码中也没有内存符号。仍有4个线程在运行时间下降30%,8个线程速度较慢。正如您来自代码,我只是等到所有线程完成他们的工作,因为他们的工作是独立的线程数不应该影响执行所需的总时间。

在机器上安装了2个四核超胸部的Nehalem处理器(共16个CPU),所以用8个线程每个人都可以专门捕获它的CPU。

当我尝试使用较小的阵列(20k条目)运行此测试时,4个线程的执行时间的跌落为7%和8个线程 - 14%,它令人满意。但是当我尝试在大阵列(40米条目)上访问的随机运行时急剧增加时,所以我认为有问题的内存大块(因为它们不适合缓存内存?)都是在非 - 效率。

是有什么想法如何解决这个问题?

希望这能以更好的方式澄清问题,再次感谢。

有帮助吗?

解决方案

测试中的瓶颈是对存储器带的CPU。即使当本地内存可用时,也将由某些数量的线程共享。(内存是一个节点的本地,而不是特定的核心。)一旦CPU可以容易地超过像上述测试的简单循环的可用带宽,因此在这种测试上增加线程不会提高性能,并且可以恶化性能由于缓慢缓慢的相干性。

只是一个理智测试,你还在使用并行收集器吗?-XX:+UseParallelGC。usenuma只生效。

其他提示

而不知道您究竟在做了什么以及您尝试解决的问题是什么。它看起来您周围的代码繁忙,因为它可能是不可能可扩展的主要原因。过度同步导致任何加速度减慢,一旦它将应用程序变得近似串行。所以我对你的建议是检查你的实施并试图解决这个问题。

添加。

添加了您在执行您所做的操作后。性能降级可以通过大而大量的存储器访问来解释。运行所有内线路后,他们需要访问内存控制器以不缓存数据,因为它们在不同的CPU上运行时,内存控制器可防止CPU同时执行它,这意味着每个缓存未命中的硬件级别都有同步。在您案例中,它几乎等于,好像您运行10个不同的独立程序。我想如果你将推出10(你可以用任何大数字替换10)复制你的Web浏览器,例如,你会看到相同的效果,但这并不意味着浏览器实现无效,您只需造成巨大的负担计算机内存。

作为Astem Notes,您可能有不必要的同步。但我首先建立事实。你的应用程序真的是你描述的吗?

这里是主题的洞察力文章: http://codeidol.com/java/java-concurrency/testing-concurrent-programs/avoiding-performance-testing-pitfalls/

写入有用的微基准真的很难,特别是当您处理并发代码时。例如,您可以拥有“死代码消除”,其中编译器优化您认为正在执行的代码。垃圾收集运行时也很难猜测。热点运行时优化也使测量更加困难。在线程的情况下,您需要考虑到创建它们的时间。因此,您可能需要使用`CyclicBarrier`等。这样的事情..

曾说过,如果你正在做的只是读,我发现它很难访问内存中的问题。如果您可以发布代码...

,我们可能能够更好地帮助您

春天有两个明显的潜在问题。

  • 使用更多线程分配更多突发缓存的数组。访问主内存或较低的缓存级别较慢。
  • 如果您使用的是随机数生成器的相同源,那么线程将在访问它的访问权限。它可能不是完全同步,而是具有无锁算法的内存障碍。一般锁定算法,虽然一般快速,但在高争用下得到得多。

除了并发问题外,您的速度最大的原因是内存缓存争用。

如果所有线程都访问相同的存储块,则需要访问它的机会在其他处理器内存缓存中。

如果存储是“只读”,您可以为每个线程提供自己的副本,这将允许JVM和处理器优化内存Accccesses。

我将测试修改了我发布的文章的建议。在我的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
.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top