我并行化了一个Java程序。在具有 4 核的 Mac 上,以下是不同线程数的时间。
threads # 1 2 4 8 16
time 2597192200 1915988600 2086557400 2043377000 1931178200
在具有两个套接字(每个套接字有 4 个内核)的 Linux 服务器上,下面是测量的时间。
threads # 1 2 4 8 16
time 4204436859 2760602109 1850708620 2370905549 2422668438
如您所见,加速比与线性加速相去甚远。在这种情况下,几乎没有并行化开销,例如同步或 I/O 依赖关系。
我有两个问题:
- 这些数据是否意味着这个Java程序是内存绑定的?
- 如果是这样,有没有办法在不改变硬件的情况下进一步提高性能?
回答标题问题
阿姆达尔定律解释说,并行化程序获得的加速取决于程序的可并行化程度。
我们还必须增加协调并行性的开销。
因此,我们考虑程序的哪些百分比/部分是可并行化的,以及会产生哪些开销(同步、通信、错误共享等)。
读取内存是否可并行化?
从硬盘
您可以同时从 2 个不同的硬盘驱动器读取数据而不会减慢速度。
但是,通常并行性不会加快从硬盘驱动器读取的速度。
硬盘驱动器(即带有旋转磁盘的驱动器)已优化为按顺序读取,在内存位置之间跳转会减慢整体内存传输速度。
固态驱动器实际上非常擅长随机访问数据,在内存中跳来跳去,因此使用固态驱动器保持读/写队列已满是个好主意。
从内存和缓存
了解缓存行的概念将有助于避免错误共享。
这种类型的内存操作可以有效地并行化,例如通过将数组划分为四个分区来迭代数组。
您的问题
我假设你的时间以纳秒为单位,所以在计算机 1 上,程序需要 2.5 秒,然后稳定到大约 2 秒,峰值为 1.9 秒。
我希望您同时运行最少的后台程序,并且您执行了几次这些测试以摆脱违规行为。
此外,由于 Java 虚拟机的实时编译 (JIT),计时可能会出现不规则,因此为了准确计时,您需要在循环中运行代码几次,并存储上次迭代的时间。(或预编译为本机代码)。
此外,由于程序第一次运行时,硬盘驱动器中使用的大部分数据都将移动到缓存中,因此以后的执行应该更快。(因此,要么使用循环后最后一次运行的计时以确保内存在缓存中,要么使用第一次计时,但在计时之间关闭电源并打开计算机)。
程序是否绑定内存?
仅根据您的时间安排,这很难说。
第一台计算机花了 2.5 秒,然后用 2 个线程加速了 20%,但随后保持在大约 2.0 秒。
就其本身而言,这种加速可能只是 JIT 和缓存内存由 1 线程上的计时填充的结果。之后,运行时的任何差异都可能只是噪音。
第二台计算机花了4.2秒,然后是2.8秒,然后是1.9秒,然后又回到了大约2.3秒。
这似乎确实展示了某种类型的并行加速,但是发生了一些争用时间(内存、缓存行、同步等),如时间从 4 个线程增加到 8 个线程所证明的那样。
有什么方法可以提高性能吗?
对代码使用探查器,确定代码的哪些部分占用的时间最多。
(可以通过调试代码并中断并查看程序的位置来模拟探查器。重复 10 次,看看是否有一个部分比另一个部分按比例停止。
使用更好的算法或以更好的方式排列内存中的数据(数据结构)。
在问题中利用更多的并行性。
尝试使硬盘内存按顺序读取。也许只有一个线程从硬盘驱动器读取,然后将数据放入并发队列中,由其他线程操作。
好吧,它们暗示该算法不受 CPU 限制。 它可能受其他东西的约束 - 它可能是内存,I/O或其他东西,但它可能不受CPU限制。