【问题标题】:Can multithreading be used to make this matrix-vector multiplication algorithm more efficient?可以使用多线程来使这种矩阵向量乘法算法更高效吗?
【发布时间】:2019-09-24 23:09:07
【问题描述】:

对于我正在制作的程序,我想实现自己的矩阵向量乘法算法。这是它的代码。

static <T extends List<Double>> List<Double> matrixVectorMulti(List<T> matrix, List<Double> vector) {
    List<Double> output = new ArrayList<>();

    for(int row = 0; row < matrix.size(); row++) {
        double sum = 0;
        for (int column = 0; column < vector.size(); column++) {
            sum += matrix.get(row).get(column) * vector.get(column);
        }

        output.add(sum);
    }

    return output;
}

是否可以使用多线程来提高算法的性能,或者我的算法是否更有效。此外,如果您能提供多线程实现的示例代码,将会很有帮助。

注意:列表列表用于对矩阵进行建模,列表用于对向量进行建模。

【问题讨论】:

  • 您有示例输入/输出数据吗?由于这是 CPU 密集型活动,因此您可以实现并行执行逻辑。假设您有两个 CPU 内核 (Runtime.getRuntime().availableProcessors()),您可以有两个线程进行计算……一个用于 row = 0 到 matrix.size()/2,另一个用于剩余的行。您可能需要有多个output 并合并结果。
  • 要让你的代码在多线程下运行,你需要找到一种方法来安全地划分问题。查看您的代码,内部for 循环中的工作似乎可以发送到单独的线程。例如,如果您有 10 行,那么您将有 10 个线程,每个线程为每行数据计算一个单独的 sum 值。然后您的主要matrixVectorMulti 方法将等待每个线程完成并捕获每个sum 值,将它们全部相加,然后将最终值返回为output

标签: java multithreading


【解决方案1】:

多线程是否会提高性能实际上取决于许多因素

  • matrix 中存在多少数据
  • (虚拟)机有多少进程
  • 工作负载类型(CPU 密集型、IO 密集型)
  • 许多其他因素

您的用例是 CPU 密集型的(乘以最少的 IO)。假设matrix 很大,您可以通过实现多线程获得一定程度的好处。

以下是多线程的一个版本,它可以利用并行处理(线程数几乎等于可用于处理的处理器数)。

请记住,还有其他方法可以提高性能...例如使用矩阵大小初始化 output ArrayList 等。

注意:下面计算的性能统计数据不是一种科学的方法……它只是一种非正式的计算方法。该代码未经过全面测试。但它可以给你一个想法。

package my.package;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class MatrixMultiplication {

    public List<Double> matrixVectorMulti(List<List<Double>> matrix, List<Double> vector) {
        List<Double> output = new ArrayList<>();

        for(int row = 0; row < matrix.size(); row++) {
            double sum = 0;
            for (int column = 0; column < vector.size(); column++) {
                sum += matrix.get(row).get(column) * vector.get(column);
            }
            output.add(sum);
        }
        return output;
    }

    static List<List<Double>> initializeMatrix(int matrix_size) {
        List<List<Double>> matrix = new ArrayList<>();
        int rangeMin = 100;
        int rangeMax = 200;

        for (int i=0; i<matrix_size; i++) {
            List<Double> row = new ArrayList<>();
            for (int j=0; j<matrix_size; j++) {
                row.add(rangeMin + (rangeMax-rangeMin) * new Random().nextDouble());
            }
            matrix.add(row);
        }

        return matrix;
    }

    static List<Double> initializeVector(int matrix_size) {
        List<Double> vector = new ArrayList<>();
        int rangeMin = 100;
        int rangeMax = 200;

        for (int j=0; j<matrix_size; j++) {
            vector.add(rangeMin + (rangeMax-rangeMin) * new Random().nextDouble());
        }
        return vector;
    }

    public List<Double> matrixVectorMultiParallel(List<List<Double>> matrix, List<Double> vector) {
        int numOfThreads = Runtime.getRuntime().availableProcessors();
        List<Double> result = new ArrayList<>();

        //System.out.println(numOfThreads);
        //System.exit(0);
        int batchSize = matrix.size()/numOfThreads;
        PartialVectorMulti[] partialVectorMultis = new PartialVectorMulti[numOfThreads];

        int rangeStart = 0;
        int rangeEnd = 0;
        for (int i=0; i<numOfThreads; i++) {
            rangeEnd = rangeStart + batchSize-1;

            if (i == numOfThreads-1) {
                partialVectorMultis[i] = new PartialVectorMulti(matrix, rangeStart, matrix.size()-1, vector);
            } else {
                partialVectorMultis[i] = new PartialVectorMulti(matrix, rangeStart, rangeEnd, vector);
            }

            partialVectorMultis[i].start();
            rangeStart = rangeEnd + 1;
        }

        for (int i=0; i<numOfThreads; i++) {
            try {
                partialVectorMultis[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i=0; i<numOfThreads; i++) {
            result.addAll(partialVectorMultis[i].getPartialResult());
        }

        return result;
    }

    public static void main(String[] args) {
        int matrix_size = 10_000;

        List<List<Double>> matrix = initializeMatrix(matrix_size);
        List<Double> vector = initializeVector(matrix_size);
        List<Double> result;
        List<Double> resultMulti;

        MatrixMultiplication matrixMultiplication = new MatrixMultiplication();

        long startTime = System.currentTimeMillis();
        result = matrixMultiplication.matrixVectorMulti(matrix, vector);
        long endTime = System.currentTimeMillis();
        System.out.println("matrixVectorMulti: " + (endTime-startTime) + "milli seconds");

        startTime = System.currentTimeMillis();
        resultMulti = matrixMultiplication.matrixVectorMultiParallel(matrix, vector);
        endTime = System.currentTimeMillis();
        System.out.println("matrixVectorMultiParallel: " + (endTime-startTime) + "milli seconds");

    }

    class PartialVectorMulti extends Thread {
        List<List<Double>> matrix;
        List<Double> vector;
        int rowStart;
        int rowEnd;
        List<Double> partialResult = new ArrayList<>();

        public PartialVectorMulti(List<List<Double>> matrix, int rowStart, int rowEnd, List<Double> vector) {
            this.matrix = matrix;
            this.rowStart = rowStart;
            this.rowEnd = rowEnd;
            this.vector = vector;
        }

        public List<Double> getPartialResult() {
            return this.partialResult;
        }

        @Override
        public void run() {
            for (int i=rowStart; i<=rowEnd; i++) {
                double sum = 0;
                for (int j=0; j<vector.size(); j++) {
                    sum += matrix.get(i).get(j) * vector.get(j);
                }
                partialResult.add(sum);
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    使用多线程来提高算法效率涉及将问题分解为可以并行运行的更小的单元,并汇总每个单元的结果。

    要分解您的算法,您可以采用分而治之的策略,如https://pdfs.semanticscholar.org/7d07/110b25ec0009962c3599662a7b6b8e4c528a.pdf 中所述 并在自己的线程中运行每个子任务。然而,同步的开销使得它几乎不值得实现。

    【讨论】:

    • '然而,同步的开销使得它几乎不值得实现'可能并非在所有情况下都是正确的。在这个特定的问题中,任务是在通常不涉及 IO 等待/阻塞的情况下进行向量乘法。如果我们实现与可用处理器数量相等的线程数(通常是处理器数+1个线程),并以不需要同步的方式划分工作,我们可以获得更好的结果。
    • 单个数组上的分治系统(fork/join)不需要显式同步,根据规范:“线程 T1 中的最终操作与线程 T1 中的任何操作同步另一个线程 T2 检测到 T1 已终止。- T2 可以通过调用 T1.isAlive() 或 T1.join() 来完成此操作。" - 来自 §17.4.4
    猜你喜欢
    • 2020-10-02
    • 2014-10-23
    • 1970-01-01
    • 1970-01-01
    • 2014-12-16
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 2016-02-22
    相关资源
    最近更新 更多