繁体   English   中英

多线程矩阵加法比 java 中的单线程版本花费更长的时间

[英]Multithreaded matrix addition taking longer than single threaded version in java

在 Java 中弄脏了我的手,并在多线程中遇到了这个相当常见的问题。 我有一段代码(如下),它只需要两个矩阵 m1 和 m2,并将m1[i][j]m2[i][j]的总和写入result[i][j]

for(int i = 0; i < numCols ; i++) {
            for(int j = 0 ; j < numRows ; j++) {
                int finalI = i;
                int finalJ = j;
                executorService.execute(
                        new Runnable() {
                            @Override
                            public void run()  {
                                    ArrayList<Integer> v1 = m1.get(finalI);
                                    Integer m1Val = v1.get(finalJ);
                                    ArrayList<Integer> v2 = m2.get(finalI);
                                    Integer m2Val = v2.get(finalJ);
                                    result.get(finalI).add(finalJ,  m1Val + m2Val);
                            }
                        }
                );
            }
        }

arrays 属于ArrayLists<ArrayList<Integer>>类型,其中每个嵌套的ArrayList描述一个列。 它们的尺寸为numRows x numCols 我测量了这个操作的时间,对一对随机生成的大小为 10000 x 10000 的矩阵求和,发现单线程版本花了我 123 秒,而多线程(6 核英特尔 i7 上的 11 个线程)版本花了我大约300s。

在这种情况下,我选择使用 ArrayList,因为它们允许不安全的并发访问,即我可以同时修改 ArrayList 的不同部分。 但是,这并没有提供我所期望的任何额外的加速。 我对为什么看不到加速的猜测是因为以下原因:

  1. memory 总线堵塞,因此无法处理线程对 RAM 的多次读/写,因此 memory 的速度是一个瓶颈。
  2. 我为此操作使用了 Executors.newFixedThreadPool。 每次从 RAM 读取后,都会更新 L1 缓存以提高数据访问速度。 但是,此缓存无效,因为在给定处理器上的线程上执行的下一个任务可能需要 memory 中不同位置的数据,这些数据可能不会缓存在 L1 或 L2 级别,从而增加了时序。

这些猜测有意义吗? 我可能没有看到任何其他解释?

你有两个主要问题:

  1. 您正在为作为矩阵加法的一部分执行的每个加法安排一个可运行文件。 创建 Runnable、将其放入线程安全队列(由线程池在内部使用)以及让工作线程轮询该队列以获取任务会产生巨大的开销。
  2. 您正在为矩阵 ( ArrayLists<ArrayList<Integer>> ) 使用非常低效的数据结构,其数据局部性较差并且访问单个项目的开销很大。

1 和 2 都会导致很多额外的 CPU 周期被完全浪费掉; 它们还导致数据局部性差,超过必要的缓存未命中。

此外,您会得到不正确的结果,因为您使用的是非线程安全的数据结构(“在这种情况下为 ArrayList,因为它们允许不安全的并发访问”)来收集结果; 如果它没有为每个结果预先填充Integer值,那么随着列表扩展并覆盖早期数据,您将丢失数据。

一种有效的方法是:

  1. 在线程池中放入与 CPU 内核一样多的线程。 给每个线程一个矩阵的一部分,让每个Runnable对该整个部分执行加法。 这意味着,如果您有 8 个内核和 8 个工作线程,那么每个线程将处理一个 Runnable,并且该 Runnable 对矩阵的 12.5% 执行加法。
  2. 为您的数据结构使用int[][] ,或者更好的是,使用int[]并对row * width + col的索引进行自己的计算。 这提供了更好的数据局部性,并且不进行任何自动装箱和拆箱,从而提高了速度。 使用int[]特别适合添加矩阵,因为您可以将矩阵视为一个数组 - 您不需要了解行和列,只需result[i] = m1[i] + m2[i];

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM