【问题标题】:Performance of Concurrent Program Degrading with Increase in Threads?并发程序的性能随着线程的增加而下降?
【发布时间】:2015-05-02 23:02:01
【问题描述】:

我一直试图在四核计算机上实现以下代码,并且执行器服务中的线程数超过 100 次迭代的平均运行时间如下

1 个线程 = 78404.95

2 个线程 = 174995.14

4 线程 = 144230.23

但根据我所研究的 2*(no of cores) 线程应该为程序提供最佳结果,这在我的程序中显然不是这种情况,它奇怪地为单线程提供了最佳时间。

代码:

  import java.util.Collections;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestHashSet {

    public static void main(String argv[]){
        Set<Integer> S = Collections.newSetFromMap(new ConcurrentHashMap<Integer,Boolean>());
        S.add(1);
        S.add(2);
        S.add(3);
        S.add(4);
        S.add(5);
        long  startTime = System.nanoTime();
        ExecutorService executor = Executors.newFixedThreadPool(8);
        int Nb = 0;
        for(int i = 0;i<10;i++){
            User runnable = new User(S);
            executor.execute(runnable);

            Nb = Thread.getAllStackTraces().keySet().size();
        }
        executor.shutdown();
        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        long endTime = System.nanoTime();
        System.out.println(0.001*(endTime-startTime)+" And "+Nb);
    }
}
class User implements Runnable{
    Set<Integer> S;
    User(Set<Integer> S){
        this.S = S;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        Set<Integer> t =Collections.newSetFromMap(new ConcurrentHashMap<Integer,Boolean>());;
        for(int i = 0;i<10;i++){
            t.add(i+5);
        }
        S.retainAll(t);
        Set<Integer> t2 =Collections.newSetFromMap(new ConcurrentHashMap<Integer,Boolean>());;
        for(int i = 0;i<10;i++){
            t2.add(i);
        }
        S.addAll(t);
        /*
        ConcurrentHashSet<Integer> D = new ConcurrentHashSet<Integer>();
        for(int i=0;i<10;i++){
            D.add(i+3);
        }
        S.difference(D);
        */
    }
}

更新:如果我将每个线程的查询数增加到 1000,则 4 线程的性能优于单线程。当我每个线程仅使用大约 4 个查询并且没有查询时,我认为开销高于运行时现在增加的运行时间大于开销。谢谢

【问题讨论】:

  • 线程也有很多开销。我听说过 2*NOfCores,但我已经获得了最佳性能,例如 NOfCores + 1。
  • @Carcigenicate 但是在大约 100 次迭代中,单线程的平均时间约为 1200 毫秒,5 线程的平均时间为 1700 毫秒。但是 5 线程应该提高性能..?
  • 所以你根本没有增加 2 个线程?抱歉,我对您的时间有点困惑,因为上面有 1 个线程占用了 3569.707(我猜是毫秒)。
  • @Carcigenicate 当我在上面的代码中运行一个线程时,我得到一个线程大约 4000 和 2 个线程 4500 的时间,但是当我围绕整个代码运行 100 次并查看平均值时就像一个线程 - 1200 和 2 个线程 - 1400。
  • 这很奇怪,抱歉。您绝对应该通过添加第二个线程来增加;我经常使用第二个线程获得几乎 2 倍的增长,但显然它取决于很多事情。我真的不知道,对不起。上次我遇到像你这样的情况时,事实证明这些任务实际上并没有被并行化。阅读文档以确保您不需要初始化某些内容以使其非顺序运行。

标签: java multithreading optimization concurrency


【解决方案1】:

但是 5 线程应该可以提高性能..?

这就是>>你

但根据我所研究的,2*(核心数)线程应该会给出最佳结果...

如果你在某个地方读到过,那么你要么误读了,要么就是大错特错。

现实情况是,实现最佳性能的线程数高度依赖您的应用程序的性质,以及您运行的硬件。


根据对您代码的粗略阅读,这似乎是一个基准测试,用于测试 Java 处理多线程访问和更新共享集 (S) 的能力。每个线程都在线程限制集中执行一些操作,然后将线程限制集中的所有条目添加或删除到共享集中。

问题在于addAllretainAll 调用很可能是并发瓶颈。与基于 HashMap 相比,基于 ConcurrentHashMap 的集合将为点访问/更新集合提供更好的并发性能。但是,addAll 和 retainAll 对其他线程正在操作的相同条目执行 N 次这样的操作。鉴于这种操作模式的性质,您可能会在 ConcurrentHashMap 的不同区域内发生严重的争用。这很可能会导致一个线程阻塞另一个线程......并且速度变慢。

更新:如果我增加每个线程的查询数,4 线程的性能优于单线程。我认为当我每个线程仅使用大约 4 个查询并且没有增加的查询时,开销高于运行时运行时间是现在大于开销。

我假设您的意思是您正在增加哈希映射条目的数量。考虑到ConcurrentHashMap 的工作方式,这可能会减少平均争用。 (该类将映射划分为区域,并安排涉及不同区域中条目的操作产生最小可能的争用开销。通过增加不同条目的数量,您可以降低两个同时操作导致争用的概率。)


所以回到“2 x no of threads”这个事实。

我怀疑您一直在阅读的资料实际上并没有说这可以为您提供最佳性能。我怀疑他们真的这么说:

  • “2 x 线程数”是一个很好的起点...您需要针对您的应用程序/问题/硬件调整它,和/或

  • 对于计算密集型任务,不要超过“2 x 线程数”...因为它不太可能有帮助。

在您的示例中,争用的主要来源很可能是对共享集/映射的更新......以及确保它们以原子方式发生的开销。

您还可以在较低级别获得争用;即内存带宽争用(RAM 读/写)和内存缓存争用。是否会发生这种情况取决于您运行的硬件的规格......


最后要注意的是,您的基准测试存在缺陷,因为它不允许各种 VM 预热效果……例如 JIT 编译。您的 2 个线程时间是 1 个线程时间的两倍多,这一事实表明了该问题。

您的基准测试还有其他值得怀疑的方面:

  • run()方法完成的工作量太小了。

  • 此基准测试似乎不能代表真实世界的用例。在一个完全虚构的(无意义的)算法中测量加速不会为您提供任何关于当您扩展线程数时真实算法可能如何执行的线索。

  • 在 4 核机器上运行测试意味着您可能没有足够的数据点来得出具有科学意义的结论……假设基准测试是可靠的。


话虽如此,您似乎看到的 2 到 4 线程减速并不意外……对我来说。

【讨论】:

  • 但是如果我的电脑有 4 个内核。当我将线程数从 1 增加到 2 时,运行时间会增加。由 addAll 和 retainAll 引起的瓶颈是造成性能急剧下降的原因吗?因为在没有任何瓶颈的情况下,核心数加倍应该可以将性能提高 2 倍。
  • “是由 addAll 和 retainAll 造成的瓶颈导致性能急剧下降。” - 这就是我提出的解释。删除这些调用,看看会发生什么...
  • 即使我注释掉 retainAll 和 addAll 结果是一样的,但是如果我添加 Thread.sleep(10) 来运行方法 2 线程,运行时间大约是单线程的一半。这是否意味着即使对于更新/删除操作,开销也大于运行时间?
  • 更新:如果我增加每个线程的查询数,4 线程的性能比单线程好。我认为当我每个线程只使用大约 4 个查询并且没有查询增加的运行时间现在大于开销。谢谢
猜你喜欢
  • 2021-05-06
  • 2018-09-22
  • 2020-09-02
  • 2012-01-22
  • 1970-01-01
  • 2016-12-19
  • 2012-08-25
  • 1970-01-01
  • 2020-09-17
相关资源
最近更新 更多