科学计算, 生活

不同CPU核数对矩阵运算的加速效果

这是之前的两篇:

由于矩阵运算在底层通常采用了自动并行化的算法,因此在选择多个 CPU 核心时,矩阵计算的速度会显著提升。这种加速主要得益于矩阵运算本身的高度可并行性,例如矩阵乘法、求逆、特征值分解等操作可以被分解为多个独立的子任务,从而分配到不同的 CPU 核心上同时执行。本篇测试和记录不同 CPU 核数对矩阵运算的加速效果。

通过简单的测试,得到大致的结论:

  • 对于大矩阵的乘法,加速效果和 CPU 的核数接近于成正比。如果只是计算大矩阵的乘法,那么选择越多核,加速效果越好,资源利用率和性价比也比较高。
  • 对于矩阵求逆、矩阵特征值和特征向量,以及中小矩阵的乘法,加速效果仅在 2~8 核区间和 CPU 的核数接近于成正比。对于极小矩阵,多核基本上不起到加速作用。
  • 实际的程序除了矩阵运算,还有其他一些串行的代码,加速效果不一定按 CPU 数量成比例增加。因此,在以机时为计算成本的情况下,且不追求计算速度,那么使用单核计算的性价比最高,其次是 2~8 核。如果希望以尽快速度完成计算,且不使用过多资源,那么推荐选择 8 核左右。

计算结果统计(矩阵维度为 5000):

计算结果统计(矩阵维度为 10000):

计算结果统计(矩阵维度为 500):

计算结果统计(矩阵维度为 100):

以下是具体的代码和数据。

提交任务的 PBS 文件 task_2.sh(核数为 2):

#!/bin/sh
#PBS -N test_2
#PBS -l nodes=1:ppn=2
python matrix_running_time_for_different_num_of_cpu_cores.py

批量生产 PBS 文件的 Python 代码:

import guan  # https://py.guanjihuan.com | install: pip install --upgrade guan
import numpy as np

cpu_num_array = np.arange(1, 17)

sh_filename = 'task'
task_name = 'test'
py_filename='matrix_running_time_for_different_num_of_cpu_cores'

for cpu_num in cpu_num_array:
    guan.make_sh_file_for_qsub(sh_filename=sh_filename+'_'+str(cpu_num), command_line=f'python {py_filename}.py', cpu_num=cpu_num, task_name=task_name+'_'+str(cpu_num), cd_dir=0)

批量提交任务 Python 代码:

import numpy as np
import os

cpu_num_array = np.arange(1, 17)

for cpu_num in cpu_num_array:
    os.system(f'qsub task_{cpu_num}.sh')

测试矩阵运行时间的 Python 代码:

"""
This code is supported by the website: https://www.guanjihuan.com
The newest version of this code is on the web page: https://www.guanjihuan.com/archives/45324
"""

import numpy as np
import time

n = 5000
A = np.random.rand(n, n)
B = np.random.rand(n, n)

test_times = 20

# 矩阵行列式
start_time = time.time()
for _ in range(test_times):
    det_A = np.linalg.det(A)
det_time = (time.time() - start_time)/test_times
print(f"矩阵行列式时间: {det_time:.3f} 秒")

# 矩阵乘法
start_time = time.time()
for _ in range(test_times):
    C = np.dot(A, B)
multiply_time = (time.time() - start_time)/test_times
print(f"矩阵乘法时间: {multiply_time:.3f} 秒")

# 矩阵求逆
start_time = time.time()
for _ in range(test_times):
    inv_A = np.linalg.inv(A)
inv_time = (time.time() - start_time)/test_times
print(f"矩阵求逆时间: {inv_time:.3f} 秒")

# 矩阵的秩
start_time = time.time()
for _ in range(test_times):
    rank_A = np.linalg.matrix_rank(A)
rank_time = (time.time() - start_time)/test_times
print(f"矩阵的秩时间: {rank_time:.3f} 秒")

# 矩阵的特征值
start_time = time.time()
for _ in range(test_times):
    eigenvalues_A = np.linalg.eigvals(A)
eigen_time = (time.time() - start_time)/test_times
print(f"矩阵特征值时间: {eigen_time:.3f} 秒")

# 矩阵的特征值和特征向量
start_time = time.time()
for _ in range(test_times):
    eigenvalues_A, eigenvector_A = np.linalg.eig(A)
eigen_time = (time.time() - start_time)/test_times
print(f"矩阵特征值和特征向量时间: {eigen_time:.3f} 秒")

以下是矩阵维度为 5000 的运行结果数据。

运行结果(1核):

矩阵行列式时间: 1.750 秒
矩阵乘法时间: 3.999 秒
矩阵求逆时间: 6.059 秒
矩阵的秩时间: 17.196 秒
矩阵特征值时间: 61.901 秒
矩阵特征值和特征向量时间: 95.896 秒

运行结果(2核):

矩阵行列式时间: 0.831 秒
矩阵乘法时间: 1.679 秒
矩阵求逆时间: 2.723 秒
矩阵的秩时间: 7.136 秒
矩阵特征值时间: 29.890 秒
矩阵特征值和特征向量时间: 49.107 秒

运行结果(3核):

矩阵行列式时间: 0.807 秒
矩阵乘法时间: 1.257 秒
矩阵求逆时间: 2.190 秒
矩阵的秩时间: 5.901 秒
矩阵特征值时间: 24.575 秒
矩阵特征值和特征向量时间: 41.703 秒

运行结果(4核):

矩阵行列式时间: 0.470 秒
矩阵乘法时间: 0.985 秒
矩阵求逆时间: 1.577 秒
矩阵的秩时间: 4.676 秒
矩阵特征值时间: 21.824 秒
矩阵特征值和特征向量时间: 38.915 秒

运行结果(5核):

矩阵行列式时间: 0.404 秒
矩阵乘法时间: 0.699 秒
矩阵求逆时间: 1.169 秒
矩阵的秩时间: 4.294 秒
矩阵特征值时间: 17.642 秒
矩阵特征值和特征向量时间: 32.468 秒

运行结果(6核):

矩阵行列式时间: 0.266 秒
矩阵乘法时间: 0.562 秒
矩阵求逆时间: 0.939 秒
矩阵的秩时间: 3.147 秒
矩阵特征值时间: 14.322 秒
矩阵特征值和特征向量时间: 25.804 秒

运行结果(7核):

矩阵行列式时间: 0.557 秒
矩阵乘法时间: 0.534 秒
矩阵求逆时间: 1.231 秒
矩阵的秩时间: 5.389 秒
矩阵特征值时间: 21.276 秒
矩阵特征值和特征向量时间: 40.716 秒

运行结果(8核):

矩阵行列式时间: 0.303 秒
矩阵乘法时间: 0.525 秒
矩阵求逆时间: 0.958 秒
矩阵的秩时间: 3.438 秒
矩阵特征值时间: 16.942 秒
矩阵特征值和特征向量时间: 31.072 秒

运行结果(9核):

矩阵行列式时间: 0.395 秒
矩阵乘法时间: 0.510 秒
矩阵求逆时间: 1.070 秒
矩阵的秩时间: 5.832 秒
矩阵特征值时间: 21.368 秒
矩阵特征值和特征向量时间: 40.045 秒

运行结果(10核):

矩阵行列式时间: 0.263 秒
矩阵乘法时间: 0.424 秒
矩阵求逆时间: 0.793 秒
矩阵的秩时间: 2.915 秒
矩阵特征值时间: 14.077 秒
矩阵特征值和特征向量时间: 26.397 秒

运行结果(11核):

矩阵行列式时间: 0.403 秒
矩阵乘法时间: 0.409 秒
矩阵求逆时间: 1.208 秒
矩阵的秩时间: 4.633 秒
矩阵特征值时间: 20.758 秒
矩阵特征值和特征向量时间: 38.557 秒

运行结果(12核):

矩阵行列式时间: 0.227 秒
矩阵乘法时间: 0.380 秒
矩阵求逆时间: 0.760 秒
矩阵的秩时间: 2.863 秒
矩阵特征值时间: 15.104 秒
矩阵特征值和特征向量时间: 28.684 秒

运行结果(13核):

矩阵行列式时间: 0.223 秒
矩阵乘法时间: 0.372 秒
矩阵求逆时间: 0.709 秒
矩阵的秩时间: 3.690 秒
矩阵特征值时间: 14.505 秒
矩阵特征值和特征向量时间: 28.267 秒

运行结果(14核):

矩阵行列式时间: 0.219 秒
矩阵乘法时间: 0.339 秒
矩阵求逆时间: 0.677 秒
矩阵的秩时间: 2.672 秒
矩阵特征值时间: 14.159 秒
矩阵特征值和特征向量时间: 26.840 秒

运行结果(15核):

矩阵行列式时间: 0.253 秒
矩阵乘法时间: 0.334 秒
矩阵求逆时间: 0.671 秒
矩阵的秩时间: 2.787 秒
矩阵特征值时间: 14.552 秒
矩阵特征值和特征向量时间: 27.381 秒

运行结果(16核):

矩阵行列式时间: 0.253 秒
矩阵乘法时间: 0.293 秒
矩阵求逆时间: 0.906 秒
矩阵的秩时间: 3.186 秒
矩阵特征值时间: 17.547 秒
矩阵特征值和特征向量时间: 34.494 秒

画图 Python 代码 1:

import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
import numpy as np

cpu_num_array = np.arange(1, 17)

# n = 5000

time_array_1 = [3.999, 1.679, 1.257, 0.985, 0.699, 0.562, 0.534, 0.525, 0.510, 0.424, 0.409, 0.380, 0.372, 0.339, 0.334, 0.293]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Time (s)')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.show()

time_0 = time_array_1[0]
for i0 in range(len(time_array_1)):
    time_array_1[i0] = time_0/time_array_1[i0]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Ratio')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.plot(cpu_num_array, cpu_num_array, '--r')
plt.show()



time_array_2 = [6.059, 2.723, 2.190, 1.577, 1.169, 0.939, 1.231, 0.958, 1.070, 0.793, 1.208, 0.760, 0.709, 0.677, 0.671, 0.906]
fig, ax = plt.subplots()
ax.set_title('np.linalg.inv()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Time (s)')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_2, '-o', )
plt.show()

time_0 = time_array_2[0]
for i0 in range(len(time_array_2)):
    time_array_2[i0] = time_0/time_array_2[i0]
fig, ax = plt.subplots()
ax.set_title('np.linalg.inv()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Ratio')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_2, '-o', )
plt.plot(cpu_num_array, cpu_num_array, '--r')
plt.show()



time_array_3 = [95.896, 49.107, 41.703, 38.915, 32.468, 25.804, 40.716, 31.072, 40.045, 26.397, 38.557, 28.684, 28.267, 26.840, 27.381, 34.494]
fig, ax = plt.subplots()
ax.set_title('np.linalg.eig()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Time (s)')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_3, '-o', )
plt.show()

time_0 = time_array_3[0]
for i0 in range(len(time_array_3)):
    time_array_3[i0] = time_0/time_array_3[i0]
fig, ax = plt.subplots()
ax.set_title('np.linalg.eig()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Ratio')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_3, '-o', )
plt.plot(cpu_num_array, cpu_num_array, '--r')
plt.show()

画图 Python 代码 2:

import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
import numpy as np

cpu_num_array = np.arange(1, 17)

# n = 10000

time_array_1 = [62.485, 36.402, 25.316, 21.785, 17.619, 15.412, 16.718, 10.874, 9.588, 7.004, 6.733, 7.251, 7.248, 4.979, 4.889, 7.037]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Time (s)')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.show()

time_0 = time_array_1[0]
for i0 in range(len(time_array_1)):
    time_array_1[i0] = time_0/time_array_1[i0]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Ratio')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.plot(cpu_num_array, cpu_num_array, '--r')
plt.show()




# n = 500

time_array_1 = [0.004053801, 0.002605676, 0.001590664, 0.001103345, 0.001266495, 0.000954730, 0.000930616, 0.000779754, 0.000970088, 0.000651353, 0.000918634, 0.000538209, 0.000716934, 0.000521487, 0.001165715, 0.000764804 ]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Time (s)')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.show()

time_0 = time_array_1[0]
for i0 in range(len(time_array_1)):
    time_array_1[i0] = time_0/time_array_1[i0]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Ratio')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.plot(cpu_num_array, cpu_num_array, '--r')
plt.show()




# n = 100

time_array_1 = [0.000041899, 0.000104283, 0.000138595, 0.000038628, 0.000039540, 0.000029726, 0.000101140, 0.000023468, 0.000061134, 0.000225034, 0.000033439, 0.000075225, 0.000057184, 0.000040229, 0.000104894, 0.000041510]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Time (s)')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.show()

time_0 = time_array_1[0]
for i0 in range(len(time_array_1)):
    time_array_1[i0] = time_0/time_array_1[i0]
fig, ax = plt.subplots()
ax.set_title('np.dot()')
ax.set_xlabel('Number of CPU cores')
ax.set_ylabel('Ratio')
ax.xaxis.set_major_locator(MultipleLocator(1))
plt.plot(cpu_num_array, time_array_1, '-o', )
plt.plot(cpu_num_array, cpu_num_array, '--r')
plt.show()
20 次浏览

【说明:本站主要是个人的一些笔记和代码分享,内容可能会不定期修改。为了使全网显示的始终是最新版本,这里的文章未经同意请勿转载。引用请注明出处:https://www.guanjihuan.com

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code