5

各位程序员好。我已经问了一个问题,但尽管我得到了非常好的答案,但我无法解决我的问题。然后,我花时间重构我的代码,以提高它的并行化潜力(通过更少的计算批次和更多的计算任务)。但是我仍然不能有比串行处理更好的性能。

我怀疑这种缓慢的并行处理是由于上下文切换造成的。或者可能是由于公共对象的“自动”同步。我想你可以帮助我了解发生了什么。

让我陈述一下我的情况:我正在编写一个用于科学计算的程序。它不依赖于外部事物,只依赖于我在开始时给它的输入值。这个问题的大小可以通过Ns(这是我使用的名称)来衡量。它可以看作是解的“分辨率”,是用户输入的一种,通常在 100 左右。

这样,我的主类中有几个双精度数组,例如 double ys[Ns][N]or phiS[Ns][Nord][N],其中 N 和 Nord 是程序的其他固定量值。在我的程序中,我必须为每个Ns点计算几件事,这就是并行化。每个点计算都是独立的,所以我可以将它们分成不同的线程并希望它变得更快。

因此,我没有使用循环,而是for (int i=0; i<Ns; <i++)将此计算任务划分为 Runnable 批次,每个批次都在一个较小的区间内:for (int i=start; i<end; i++),其中 start 和 end 始终介于 0 和 Ns 之间。例如,如果我在双核 PC 上,我会制作两批,一批使用start = 0and end = Ns/2,另一批使用start = Ns/2and end = Ns。如果我在四核上,第二批将不得不start = Ns/4等等end = Ns/2(假设在每种情况下划分都是准确的)。

每个 Batch 作为实现 Runnable 的类,都存储在 a 中ArrayList<Batch>,并赋予 aFixedThreadPool大小等于内核数。CountDown它使用简单的方案执行批处理并等待它们完成。

这些批次中的每一个都需要从程序的主类访问这些数组上的数据,但是它们的访问是这样的,每个批次只能从yS[start][]to读取yS[end][],因此两个批次永远不会尝试读取相同的数组元素。我想知道 Java 是否仍然锁定 yS,即使每个批次都没有尝试访问与其他批次相同的元素。

我还想知道我的问题是否与上下文切换导致的开销有关,因为每个批次都需要处理数千个双打,以及程序的构建方式是否会影响它。

也许我应该找到一种方法将与其相关的数组元素传递给每个批次,但我不知道如何解决这个问题。如果有指针,我可以通过简单的指针操作获得仅包含所需元素的新数组,而无需重新分配任何内容。有没有办法在 Java 中做这样的事情?

好吧,最后,提一下:有一部分代码需要同步(它处理其他数组)并且它已经可以正常工作了。我上面描述的计算任务并不是我的程序唯一要做的事情。它们在一个循环中,与顺序处理部分交替,但作为总执行时间确实很重要。

所以,总而言之,问题是:为什么我没有从多线程中获益,当我期望的时候?

我刚刚在这里运行了几次普通串行和多线程程序,串行和多线程分别为 14500 毫秒和 15651 毫秒。两者都在同一个双核上。其他需要注意的点:在串行运行中,每个计算任务(从 0 到 Ns)大约需要 1.1 到 4.5 ms。从双线程开始,每批(Ns/2 个点)大约需要 0.5 到 3 毫秒;(从上到下测量 run() 方法。每次计算任务因它自己的数值收敛而异)

非常感谢您的关注。

4

4 回答 4

2
 I wonder if Java still locks up yS, even that each batch isn't trying to access
 the same elements as others.

Java 中没有自动同步或锁定。您必须明确编码。

I wonder also if my problem is related to the overhead due to context switching..

上下文切换确实有开销。如果您的所有线程都在同一个任务上工作,这是 CPU 密集型的,那么您的线程数应该等于可用处理器内核的数量。

If there were pointers, I could have new arrays of just the desired elements with
simple pointer operations and without reallocating anything.

Java 中的所有对象都是通过引用传递的(例如,当您将它们传递给方法时)。基本上所有的引用都是指针(不同的是你不能取消引用它们)。因此,在 Java 中不会复制任何对象,除非您的代码明确要求。

话虽如此,您应该注意另一件事:如果您向集合(列表、哈希映射等)添加大量元素,那么集合需要增长。在内部,所有 Collections 都使用数组来存储元素,当添加元素时,需要调整数组的大小。由于无法在 Java 中调整数组的大小,因此需要创建一个新数组并将对旧对象的所有引用复制到一个新数组中。或者,如果您使用原始类型,则需要复制所有数据。因此,在创建集合时,您应该将它们调整为适当的大小,这样就不需要调整它们的大小。

您可能还想阅读我的 Java 程序中应该使用多少线程?

于 2011-02-24T14:33:28.953 回答
2

您可能遇到的一种可能是线程在缓存行上颠簸。如果不同的线程快速写入同一缓存行中的位置(例如,在同一阵列中关闭),则硬件具有很高的通信开销,以确保数据保持一致。

于 2011-02-24T14:23:21.273 回答
1

根据您到目前为止提到的内容,我将尝试以下操作

  1. 比较串行和并行版本之间的结果,以增加阵列的大小。性能差异对于您的问题大小可能确实微不足道,并且可能仅在大小变大时才显示出来,即数组的大小

  2. 为每个可运行对象提供其自己的数组副本。鉴于性能,数组在内存中的布局方式以及访问它们的方式都会对性能产生影响。即使您可能有一个 2D 数组,它也会被布置为内存中串行的并发数组列表。因此,如果您在可运行对象之间共享此数组,则其中一些可能会变得低效。

于 2011-02-24T14:41:17.910 回答
-1

您是否有足够的内存来创建多个集合并将唯一的工作集合传递给每个线程,这样您就可以完全消除多个线程访问同一内存的争用?

于 2011-02-24T14:41:30.997 回答