多核机器上单精度数组与双精度数组的矩阵乘法的性能下降

2023-12-28

UPDATE

不幸的是,由于我的疏忽,我有一个旧版本的 MKL (11.1) 与 numpy 链接。新版本的 MKL (11.3.1) 在 C 中和从 python 调用时提供相同的性能。

令人困惑的是,即使将编译后的共享库与较新的 MKL 显式链接,并通过 LD_* 变量指向它们,然后在 python 中执行 import numpy,也会以某种方式使 python 调用旧的 MKL 库。只有通过用较新的 MKL 替换 python lib 文件夹中的所有 libmkl_*.so,我才能匹配 python 和 C 调用的性能。

背景/图书馆信息。

矩阵乘法是通过 sgemm(单精度)和 dgemm(双精度)Intel 的 MKL 库调用(通过 numpy.dot 函数)完成的。库函数的实际调用可以通过以下方式进行验证:奥教授。

这里使用 2x18 核心 CPU E5-2699 v3,因此总共有 36 个物理核心。 KMP_AFFINITY=分散。在Linux上运行。

TL;DR

1) 为什么 numpy.dot 尽管调用相同的 MKL 库函数,但与 C 编译代码相比最多慢两倍?

2) 为什么通过 numpy.dot 性能会随着内核数量的增加而降低,而在 C 代码中却没有观察到相同的效果(调用相同的库函数)。

问题

我观察到在 numpy.dot 中进行单/双精度浮点数的矩阵乘法,以及直接从编译的 C 调用 cblas_sgemm/dgemm共享库与从纯 C 代码内部调用相同的 MKL cblas_sgemm/dgemm 函数相比,性能明显较差。

import numpy as np
import mkl
n = 10000
A = np.random.randn(n,n).astype('float32')
B = np.random.randn(n,n).astype('float32')
C = np.zeros((n,n)).astype('float32')

mkl.set_num_threads(3); %time np.dot(A, B, out=C)
11.5 seconds
mkl.set_num_threads(6); %time np.dot(A, B, out=C)
6 seconds
mkl.set_num_threads(12); %time np.dot(A, B, out=C)
3 seconds
mkl.set_num_threads(18); %time np.dot(A, B, out=C)
2.4 seconds
mkl.set_num_threads(24); %time np.dot(A, B, out=C)
3.6 seconds
mkl.set_num_threads(30); %time np.dot(A, B, out=C)
5 seconds
mkl.set_num_threads(36); %time np.dot(A, B, out=C)
5.5 seconds

与上面完全相同,但使用双精度 A、B 和 C,您将得到: 3核:20s,6核:10s,12核:5s,18核:4.3s,24核:3s,30核:2.8s,36核:2.8s。

单精度浮点速度的提高似乎与缓存未命中有关。 对于 28 核运行,以下是 perf 的输出。 对于单精度:

perf stat -e task-clock,cycles,instructions,cache-references,cache-misses ./ptestf.py
631,301,854 cache-misses # 31.478 % of all cache refs

和双精度:

93,087,703 cache-misses # 5.164 % of all cache refs

C 共享库,编译为

/opt/intel/bin/icc -o comp_sgemm_mkl.so -openmp -mkl sgem_lib.c -lm -lirc -O3 -fPIC -shared -std=c99 -vec-report1 -xhost -I/opt/intel/composer/mkl/include

#include <stdio.h>
#include <stdlib.h>
#include "mkl.h"

void comp_sgemm_mkl(int m, int n, int k, float *A, float *B, float *C);

void comp_sgemm_mkl(int m, int n, int k, float *A, float *B, float *C)
{
    int i, j;
    float alpha, beta;
    alpha = 1.0; beta = 0.0;

    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
                m, n, k, alpha, A, k, B, n, beta, C, n);
}

Python包装函数,调用上面编译的库:

def comp_sgemm_mkl(A, B, out=None):
    lib = CDLL(omplib)
    lib.cblas_sgemm_mkl.argtypes = [c_int, c_int, c_int, 
                                 np.ctypeslib.ndpointer(dtype=np.float32, ndim=2), 
                                 np.ctypeslib.ndpointer(dtype=np.float32, ndim=2),
                                 np.ctypeslib.ndpointer(dtype=np.float32, ndim=2)]
    lib.comp_sgemm_mkl.restype = c_void_p
    m = A.shape[0]
    n = B.shape[0]
    k = B.shape[1]
    if np.isfortran(A):
        raise ValueError('Fortran array')
    if m != n:
        raise ValueError('Wrong matrix dimensions')
    if out is None:
        out = np.empty((m,k), np.float32)
    lib.comp_sgemm_mkl(m, n, k, A, B, out)

然而,来自 C 编译二进制文件的显式调用(调用 MKL 的 cblas_sgemm / cblas_dgemm)以及通过 C 中的 malloc 分配的数组,与 python 代码(即 numpy.dot 调用)相比,性能几乎提高了 2 倍。此外,没有观察到随着内核数量的增加而导致性能下降的影响。单精度矩阵乘法的最佳性能为 900 ms通过 mkl_set_num_cores 使用全部 36 个物理核心并使用 numactl --interleave=all 运行 C 代码时实现。

也许有任何奇特的工具或建议可以进一步分析/检查/理解这种情况?任何阅读材料也非常受欢迎。

UPDATE按照 @Hristo Iliev 的建议,运行 numactl --interleave=all ./ipython 并没有改变计时(在噪音范围内),但改善了纯 C 二进制运行时。


我怀疑这是由于不幸的线程调度造成的。我能够重现与您类似的效果。 Python 的运行时间约为 2.2 秒,而 C 版本的运行时间在 1.4-2.2 秒之间存在巨大差异。

申请:KMP_AFFINITY=scatter,granularity=thread这可确保 28 个线程始终在同一处理器线程上运行。

将 C 的运行时间减少到更稳定的约 1.24 秒,将 Python 的运行时间减少到约 1.26 秒。

这是在 28 核双路 Xeon E5-2680 v3 系统上。

有趣的是,在非常相似的 24 核双插槽 Haswell 系统上,即使没有线程关联/固定,Python 和 C 的性能也几乎相同。

为什么python会影响调度?好吧,我假设它周围有更多的运行时环境。最重要的是,如果不固定,您的性能结果将是不确定的。

您还需要考虑,Intel OpenMP 运行时会产生一个额外的管理线程,这可能会混淆调度程序。固定还有更多选择,例如KMP_AFFINITY=compact- 但由于某种原因,我的系统完全混乱了。你可以加,verbose到变量以查看运行时如何固定线程。

利克维德平 https://github.com/RRZE-HPC/likwid/wiki/Likwid-Pin是一种有用的替代方案,提供更方便的控制。

一般来说,单精度应该至少与双精度一样快。双精度可能会更慢,因为:

  • 您需要更多的内存/缓存带宽来实现双精度。
  • 您可以构建具有更高单精度吞吐量的 ALU,但这通常不适用于 CPU,而是适用于 GPU。

我认为一旦你消除了性能异常,这就会反映在你的数字中。

当您扩大 MKL/*gemm 的线程数时,请考虑

  • 内存/共享缓存带宽可能成为瓶颈,限制可扩展性
  • Turbo模式在提高利用率的同时会有效降低核心频率。即使您以标称频率运行,这也适用:在 Haswell-EP 处理器上,AVX 指令将施加较低的“AVX 基本频率” - 但当使用较少核心/可用热空间时,处理器允许超过该频率,并且通常甚至是这样更多的时间较短。如果您想要完全中性的结果,则必须使用 AVX 基本频率,即 1.9 GHz。有记录here http://www.intel.com/content/dam/www/public/us/en/documents/specification-updates/xeon-e5-v3-spec-update.pdf,并在中解释一张照片 http://images.anandtech.com/doci/8423/Hep_AVX_turbo.png.

我认为没有一种真正简单的方法来衡量您的应用程序如何受到不良调度的影响。你可以暴露这个perf trace -e sched:sched_switch并且有一些软件 http://tu-dresden.de/zih/perf/可视化这一点,但这将伴随着很高的学习曲线。再说一遍 - 对于并行性能分析,无论如何您都应该固定线程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多核机器上单精度数组与双精度数组的矩阵乘法的性能下降 的相关文章

  • python中basestring和types.StringType之间的区别?

    有什么区别 isinstance foo types StringType and isinstance foo basestring 对于Python2 basestring是两者的基类str and unicode while type
  • 如何查找或安装适用于 Python 的主题 tkinter ttk

    过去 3 个月我一直在制作一个机器人 仅用代码就可以完美运行 现在我的下一个目标是为它制作一个 GUI 但是我发现了一些障碍 主要的一个是能够看起来不像一个 30 年前的程序 我使用的是 Windows 7 我仅使用 Python 3 3
  • Airflow 1.9 - 无法将日志写入 s3

    我在 aws 的 kubernetes 中运行气流 1 9 我希望将日志发送到 s3 因为气流容器本身的寿命并不长 我已经阅读了描述该过程的各种线程和文档 但我仍然无法让它工作 首先是一个测试 向我证明 s3 配置和权限是有效的 这是在我们
  • 在骨架图像中查找线 OpenCV python

    我有以下图片 我想找到一些线来进行一些计算 平均长度等 我尝试使用HoughLinesP 但它找不到线 我能怎么做 这是我的代码 sk skeleton mask rows cols sk shape imgOut np zeros row
  • C# 获取数据表中所有重复行的计数

    我通过运行存储过程来填充数据集 并且从数据集中填充数据表 DataSet RawDataSet DataAccessHelper RunProcedure storedprocedureName this will just return
  • 使用 Python 将连续日期分组在一起

    Given dates datetime 2014 10 11 datetime 2014 10 1 datetime 2014 10 2 datetime 2014 10 3 datetime 2014 10 5 datetime 201
  • 使用另一个数据帧在数据帧中创建子列

    我对 python 和 pandas 很陌生 在这里 我有一个以下数据框 did features offset word JAPE feature manual feature 0 200 0 aa 200 200 0 200 11 bf
  • 对于 C# Express 用户来说,有哪些好的工具可以识别可能重复的代码? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 也可以看看 有什么工具可以检查重复的 VB NET 代码吗 https stackoverflow c
  • 为什么 __dict__ 和 __weakref__ 类从未在 Python 中重新定义?

    类创建似乎从来没有re 定义 dict and weakref class属性 即 如果它们已经存在于超类的字典中 则它们不会添加到其子类的字典中 但始终re 定义 doc and module class属性 为什么 gt gt gt c
  • SQLAPI++ 的免费替代品? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 是否有任何免费 也许是开源 的替代品SQLAPI http www sqlapi com 这个库看起来
  • Xamarin Forms Binding - 访问父属性

    我无法访问页面的 ViewModel 属性以便将其绑定到 IsVisible 属性 如果我不设置 BindingContext 我只能绑定它 有没有办法可以在设置 BindingContext 的同时访问页面的 viewmodel root
  • C++ 指针引用混淆

    struct leaf int data leaf l leaf r struct leaf p void tree findparent int n int found leaf parent 这是 BST 的一段代码 我想问一下 为什么
  • 如何在亚马逊 EC2 上调试 python 网站?

    我是网络开发新手 这可能是一个愚蠢的问题 但我找不到可以帮助我的确切答案或教程 我工作的公司的网站 用 python django 构建 托管在亚马逊 EC2 上 我想知道从哪里开始调试这个生产站点并检查存储在那里的日志和数据库 我有帐户信
  • 在 C# 的 WebAPI 中的 ApiController 上使用“传输编码:分块”提供数据

    我需要服务分块传输使用编码数据API控制器 因为我无权访问HttpContext or the Http请求 我有点不知道在哪里写入响应以及在哪里刷新它 设置如下 public class MyController ApiControlle
  • 如何高效计算连续数的数字积?

    我正在尝试计算数字序列中每个数字的数字乘积 例如 21 22 23 98 99 将会 2 4 6 72 81 为了降低复杂性 我只会考虑 连续的数字 http simple wikipedia org wiki Consecutive in
  • 如何从 Windows Phone 7 模拟器获取数据

    我有一个 WP7 的单元测试框架 它在手机上运行 结果相当难以阅读 因此我将它们写入 XDocument 我的问题是 如何才能将这个 XML 文件从手机上移到我的桌面上 以便我可以实际分析结果 到目前为止 我所做的是将 Debugger B
  • 从后面的代码添加外部 css 文件

    我有一个 CSS 文件 例如 SomeStyle css 我是否可以将此样式表文档从其代码隐藏应用到 aspx 页面 您可以将文字控件添加到标头控件中 Page Header Controls Add new System Web UI L
  • 如果找不到指定的图像文件,显示默认图像的最佳方式?

    我有一个普通的电子商务应用程序 我将 ITEM IMAGE NAME 存储在数据库中 有时经理会拼错图像名称 为了避免 丢失图像 IE 中的红色 X 每次显示产品列表时 我都会检查服务器中是否有与该产品相关的图像 如果该文件不存在 我会将其
  • 如何在 C# 中获取 CMD/控制台编码

    我需要指定正确的代码页来使用 zip 库打包文件 正如我所见 我需要指定控制台编码 在我的例子中为 866 C Users User gt mode Status for device CON Lines 300 Columns 130 K
  • 如何为有时异步的操作创建和实现接口

    假设我有数百个类 它们使用 计算 方法实现公共接口 一些类将执行异步 例如读取文件 而实现相同接口的其他类将执行同步代码 例如将两个数字相加 为了维护和性能 对此进行编码的好方法是什么 到目前为止我读到的帖子总是建议将异步 等待方法冒泡给调

随机推荐