5

我正在编写一个在 Nehalem 处理器上运行的多线程 Java 应用程序。但是我有一个问题,从 4 个线程开始,我几乎看不到我的应用程序中的加速。

我做了一些简单的测试。我创建了一个线程,它只分配一个大数组并访问数组中的随机条目。因此,当我运行线程数时,运行时间不应该改变(假设我没有超过可用 CPU 内核的数量)。但我观察到的是,运行 1 或 2 个线程几乎需要相同的时间,但运行 4 或 8 个线程要慢得多。因此,在尝试解决我的应用程序中的算法和同步问题之前,我想找出我可以实现的最大可能并行化。

我使用-XX:+UseNUMA了 JVM 选项,所以数组应该分配在相应线程附近的内存中。

PS如果线程进行简单的数学计算,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 条目)上操作随机访问时,运行时间会急剧增加,所以我认为存在大块内存(因为它们不适合缓存内存?)在非访问中访问的问题- 高效的方式。

有什么想法可以解决这个问题吗?

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

4

6 回答 6

3

测试中的瓶颈是 CPU 到内存带宽。即使本地内存可用,它也会被一些线程共享。(内存是节点本地的,而不是特定内核的。)一旦 CPU 很容易超过像上面测试这样的简单循环的可用带宽,因此在这样的测试中增加线程不会提高性能,并且会降低性能由于缓存一致性恶化。

只是一个健全性测试,您是否也在使用并行收集器?-XX:+UseParallelGC. UseNUMA 才生效。

于 2010-07-15T13:35:16.950 回答
1

在不知道您到底在做什么以及您要解决的问题是什么的情况下。看起来您的代码有大量同步,因为它可能是不够可扩展的主要原因。一旦它使您的应用程序几乎是串行的,过度同步会导致减慢任何加速。所以我给你的建议是检查你的实现并试图解决这个问题。

添加。

在你添加了你正在做的事情的实现之后。性能下降可以通过大量的内存访问来解释。一旦您运行所有线程并且他们需要访问内存控制器以获取未缓存的数据,因为它们在不同的 CPU 上运行,内存控制器会阻止 CPU 同时执行此操作,这意味着每次缓存未命中时都会在硬件级别进行同步。在你的情况下,它几乎等于你在运行 10 个不同的独立程序。我猜如果你启动 10 个(你可以用任意大的数字替换 10 个)复制你的网络浏览器,例如,你会看到相同的效果,但这并不意味着浏览器的实现是无效的,你只是在电脑内存。

于 2010-07-15T06:37:14.633 回答
0

正如 Artem 所说,您可能有不必要的同步。但我会从确定事实开始。您的应用程序真的像您描述的那样运行得更慢吗?

这是一篇关于该主题的有见地的文章:http: //codeidol.com/java/java-concurrency/Testing-Concurrent-Programs/Avoiding-Performance-Testing-Pitfalls/

编写有用的微基准实际上非常困难,尤其是在处理并发代码时。例如,您可以进行“死代码消除”,其中编译器会优化您认为正在执行的代码。垃圾收集何时运行也很难猜测。Hotspot 的运行时优化也使测量更加困难。在线程的情况下,您需要考虑用于创建它们的时间。因此,您可能需要使用 `CyclicBarrier` 等来进行准确测量。像这样的东西..

话虽如此,如果您所做的只是阅读,我发现您很难在访问内存时遇到问题。如果您可以发布代码,我们可能会为您提供更好的帮助...

于 2010-07-15T07:11:18.490 回答
0

有两个明显的潜在问题浮现在脑海中。

  • 使用更多线程会分配更多数组,从而使缓存爆裂。访问主内存或较低级别的缓存要慢得多。
  • 如果您使用相同的随机数生成器实例源,那么线程将争夺对它的访问权。它可能不是完全同步,而是使用无锁算法的内存屏障。通常无锁算法虽然通常很快,但在高争用情况下会慢得多。
于 2010-07-15T08:18:57.140 回答
0

除了并发问题之外,最可能导致速度变慢的原因是内存缓存争用。

如果所有线程都在访问同一块存储,那么当您想要访问它时,它可能在其他处理器的内存缓存中。

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

于 2010-07-15T08:25:28.310 回答
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
于 2010-07-15T13:19:27.903 回答