【发布时间】:2015-04-16 03:46:14
【问题描述】:
我正在对我为 Java 中的并行矩阵乘法编写的算法进行性能测试。 我从运行时获取 cpu 内核数,然后使用线程池在可用内核之间划分计算循环。我测量顺序和并行版本的运行时间,然后在 Excel 图表中显示结果。 在那里我注意到一个奇怪的行为:
这是对从 50 到 1500 的方阵进行矩阵乘法的性能测试。结果是 10 次运行的平均值。测量值由线条上的点表示,线条本身是平滑的。 如您所见,顺序函数和并行函数的行交叉了两次。实际上,是 3 次,但第一次大约是 60-80 的矩阵大小,因此在此图表中不可见。这很正常,因为线程会引入开销,所以快速函数按顺序运行会更好。
另外两个路口正常吗?我在 500-700 区域进行了多次测量,这种行为似乎很普遍。
我认为可以参与其中的事情:操作系统线程管理、JVM 线程管理、一些线程池特定的行为、英特尔超线程(因为我的机器中有一个英特尔 i5-3210M cpu)。 然而,似乎表现不规则的东西(至少对我而言)实际上是顺序算法。请注意,在它达到 650 的大小之前,它几乎不会受到任何时间惩罚。然后它突然变陡,大小为 650。 相比之下,平行曲线看起来很平滑。
我已经检查了几次算法,我很确定它们没有错误。计算结果是正确的,这是肯定的。 我的函数是在一个双循环中测量的:外部函数重复测量以供以后平均,内部函数每一步都增加矩阵大小。 其中,源矩阵是随机的,顺序函数运行和测量,然后并行函数运行和测量。
图表上的行为是否正常?
主要:
// do n measurements
for (int n = 0; n < measurements; ++n) {
// display progress
System.out.println("Progress: " + (float) n / measurements * 100 + "%");
// single measurement
for (int i = 0, size_n = size; i < steps; ++i, size_n += increment) {
// allocate memory for matrices: source a, source b, result
float[][] src_a_seq = new float[size_n][size_n];
float[][] src_b_seq = new float[size_n][size_n];
float[][] src_a_par = new float[size_n][size_n];
float[][] src_b_par = new float[size_n][size_n];
float[][] res_seq = new float[size_n][size_n];
float[][] res_par = new float[size_n][size_n];
// fill source matrices with random values
miscManager.genRandMatrix(src_a_seq, size_n);
miscManager.genRandMatrix(src_b_seq, size_n);
miscManager.genRandMatrix(src_a_par, size_n);
miscManager.genRandMatrix(src_b_par, size_n);
// create time variables
long before, after, delta_t;
// time measurement, serial multiplication
before = System.nanoTime();
serialMultiplier.mul(src_a_seq, src_b_seq, res_seq, size_n);
after = System.nanoTime();
delta_t = after - before;
// add measurement to data
data[i][0] += delta_t;
// time measurement, parallel multiplication
before = System.nanoTime();
parallelMultiplier.mul(src_a_par, src_b_par, res_par, size_n);
after = System.nanoTime();
delta_t = after - before;
// add measurement to data
data[i][1] += delta_t;
}
}
System.out.println("Progress: 100.0%");
串行乘法:
public void mul(float[][] src_a, float[][] src_b, float[][] res, int size) {
for (int i = 0; i < size; ++i) {
for (int j = 0; j < size; ++j) {
res[i][j] = 0;
for (int k = 0; k < size; k++) {
res[i][j] += src_a[i][k] * src_b[k][j];
}
}
}
}
平行乘法:
public void mul(float[][] src_a, float[][] src_b, float[][] res, int size) {
// calculate data required for labor division
int n = size * size;
int load = n / cpuCoreCount + 1;
int remainder = n % cpuCoreCount;
// create thread pool
ExecutorService taskExecutor = Executors.newFixedThreadPool(cpuCoreCount);
// assign tasks
int m = 0;
int i = 0;
while (i < remainder) {
taskExecutor.execute(new MultiplierUnit(src_a, src_b, res, size, m, m + load));
m += load;
++i;
}
--load;
while (i < cpuCoreCount) {
taskExecutor.execute(new MultiplierUnit(src_a, src_b, res, size, m, m + load));
m += load;
++i;
}
// wait for tasks to finish
taskExecutor.shutdown();
try {
taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
System.out.println("error: thread pool interrupted exception");
System.exit(-1);
}
}
数据数组中的值稍后除以“测量值”以获得平均值。
乘数单位:
public class MultiplierUnit implements Runnable {
// source a, source b, result
private final float[][] src_a, src_b, res;
// matrix dimensions, first entry to execute, last entry to execute
private final int size, first, last;
public MultiplierUnit(float[][] src_a, float[][] src_b, float[][] res,
int size, int first, int last) {
this.src_a = src_a;
this.src_b = src_b;
this.res = res;
this.size = size;
this.first = first;
this.last = last;
}
// parallel multiplication
@Override
public void run() {
// index setup
int i = first / size;
int j = first % size;
int n = first;
// computation
while (n < last) {
while (j < size && n < last) {
res[i][j] = 0;
for (int k = 0; k < size; k++) {
res[i][j] += src_a[i][k] * src_b[k][j];
}
++n;
++j;
}
j = 0;
++i;
}
}
}
【问题讨论】:
-
我想知道矩阵大小为 650 时还会发生什么;也许矩阵大小达到某种缓存限制?类似的轮廓变化似乎发生在并行矩阵大小为 500 处。
-
@MarkoTopolnik 我已经包含了相关代码,但几乎所有这些都是相关的
-
我可能会创建一个jmh 基准。编写自定义基准测试代码是出了名的不可靠。
标签: java performance matrix parallel-processing performance-testing