5

我最近在具有 2 个处理器的 Linux 服务器上进行了几次测试,每个处理器具有 20 个物理内核(下面给出了一个处理器的完整硬件描述)以及 20 个额外的逻辑内核(因此总共有 80 个内核)。进行这项调查的原因是我在一个研究实验室工作,那里的大部分代码都是用 Python 编写的,并且我发现了许多与计算机之间的 Python 性能变化相关的在线帖子。

这是我的设置:

  • 操作系统:CentOS 7.7.1908
  • 比较了两个python版本:Python 3.6.8Python Intel 3.6.9(2019更新5)
  • 处理器:Intel(R) Xeon(R) CPU E5-2698 v4 @ 2.20GHz

我对 numpy 和 scipy 的各种基本功能进行了比较,具体来说:

  • scipy.sparse.linal.spsolve:线性系统 (Ax=b) 的解,A 为 68000x68000 稀疏矩阵,xa 为 68000x50 稀疏矩阵,
  • scipy.sparse.linalg.eigsh: 68000x68000 稀疏矩阵的广义特征值问题的解决方案,
  • numpy.dot
  • scipy.linalg.choleskyscipy.linalg.svd

基本上,我决定timeit为每个版本的 Python 运行测试脚本(每个运行 25 到 100 次以获得相关结果),考虑到测试脚本的默认执行及其使用不同数量(x-1)的内核的执行命令taskset --cpu-list 0-x ...

以下是我的结果的简要总结:

  • scipy.sparse.linal.spsolve

计算时间与核心数量有关

  • scipy.sparse.linalg.eigsh

计算时间与核心数量有关

  • numpy.dot

计算时间与核心数量有关

  • scipy.linalg.cholesky

计算时间与核心数量有关

  • scipy.linalg.svd

计算时间与核心数量有关

我应该补充一点,黑点和虚线表示默认执行时间,而不使用taskset命令。

正如预期的那样,Python Intel 的性能优于 Python 3。然而,令我惊讶的是,使用 Python Intel 执行代码的默认执行速度可能比在有限(3 到 5)个内核上的执行速度要慢(尤其是对于 spsolve 和 eigsh )。

这正常吗(我猜在计算时间和核心之间的通信时间之间找到平衡点)?有什么方法可以优化 Python 代码在多核处理器上的默认执行?

以下是我的服务器核心之一的规格:

processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 79
model name      : Intel(R) Xeon(R) CPU E5-2698 v4 @ 2.20GHz
stepping        : 1
microcode       : 0xb00002e
cpu MHz         : 1207.958
cache size      : 51200 KB
physical id     : 0
siblings        : 20
core id         : 0
cpu cores       : 20
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 20
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 invpcid_single intel_ppin intel_pt ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts spec_ctrl intel_stibp flush_l1d
bogomips        : 4389.92
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

另外,这是我使用的代码:(1)对于spsolve

#!/usr/bin/env python
import timeit

setup = '''
import numpy
import sys
import timeit
import scipy.io
from scipy.sparse.linalg import spsolve
from scipy import sparse

K=scipy.io.loadmat('K0.mat', struct_as_record=False,squeeze_me=True)
K = K.tocsc()

def problin(K):   
    MS = numpy.zeros([numpy.shape(K)[0],50])
    for i in range(50):
        MS[i,i]=1;
    MS = sparse.csc_matrix(MS)
    #
    x = spsolve(-K,MS)
    return x
'''

code = '''  
x = problin(K) 
'''

count = 5
t = timeit.Timer(code,setup = setup) 
print("spsolve:", t.timeit(count)/count, "sec")

(2) 为eigsh

#!/usr/bin/env python
import timeit

setup = '''
import numpy
import sys
import scipy.io
from scipy.sparse.linalg import eigsh

K=scipy.io.loadmat('K0.mat', struct_as_record=False,squeeze_me=True)
M=scipy.io.loadmat('M0.mat', struct_as_record=False,squeeze_me=True)

K = K['K'].tocsc()
M = M['M'].tocsc()

def vp(K,M): # Function is compiled to machine code when called the first time  
    w, z = eigsh(K,10,M,sigma=1)
    f = numpy.sqrt(w)/2/numpy.pi
    return f
'''

code = '''  
f = vp(K,M)  
'''

count = 5
t = timeit.Timer(code,setup = setup) 
print("eigsh:", t.timeit(count)/count, "sec")

(3)对于cholesky和SVD,我在网上找到了脚本:

#!/usr/bin/env python                                                           
import timeit

setup = "import numpy;\
        import scipy.linalg as linalg;\
        x = numpy.random.random((1000,1000));\
        z = numpy.dot(x, x.T)"
count = 25

t = timeit.Timer("linalg.cholesky(z, lower=True)", setup=setup)
print("cholesky:", t.timeit(count)/count, "sec")

t = timeit.Timer("linalg.svd(z)", setup=setup)
print("svd:", t.timeit(count)/count, "sec")

(4) dot:

#!/usr/bin/env python
import numpy
from numpy.distutils.system_info import get_info
import sys
import timeit

print("version: %s" % numpy.__version__)
print("maxint:  %i\n" % sys.maxsize)

info = get_info('blas_opt')
print('BLAS info:')

for kk, vv in info.items():
    print(' * ' + kk + ' ' + str(vv))

setup = "import numpy; x = numpy.random.random((2000, 2000))"
count = 100

t = timeit.Timer("numpy.dot(x, x.T)", setup=setup)
print("\ndot: %f sec" % (t.timeit(count) / count))
4

0 回答 0