【问题标题】:Why is my multi threading not efficient?为什么我的多线程效率不高?
【发布时间】:2011-06-20 18:52:32
【问题描述】:

我设计了一个类,它使用不同数量的线程用整数填充数组,以了解多线程的威力。但是根据我的结果,没有……

想法:想法过于用值“1”填充 100000000 个整数的数组。从 1 个线程开始(一个线程填充整个数组)并递增到 100 个线程(每个线程填充大小为 100000000/nbThreads 的子数组)

示例:使用 10 个线程,我创建了 10 个线程,每个线程填充 10000000 个整数的数组。

这是我的代码:

public class ThreadedArrayFilling extends Thread{
    private int start;
    private int partitionSize;
    public static int[] data;
    public static final int SIZE = 100000000;
    public static final int NB_THREADS_MAX = 100;


    public static void main(String[] args){
        data = new int[SIZE];
        long startTime, endTime;
        int partition, startIndex, j;
        ThreadedArrayLookup[] threads;

        for(int i = 1; i <= NB_THREADS_MAX; i++){       
            startTime = System.currentTimeMillis();
            partition = SIZE / i;
            startIndex = 0;
                threads = new ThreadedArrayLookup[i];
            for(j = 0; j < i; j++){         
                threads[j] = new ThreadedArrayLookup(startIndex, partition);
                startIndex += partition;
            }
            for(j = 0; j < i; j++){
                try {
                    threads[j].join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            endTime = System.currentTimeMillis();       
            System.out.println(i + " THREADS: " + (endTime - startTime) + "ms");
        }
    }

    public ThreadedArrayFilling(int start, int size){
        this.start = start;
        this.partitionSize = size;
        this.start();
    }

    public void run(){
        for(int i = 0; i < this.partitionSize; i++){
            data[this.start + i] = 1;
        }
    }

    public static String display(int[] d){
        String s = "[";

        for(int i = 0; i < d.length; i++){
            s += d[i] + ", ";
        }

        s += "]";
        return s;
    }

}

这是我的结果:

1 THREADS: 196ms
2 THREADS: 208ms
3 THREADS: 222ms
4 THREADS: 213ms
5 THREADS: 198ms
6 THREADS: 198ms
7 THREADS: 198ms
8 THREADS: 198ms
9 THREADS: 198ms
10 THREADS: 206ms
11 THREADS: 201ms
12 THREADS: 197ms
13 THREADS: 198ms
14 THREADS: 204ms
15 THREADS: 199ms
16 THREADS: 203ms
17 THREADS: 234ms
18 THREADS: 225ms
19 THREADS: 235ms
20 THREADS: 235ms
21 THREADS: 234ms
22 THREADS: 221ms
23 THREADS: 211ms
24 THREADS: 203ms
25 THREADS: 206ms
26 THREADS: 200ms
27 THREADS: 202ms
28 THREADS: 204ms
29 THREADS: 202ms
30 THREADS: 200ms
31 THREADS: 206ms
32 THREADS: 200ms
33 THREADS: 205ms
34 THREADS: 203ms
35 THREADS: 200ms
36 THREADS: 206ms
37 THREADS: 200ms
38 THREADS: 204ms
39 THREADS: 205ms
40 THREADS: 201ms
41 THREADS: 206ms
42 THREADS: 200ms
43 THREADS: 204ms
44 THREADS: 204ms
45 THREADS: 206ms
46 THREADS: 203ms
47 THREADS: 204ms
48 THREADS: 204ms
49 THREADS: 201ms
50 THREADS: 205ms
51 THREADS: 204ms
52 THREADS: 207ms
53 THREADS: 202ms
54 THREADS: 207ms
55 THREADS: 207ms
56 THREADS: 203ms
57 THREADS: 203ms
58 THREADS: 201ms
59 THREADS: 206ms
60 THREADS: 206ms
61 THREADS: 204ms
62 THREADS: 201ms
63 THREADS: 206ms
64 THREADS: 202ms
65 THREADS: 206ms
66 THREADS: 205ms
67 THREADS: 207ms
68 THREADS: 210ms
69 THREADS: 207ms
70 THREADS: 203ms
71 THREADS: 207ms
72 THREADS: 205ms
73 THREADS: 203ms
74 THREADS: 211ms
75 THREADS: 202ms
76 THREADS: 207ms
77 THREADS: 204ms
78 THREADS: 212ms
79 THREADS: 203ms
80 THREADS: 210ms
81 THREADS: 206ms
82 THREADS: 205ms
83 THREADS: 203ms
84 THREADS: 203ms
85 THREADS: 209ms
86 THREADS: 204ms
87 THREADS: 206ms
88 THREADS: 208ms
89 THREADS: 263ms
90 THREADS: 216ms
91 THREADS: 230ms
92 THREADS: 216ms
93 THREADS: 230ms
94 THREADS: 234ms
95 THREADS: 234ms
96 THREADS: 217ms
97 THREADS: 229ms
98 THREADS: 228ms
99 THREADS: 215ms
100 THREADS: 232ms

我错过了什么?

编辑:附加信息:

我的机器运行的是双核。

期望

  • 我期待看到 1 到 2 个线程之间的性能大幅提升(以利用双核)
  • 我还预计在此之后大量线程会出现减速。

但这并不能证实我的期望。我的期望是错误的,还是我的算法有问题?

【问题讨论】:

  • @nbarraille,你的机器有多少个内核?
  • “示例:有 10 个线程,我创建了 10 个线程,每个线程填充 10000000 个整数的数组。” - 我假设你的意思是每个线程都填充了数组的 1/10?
  • dsolimano:这台机器上有 2 个核心
  • @nbarraille,在修复了类和构造函数名称之间明显的不匹配之后,我已经在我的机器(2 个核心)上测试了你的代码,并且我在 2 个线程的性能上得到了相当显着的提升:800 1 线程毫秒,2 线程 500 毫秒。进一步增加并没有太大变化。
  • 这个网站急需“如何写微基准?”和“缓存未命中到底是什么?”指南。

标签: java multithreading performance benchmarking


【解决方案1】:

如果使用两个内核,您可能期望的最佳性能是 2 个线程占用一个线程一半的时间。之后任何额外的线程只会产生无用的开销——假设您完全受 CPU 限制,但实际上并非如此。

问题是为什么从 1 个线程变为 2 个线程时没有看到改进。原因可能是您的程序不受 CPU 限制,而是受内存限制。您的瓶颈是主内存访问,两个线程只是轮流写入主内存。实际的 CPU 内核大部分时间什么都不做。如果不是在大块内存上做少量实际工作,而是在少量内存上做大量 CPU 密集型工作,您会看到预期的差异。因为这样每个 CPU 内核都可以在其缓存中完全工作。

【讨论】:

  • 谢谢。我明白了为什么我没有看到 1 到 2 个线程之间的任何性能提升。但是你怎么解释100个线程的性能没有下降呢?
  • 附加问题:我已经了解到,在某些应用程序中,当限制因素不是 CPU 或内存时,使用比 CPU 数量更多的线程可能更有效(像磁盘或网络访问)。你同意吗?
  • @nbarraille:是的,确实如此。这个想法是一些线程可以使用 CPU,而其他线程则等待 IO。但是,最好为这些任务使用单独的线程(或线程池)。您通常不希望有多个线程访问磁盘,并且一个线程可以将任务移交给计算线程池。至于没有看到许多线程的退化,我不确定。也许每个线程基本上都按顺序完成了自己的工作,因此没有争用开销,并且线程创建开销被两个交替的内核隐藏起来,或者只是很小。
  • 如果您尝试分析您的应用程序,例如运行两个线程,您应该能够通过深入到程序集级别来查看哪些指令是面对面的。这也应该给你提示在哪里试验替代代码序列以提高并行性。在您获得经验以使您在编码过程中已经对热点将发生的位置有所了解之前,任何其他方式实际上都是浪费时间。
  • “您通常不希望有多个线程访问磁盘”这似乎是个好主意,但如果您使用带有 IOCP 的 WriteFile 和 ReadFile(它们是线程安全的) Windows 由磁盘 I/O 子系统来安排时间(相对于其他挂起的访问)以及如何安排(可能与其他访问相结合。在这种情况下,仅让一个线程进行磁盘访问将是非常低效的资源使用。
【解决方案2】:

当您的软件受 CPU 限制时,多线程非常高效:有很多应用程序是单线程的,您可以看到它们通过仅最大化一个内核的使用率(这在 CPU 监视器中非常明显)使用现代 CPU 令人痛苦地不足.

但是,启动多于可用(虚拟)CPU 数量的线程是没有意义的。

正确的多线程应用程序(例如数字运算)确实会创建与 JVM 可用的(虚拟)CPU 数量相关的工作线程。

【讨论】:

  • 是的,这正是我所期待的:由于我的计算机有 2 个 CPU,我期待看到 1 到 2 个核心之间的性能真正提高,然后大量线程会变慢.但我的假设都没有得到验证......
  • @nbarraille - 您的代码可能不受 CPU 限制,但更多地受到内存访问速度的限制。
  • 贾斯汀:我可以通过改变分配给 JVM 的内存量来改变这一点吗?如果是,我该如何在 Eclipse 上这样做?
  • @nbarraille:我认为你需要找到其他东西来让你的线程执行更多 CPU 密集型操作......增加分配的内存不会改变任何事情。
  • 代码不需要被 cpu 绑定以从多线程中受益。例如,尝试访问数据库。 1 个线程执行 100 个查询将比 10 个线程执行 10 个查询慢很多。当一些线程在等待、休眠、锁定阻塞或任何你命名的线程时,其他线程可以继续工作。在这种情况下,使用(甚至更多)比可用 CPU 更多的线程当然是有意义的。如果不是这种情况,多线程在单核处理器上就没有意义了。
【解决方案3】:

您在线程中执行的任务非常小,所用的时间被您的设置开销所抵消。

进行一些繁重的计算(例如,运行 PI 的近似值以放入数组中),您将看到多线程的好处,但最多只能达到您机器的内核数量。

或者做一些等待外部的事情(从数据库读取,从网站上抓取数据),只要其他线程在其他线程在等待时做一些有用的事情,这可能会更高性能。

【讨论】:

    【解决方案4】:

    有可能两个线程 - 每个都有自己的 cpu 或核心 - 协同工作,完成任务比仅一个线程完成所有工作要慢。两个内核都希望它们的 L1+L2 缓存将数据写入内存,这很好。然而,它们很快就会以这样一种方式使公共 L3 缓存饱和,即它会停止额外的写入,直到它设法将更新的缓存行写入 RAM,从而释放它以接受新的写入。

    换句话说,线程的目的不是执行任何处理,而是填充系统 RAM。系统 RAM 很慢,通过将单线程结果与两个线程的结果进行比较可以看出,写入 RAM 容量已全部用完一个线程,因此两个线程不能更快。

    您的线程非常小,很可能它们将驻留在 L1 缓存中,因此不需要从系统 RAM 中提取,这会妨碍您进行 RAM 写入的能力。无论您有 1 个或 100 个线程尝试写入 RAM,您写入 RAM 的能力都是相同的。但是,您拥有的线程越多,您将拥有的线程管理开销就越多。这对于少数线程可以忽略不计,但每增加一个线程就会增加,最终会变得明显。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-07
      • 2021-11-08
      • 1970-01-01
      • 1970-01-01
      • 2011-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多