JavaRush /Java 博客 /Random-ZH /Java 8 中数组的并行操作 - 翻译
billybonce
第 29 级
Москва

Java 8 中数组的并行操作 - 翻译

已在 Random-ZH 群组中发布
文章翻译
//Java 8 中的并行数组操作 //作者:Eric Bruno,2014 年 3 月 25 日 //drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287 //Eric Bruno 在金融领域工作并撰写博客对于网站 Dr. 多布的。
新版本的 Java 使与数组的并行交互变得更加容易,从而以最少的编码显着提高了性能。现在,Oracle 发布了 Java SE 8——这在语言方面是一个巨大的进步。此版本的重要功能之一是改进的并发性,其中一些功能出现在 java.util.Arrays 基类中。此类中添加了新方法,我将在本文中对其进行描述。其中一些用于 JDK8 的另一个新功能 - lambdas。但让我们言归正传吧。
Arrays.parallSort()
ParallelSort 的许多功能都基于并行合并排序算法,该算法递归地将数组拆分为多个部分,对它们进行排序,然后将它们同时重新组合成最终数组。使用它代替现有的顺序 Arrays.sort 方法可以提高对大型数组进行排序时的性能和效率。 例如,下面的代码使用顺序排序()和并行并行排序()对相同的数据数组进行排序: public class ParallelSort { public static void main(String[] args) { ParallelSort mySort = new ParallelSort(); int[] src = null; System.out.println("\nSerial sort:"); src = mySort.getData(); mySort.sortIt(src, false); System.out.println("\nParallel sort:"); src = mySort.getData(); mySort.sortIt(src, true); } public void sortIt(int[] src, boolean parallel) { try { System.out.println("--Array size: " + src.length); long start = System.currentTimeMillis(); if ( parallel == true ) { Arrays.parallelSort(src); } else { Arrays.sort(src); } long end = System.currentTimeMillis(); System.out.println( "--Elapsed sort time: " + (end-start)); } catch ( Exception e ) { e.printStackTrace(); } } private int[] getData() { try { File file = new File("src/parallelsort/myimage.png"); BufferedImage image = ImageIO.read(file); int w = image.getWidth(); int h = image.getHeight(); int[] src = image.getRGB(0, 0, w, h, null, 0, w); int[] data = new int[src.length * 20]; for ( int i = 0; i < 20; i++ ) { System.arraycopy( src, 0, data, i*src.length, src.length); } return data; } catch ( Exception e ) { e.printStackTrace(); } return null; } } 为了测试,我将图像中的原始数据加载到数组中,这占用了 46,083,360 字节(您的数据将取决于图像您将使用它)。在我的 4 核笔记本电脑上,顺序排序方法花了近 3,000 毫秒对数组进行排序,而并行排序方法最多需要大约 700 毫秒。同意,新语言更新将课堂表现提高 4 倍的情况并不常见。
Arrays.parallelPrefix()
parallelPrefix 方法将指定的数学函数共同应用于数组的元素,并行处理数组中的结果。与大型阵列上的顺序操作相比,这在现代多核硬件上要高效得多。对于不同基本类型的数据操作(例如,IntBinaryOperator、DoubleBinaryOperator、LongBinaryOperator 等)以及不同类型的数学运算符,此方法有多种实现。下面是一个使用与前一个示例相同的大型阵列的并行阵列堆叠示例,该示例在我的 4 核笔记本电脑上大约 100 毫秒内完成。 public class MyIntOperator implements IntBinaryOperator { @Override public int applyAsInt(int left, int right) { return left+right; } } public void accumulate() { int[] src = null; // accumulate test System.out.println("\nParallel prefix:"); src = getData(); IntBinaryOperator op = new ParallelSort.MyIntOperator(); long start = System.currentTimeMillis(); Arrays.parallelPrefix(src, new MyIntOperator()); long end = System.currentTimeMillis(); System.out.println("--Elapsed sort time: " + (end-start)); } ... }
Arrays.parallelSetAll()
新的parallelSetAll()方法创建一个数组,并根据生成这些值的函数将每个数组元素设置为一个值,使用并行性来提高效率。该方法基于 lambda(在其他语言中称为“闭包”) (并且,是的,这是作者的错误,因为 lambda 和闭包是不同的东西) ,这是 JDK8 的另一个新特性,我们将在以后的文章中讨论。只需注意 lambda 的语法很容易通过 -> 运算符识别,它会在箭头后面的右侧对传递给它的所有元素执行操作。在下面的代码示例中,对数组中的每个元素执行该操作,索引为 i。Array.parallelSetAll() 生成数组元素。例如,以下代码用随机整数值填充一个大数组: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } 要创建一个更复杂的数组元素生成器(例如,根据实际传感器的读数生成值的生成器),您可以使用类似于以下的代码我们将从 getNextSensorValue开始 public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( customGenerator(getNextSensorValue()))); } public int customGenerator(int arg){ return arg + 1; // some fancy formula here... } public int getNextSensorValue() { // Just random for illustration return new Random().nextInt(); } ,实际上,它会要求传感器(例如温度计)返回其当前值。这里,作为示例,生成随机值。以下 customGenerator() 方法根据您选择的情况使用选定的逻辑生成元素数组。这是一个小的补充,但对于实际情况,情况会更复杂。
什么是分裂器?
Arrays 类中另一个使用并发和 lambda 的附加功能是 Spliterator,它用于迭代和拆分数组。它的作用不仅限于数组——它也适用于 Collection 类和 IO 通道。Spliterator 的工作原理是自动将数组拆分为不同的部分,并安装新的 Spliterator 来对这些链接的子数组执行操作。它的名字由 Iterator 组成,它将移动迭代的工作“分割”成多个部分。使用相同的数据,我们可以对数组执行拆分操作,如下所示: 以这种方式对数据执行操作可以利用并行性。您还可以设置分割器参数,例如每个子数组的最小大小。 public void spliterate() { System.out.println("\nSpliterate:"); int[] src = getData(); Spliterator spliterator = Arrays.spliterator(src); spliterator.forEachRemaining( n -> action(n) ); } public void action(int value) { System.out.println("value:"+value); // Perform some real work on this data here... }
流处理
最后,您可以从数组创建一个 Stream 对象,它允许对整个数据样本进行并行处理,并概括为流序列。数据集合和新 JDK8 中的流之间的区别在于,集合允许您单独处理元素,而流则不允许。例如,对于集合,您可以添加元素、删除元素以及将它们插入到中间。流序列不允许您操作数据集中的单个元素,而是允许您对整个数据执行函数。您可以执行一些有用的操作,例如从集合中仅提取特定值(忽略重复)、数据转换操作、查找数组的最小值和最大值、map-reduce 函数(用于分布式计算)以及其他数学运算。以下简单示例使用并发来并行处理数据数组并对元素求和。 public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
结论
Java 8 肯定会是该语言最有用的更新之一。这里提到的并行功能、lambda 和许多其他扩展将成为我们网站上其他 Java 8 评论的主题。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION