对于一个试图自动将代码分割为线程的项目,你有什么意见(也许在编译时,可能是在运行时)。
请查看下面的代码:
for(int i=0;i<100;i++)
sum1 += rand(100)
for(int j=0;j<100;j++)
sum2 += rand(100)/2
这种代码可以自动分割成2个并行运行的线程。您认为这种情况可能吗?我有一种感觉,从理论上讲它是不可能的(它让我想起了停机问题),但我无法证明这个想法。
您认为这是一个有用的项目吗?是否有类似的东西?
对于一个试图自动将代码分割为线程的项目,你有什么意见(也许在编译时,可能是在运行时)。
请查看下面的代码:
for(int i=0;i<100;i++)
sum1 += rand(100)
for(int j=0;j<100;j++)
sum2 += rand(100)/2
这种代码可以自动分割成2个并行运行的线程。您认为这种情况可能吗?我有一种感觉,从理论上讲它是不可能的(它让我想起了停机问题),但我无法证明这个想法。
您认为这是一个有用的项目吗?是否有类似的东西?
这被称为自动并行化。如果你正在寻找可以为你完成此操作的程序,那么目前还没有这样的程序。不过,这可能会出现。这是一个难题,并且是一个活跃研究领域。如果你仍然感兴趣...
可以自动将示例拆分为多个线程,但不是您想象的方式。一些当前的技术尝试在其自己的线程中运行每次迭代的 for -loop。其中一个线程将获得偶数索引(i = 0,i = 2,...),另一个将获得奇数索引(i = 1,i = 3,...)。完成该 for -loop后,可以启动下一个循环。其他技术可能会更疯狂,执行 i ++ 增量在一个线程中进行,而在另一个线程中执行
rand()。
正如其他人所指出的那样,迭代之间存在真正的依赖性,因为 rand()具有内部状态。这本身并不妨碍并行化。编译器可以识别内存依赖性,并且可以从一个线程转发 rand()的修改状态到另一个线程。但是它可能会限制您只能使用少数几个并行线程。如果没有依赖关系,您可以在所有可用的核心上运行此代码。
如果您真正对此主题感兴趣,并且不介意阅读研究论文:
- 使用解耦软件管道自动提取线程(2005年)G. Ottoni。
- 使用软件多线程事务进行推测并行化(2010年)A. Raman。
这几乎是不可能的。
问题在于,为了有效地并行化,您需要知道比编译器甚至运行时更多的信息,而这些信息是不容易获得的。
虽然可能对非常简单的循环进行并行化,但是即使如此,也存在风险。例如,如果rand()
不是线程安全的,则上述代码只能并行化。 (Java的Math.random()已经为您同步。)
目前来看,尝试进行这种类型的自动并行化对于任何“真实”应用程序都不实用。
sum1 = (((rand_0 + rand_1) + rand_2) + rand_3) ....
sum1 = (rand_0 + rand_1) + (rand_2 + rand_3) ...
在一般情况下,能否知道一段代码是否可以并行化并不重要,因为即使你的算法无法检测出所有可以并行化的情况,也许它可以检测出其中的一些。
但这并不意味着这样做会有用。考虑以下几点:
我认为,如果你想在Java中实现这一点,你需要将其写成一个库,并让用户决定要并行化什么(库函数与注解一起使用?只是随便想想)。函数式语言更适合这样的任务。
作为一则趣闻:在并行编程课程中,我们必须检查代码并决定它是否可并行化。我记不清具体细节了(关于“最多一次”属性的什么东西?有人能告诉我吗?),但故事的寓意是,即使对于看似微不足道的情况,这也是极其困难的。我学到了自JDK 1.8(Java 8)起,可以使用parallelStream()在流使用中利用多个CPU核心。
然而,在最终决定使用parallelStream()进入生产之前,最好通过基准测试比较sequential()和parallel()的性能,然后决定哪个更理想。
为什么呢?原因是:在需要进行自动拆装箱操作时,平行流可能会比顺序流表现得差得多。对于这些情况,建议使用Java 8的原始流,例如IntStream、LongStream、DoubleStream。
参考资料:《现代Java实战》:曼宁出版社2019年
编程语言是Java,而Java是一种虚拟机。因此,一个人应该能够在VM拥有的不同线程上在运行时执行代码。由于所有的内存等都是这样处理的,它不会造成任何破坏。您可以将代码视为指令堆栈,估计执行时间,然后将其分配到一个数组的线程上,每个线程都有大致相同的执行堆栈时间。尽管如此,某些图形(如OpenGL立即模式)需要维护顺序,并且大多数情况下不应该被线程化。
rand
修改了全局状态;任何尝试并行化此过程都会改变代码的语义。 - Philipp