JavaRush /Blog Java /Random-VI /Các thao tác song song trên mảng trong Java 8 - bản dịch
billybonce
Mức độ
Москва

Các thao tác song song trên mảng trong Java 8 - bản dịch

Xuất bản trong nhóm
bản dịch của bài viết
// Thao tác mảng song song trong Java 8 // Tác giả Eric Bruno, ngày 25 tháng 3 năm 2014 //drdobbs.com/jvm/parallel-array-Operations-in-java-8/240166287 //Eric Bruno làm việc trong lĩnh vực tài chính và blog cho trang web Dr. của Dobb.
Bản phát hành mới của Java giúp tương tác song song với các mảng dễ dàng hơn - dẫn đến hiệu suất được cải thiện đáng kể với mức độ mã hóa tối thiểu. Bây giờ, Oracle đang phát hành Java SE 8 - đây là một bước tiến lớn về mặt ngôn ngữ. Một trong những tính năng quan trọng của bản phát hành này là tính năng đồng thời được cải thiện, một số tính năng này xuất hiện trong lớp cơ sở java.util.Arrays. Các phương thức mới đã được thêm vào lớp này mà tôi sẽ mô tả trong bài viết này. Một số trong số này được sử dụng trong một tính năng mới khác của JDK8 - lambdas. Nhưng hãy bắt tay vào công việc.
Mảng.paralellSort()
Nhiều tính năng của ParallelSort dựa trên thuật toán sắp xếp hợp nhất song song, phân chia đệ quy một mảng thành các phần, sắp xếp chúng và sau đó kết hợp lại chúng đồng thời thành một mảng cuối cùng. Việc sử dụng nó thay vì phương pháp Arrays.sort tuần tự hiện có sẽ mang lại hiệu suất và hiệu quả được cải thiện khi sắp xếp các mảng lớn. Ví dụ: mã bên dưới sử dụng sắp xếp tuần tự() và song song song song() để sắp xếp cùng một mảng dữ liệu: 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; } } Để kiểm tra, tôi đã tải dữ liệu thô từ hình ảnh vào mảng, mất 46.083.360 byte (và dữ liệu của bạn sẽ phụ thuộc vào hình ảnh mà bạn sẽ sử dụng). Phương pháp sắp xếp tuần tự mất gần 3.000 mili giây để sắp xếp mảng trên máy tính xách tay 4 lõi của tôi, trong khi phương pháp sắp xếp song song mất tối đa khoảng 700 mili giây. Đồng ý rằng việc cập nhật ngôn ngữ mới sẽ cải thiện hiệu suất lớp lên 4 lần không thường xuyên xảy ra.
Mảng.parallelPrefix()
Phương thức ParallelPrefix áp dụng một hàm toán học đã chỉ định cho các phần tử của một mảng một cách tổng thể, xử lý các kết quả trong mảng một cách song song. Điều này hiệu quả hơn nhiều trên phần cứng đa lõi hiện đại, so với các hoạt động tuần tự trên các mảng lớn. Có nhiều cách triển khai phương pháp này cho các loại hoạt động dữ liệu cơ bản khác nhau (ví dụ: IntBinaryOperator, DoubleBinaryOperator, LongBinaryOperator, v.v.), cũng như cho các loại toán tử toán học khác nhau. Đây là ví dụ về xếp chồng mảng song song bằng cách sử dụng cùng một mảng lớn như ví dụ trước, hoàn thành trong khoảng 100 mili giây trên máy tính xách tay 4 lõi của tôi. 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)); } ... }
Mảng.parallelSetAll()
Новый метод parallelSetAll() создает массив и устанавливает каждому элементу массива meaning в соответствии с генерирующей эти значения функцией, используя параллельность для повышения эфективности. Этот метод основан на лямбдах(называемых "замыканиями"(closures) в других языках) (и, да, тут ошибка автора, ибо лямбды и замыкания это разные вещи) , и которые являются еще одной новинкой JDK8, которую мы обсудим в будущих статьях. Будет достаточно заметить, лямбды, чей синтаксис легко опознать по оператору ->, производящему операцию над правой частью после стрелки для всех переданных ему элементов. В примере codeа, представленном ниже - действие производится для каждого element в массиве, проиндексированного по i. Array.parallelSetAll() генерирует элементы массива. Например, следующий code заполняет большой массив случайными integer-значениями: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } Для создания более сложного генератора элементов массива(например, такого что генерировал бы значения на основе считывания с датчиков из реального мира), можно использовать code близкий к следующему: 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(); } Мы начнем с getNextSensorValue, который в реальности будет запрашивать датчик(например термометр) вернуть ему текущее meaning. Здесь же для примера генерируется случайное meaning. Следующий customGenerator() метод генерирует массив элементов с использованием выбранной логики на основе выбранного вами случая. Вот небольшое дополнение, но для реальных случаев, тут было бы что-нибудь посложнее.
What такое Spliterator?
Другое дополнение к классу Arrays, которое использует параллельность и лямбды - это Spliterator, который используется для итерации и разделения массива. Его действие не ограничено только массивами - он также хорошо работает и для классов Collection и IO каналов. Spliterator'ы работают на основе автоматического разбиения массива на различные части, а новый Spliterator устанавливается для того чтобы производить операции над этими связанными подмассивами. Его название составленно из Iterator(итератора), который "разделяет"(splits) его работу по перемещению-итерации на части. Используя наши, всё те же, данные, мы можем произвести раздельноитерированное(splititerated) действие над нашим массивом следующим образом: 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... } Выполнение действий над данными таким образом использует плюсы параллельности. Вы можете также задать параметры сплититератора, такие How минимальный размер каждого подмассива.
Stream - обработка
Cuối cùng, từ một Mảng, bạn có thể tạo một đối tượng Luồng, cho phép xử lý song song trên toàn bộ mẫu dữ liệu, được tổng quát hóa thành một chuỗi luồng. Sự khác biệt giữa Bộ sưu tập dữ liệu và Luồng từ JDK8 mới là các bộ sưu tập cho phép bạn làm việc với các phần tử riêng lẻ khi chuỗi Luồng không làm như vậy. Ví dụ: với bộ sưu tập, bạn có thể thêm các phần tử, xóa chúng và chèn chúng vào giữa. Chuỗi Luồng không cho phép bạn thao tác các phần tử riêng lẻ từ một tập dữ liệu mà thay vào đó cho phép bạn thực hiện toàn bộ các chức năng trên dữ liệu. Bạn có thể thực hiện các thao tác hữu ích như chỉ trích xuất các giá trị cụ thể (bỏ qua sự lặp lại) từ một tập hợp, các thao tác chuyển đổi dữ liệu, tìm giá trị tối thiểu và tối đa của một mảng, các hàm thu nhỏ bản đồ (được sử dụng trong điện toán phân tán) và các phép toán khác. Ví dụ đơn giản sau đây sử dụng đồng thời để xử lý song song một mảng dữ liệu và tính tổng các phần tử. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
Phần kết luận
Java 8 chắc chắn sẽ là một trong những bản cập nhật hữu ích nhất cho ngôn ngữ này. Các tính năng song song được đề cập ở đây, lambdas và nhiều tiện ích mở rộng khác sẽ là chủ đề của các bài đánh giá Java 8 khác trên trang web của chúng tôi.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION