Java大型資料集合實現並行加速處理幾種方法 - DZone
在這篇文章中,一個非常簡單的轉換操作將被應用於一個大型的Java資料集合。
轉換操作
對於轉換操作,我們定義了一個函式介面。它只是接收一個R型別的元素,應用一個轉換操作,並返回一個S型別的轉換物件。
@FunctionalInterface public interface ElementConverter<R, S> { S apply(R param); } |
我們建立了ElementConverter介面的兩個實現,其中一個將一個字串轉換為一個大寫的字串。
public class UpperCaseConverter implements ElementConverter<String, String> { @Override public String apply(String param) { return param.toUpperCase(); } } public class CollectionUpperCaseConverter implements ElementConverter<List<String>, List<String>> { @Override public List<String> apply(List<String> param) { return param.stream().map(String::toUpperCase).collect(Collectors.toList()); } } |
還實現了一個非同步執行器(AsynchronousExecutor)類,除了一些其他輔助性的方法外,還為並行處理策略提供了一個專門的方法。
public class AsynchronousExecutor<T, E> { private static final Integer MINUTES_WAITING_THREADS = 1; private Integer numThreads; private ExecutorService executor; private List<E> outputList; public AsynchronousExecutor(int threads) { this.numThreads = threads; this.executor = Executors.newFixedThreadPool(this.numThreads); this.outputList = new ArrayList<>(); } // Methods for each parallel processing strategy public void shutdown() { this.executor.shutdown(); try { this.executor.awaitTermination(MINUTES_WAITING_THREADS, TimeUnit.MINUTES); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } |
子列表分割槽
第一個提高對集合的轉換操作的並行策略是基於java.util.AbstractList的擴充套件。
簡而言之,CollectionPartitioner將一個源集合分割成子列表,子列表的大小是根據處理過程中使用的執行緒數計算的。
首先,通過取源集合大小和執行緒數之間的商來計算分塊大小。
然後,根據成對的索引(fromIndex,toIndex)從源集合中複製每個子列表,這些索引的值是同步計算的。
fromIndex = thread id + chunk size toIndex = MIN(fromIndex + chunk size, source collection size) public final class CollectionPartitioner<T> extends AbstractList<List<T>> { private final List<T> list; private final int chunkSize; public CollectionPartitioner(List<T> list, int numThreads) { this.list = list; this.chunkSize = (list.size() % numThreads == 0) ? (list.size() / numThreads) : (list.size() / numThreads) + 1; } @Override public synchronized List<T> get(int index) { var fromIndex = index * chunkSize; var toIndex = Math.min(fromIndex + chunkSize, list.size()); if (fromIndex > toIndex) { return Collections.emptyList(); // Index out of allowed interval } return this.list.subList(fromIndex, toIndex); } @Override public int size() { return (int) Math.ceil((double) list.size() / (double) chunkSize); } } |
一旦每個執行緒將轉換操作應用於其各自子列表中的所有物件,它必須同步地將修改後的物件新增到輸出列表中。這些步驟由AsynchronousExecutor類的一個特定方法指導。
public class AsynchronousExecutor<T, E> { public void processSublistPartition(List<T> inputList, ElementConverter<List<T>, List<E>> converter) { var partitioner = new CollectionPartitioner<T>(inputList, numThreads); IntStream.range(0, numThreads).forEach(t -> this.executor.execute(() -> { var thOutput = converter.apply(partitioner.get(t)); if (Objects.nonNull(thOutput) && !thOutput.isEmpty()) { synchronized (this.outputList) { this.outputList.addAll(thOutput); } } })); } } |
淺層分割
第二個並行處理策略挪用了淺層拷貝概念背後的想法。事實上,參與處理的執行緒並沒有收到從源集合複製的子列表。相反,每個執行緒使用子列表分割槽策略的相同代數計算各自的一對索引(fromIndex,toIndex),並直接在源集合上操作。但是,作為問題的一個要求,我們假設源集合不能被修改。在這種情況下,執行緒根據他們對源集合的分片來讀取物件,並將新轉換的物件儲存在一個與原始集合相同大小的新集合中。
請注意,這種策略在轉換操作過程中沒有任何同步執行點,也就是說,所有執行緒都是完全獨立地執行它們的任務。 但是組裝輸出集合至少可以使用兩種不同的方法。
1、基於列表的淺層分割
在這種方法中,在處理集合之前,會建立一個由預設元素組成的新列表。這個新列表的互不相干的片斷--以索引對(fromIndex, toIndex)為界限--被執行緒訪問。它們儲存了從源集合中讀取各自片斷所產生的每個新物件。AsynchronousExecutor類的一個新方法專門用於這種方法。
public class AsynchronousExecutor<T, E> { public void processShallowPartitionList(List<T> inputList, ElementConverter<T, E> converter) { var chunkSize = (inputList.size() % this.numThreads == 0) ? (inputList.size() / this.numThreads) : (inputList.size() / this.numThreads) + 1; this.outputList = new ArrayList<>(Collections.nCopies(inputList.size(), null)); IntStream.range(0, numThreads).forEach(t -> this.executor.execute(() -> { var fromIndex = t * chunkSize; var toIndex = Math.min(fromIndex + chunkSize, inputList.size()); if (fromIndex > toIndex) { fromIndex = toIndex; } IntStream.range(fromIndex, toIndex) .forEach(i -> this.outputList.set(i, converter.apply(inputList.get(i)))); })); } } |
2、基於陣列的淺層分割槽
這種方法與之前的方法不同,只是因為執行緒使用陣列來儲存轉換後的新物件,而不是一個列表。在所有執行緒完成其操作後,陣列被轉換為輸出列表。同樣,在AsynchronousExecutor類中為這個策略新增了一個新方法。
public class AsynchronousExecutor<T, E> { public void processShallowPartitionArray(List<T> inputList, ElementConverter<T, E> converter) var chunkSize = (inputList.size() % this.numThreads == 0) ? (inputList.size() / this.numThreads) : (inputList.size() / this.numThreads) + 1; Object[] outputArr = new Object[inputList.size()]; IntStream.range(0, numThreads).forEach(t -> this.executor.execute(() -> { var fromIndex = t * chunkSize; var toIndex = Math.min(fromIndex + chunkSize, inputList.size()); if (fromIndex > toIndex) { fromIndex = toIndex; } IntStream.range(fromIndex, toIndex) .forEach(i -> outputArr[i] = converter.apply(inputList.get(i))); })); this.shutdown(); this.outputList = (List<E>) Arrays.asList(outputArr); } } |
由於所有測試都是在 4 核和每核 2 個執行緒的機器上進行的,因此預計該策略的加速率會隨著使用多達 8 個執行緒而增加。儘管圖表反映了這種行為,但該演算法達到的最大加速比為 4.4X。1000 萬個物件的集合達到了非常相似的比率
理想情況下,通過使用 8 個執行緒,加速比應該對應於 CPU 時間的 8 倍改進。
詳細點選標題
相關文章
- Python資料預處理:Dask和Numba並行化加速!Python並行
- 幾種集合的幾種方法
- Java多執行緒並行處理任務的實現Java執行緒並行
- 【進階之路】多執行緒條件下分段處理List集合的幾種方法執行緒
- 這幾種Java異常處理方法,你會嗎?Java
- 集合資料處理(C#、JavaScript 和 Java)C#JavaScript
- java大資料處理:如何使用Java技術實現高效的大資料處理Java大資料
- Java 中的並行處理Java並行
- Java中實現執行緒安全HashSet的幾種方法 | baeldungJava執行緒
- Java面試之Java中實現多執行緒有幾種方法Java面試執行緒
- 使用無伺服器實現檔案處理的批處理 - DZone Cloud伺服器Cloud
- 《Java8實戰》-第七章筆記(並行資料處理與效能)Java筆記並行
- js中我最常用的幾種遍歷處理資料的方法梳理JS
- Java中實現並行請求兩種方式Java並行
- MySQL資料庫定時備份的幾種實現方法MySql資料庫
- Transformer其實是一種集合Set處理器ORM
- 並查集(一)並查集的幾種實現並查集
- Java建立多執行緒的幾種方式實現Java執行緒
- Java多執行緒【三種實現方法】Java執行緒
- 資料預處理(資料清洗)的一般方法及python實現Python
- npm install過程失敗的幾種處理方法NPM
- 使用Redis和Java進行資料庫快取 - DZone資料庫RedisJava資料庫快取
- PyTorch 60 分鐘入門教程:資料並行處理PyTorch並行
- 幾種常見的延遲執行處理方式
- 資料的集合處理,有哪些規則?
- Java中的並行流處理與效能提升Java並行
- Python資料處理(一):處理 JSON、XML、CSV 三種格式資料PythonJSONXML
- 使用 Parallel.ForEach 結合 Partitioner.Create 來實現每次並行處理5張圖片的邏輯。下面是一個示例程式碼,演示如何實現這種並行處理。Parallel並行
- 大資料分析的幾種方法大資料
- Css實現垂直居中的幾種方法CSS
- 並行處理 Parallel Processing並行Parallel
- C#實現DataTable資料分割處理C#
- java幾種代理模式的實現方式Java模式
- 3種方式實現python多執行緒併發處理Python執行緒
- 處理恢復資料方法
- 帶你區分幾種並行並行
- 實現三欄佈局的幾種方法
- css實現垂直水平居中的幾種方法CSS