OpenMPI的底层实现:

我们知道,OpenMPI应用起来还是比较简单的,但是如果让我自己来实现一个MPI的并行计算,你会怎么设计呢?————这就涉及到比较底层的东西了。

回想起我们最简单的代码,通过comm_rank来决定做不同的事情,那么这个comm_rank是怎么得到的呢?

源代码从哪里看起?在百度,谷歌都没有找到关于源码剖析的一些资料,只能先找找头文件

mpi.h搜索找到了在ompi/include/mpi.h.in中的一个文件,查找一下最简单的函数 MPI_Comm_size 和 MPI_Init 函数,
作为总头文件,所有函数声明似乎都在这里了,那么函数定义怎么找呢....如果有vs那样的F12就爽歪歪了。

面对的应用场景: 我要找OpenMpi文件目录下含有字符串 "MPI_Init"的文本文件,有什么工具吗?
幸好,windows已经提供了这样一个cmd命令findstr————参考 http://www.netingcn.com/window-findstr-command.html
搜出来了,发现有一大堆文件满足上述条件啊,突然间看到C文件里面有很多熟悉的函数名

那么应该是一个函数对应一个文件了,去找找看!

有comm_rank.c 还有 init.c:
进入init.c发现了入门第一个函数: MPI_Init(int *argc, char ***argv)
由于是第一个接触到的函数,所以要认真仔细地一行一行去学习:

  1. #include "ompi_config.h"
  2.  
  3. #include <stdlib.h>
  4.  
  5. #include "opal/util/show_help.h"
  6. #include "ompi/mpi/c/bindings.h"
  7. #include "ompi/communicator/communicator.h"
  8. #include "ompi/errhandler/errhandler.h"
  9. #include "ompi/constants.h"
  10.  
  11. #if OMPI_BUILD_MPI_PROFILING
  12. #if OPAL_HAVE_WEAK_SYMBOLS
  13. #pragma weak MPI_Init = PMPI_Init
  14. #endif
  15. #define MPI_Init PMPI_Init
  16. #endif
  17.  
  18. static const char FUNC_NAME[] = "MPI_Init";
  19.  
  20. int MPI_Init(int *argc, char ***argv)
  21. {
  22. int err;
  23. int provided;
  24. char *env;
  25. int required = MPI_THREAD_SINGLE;
  26.  
  27. /* check for environment overrides for required thread level. If
  28. there is, check to see that it is a valid/supported thread level.
  29. If not, default to MPI_THREAD_MULTIPLE. */
  30.  
  31. if (NULL != (env = getenv("OMPI_MPI_THREAD_LEVEL"))) {
  32. required = atoi(env);
  33. if (required < MPI_THREAD_SINGLE || required > MPI_THREAD_MULTIPLE) {
  34. required = MPI_THREAD_MULTIPLE;
  35. }
  36. }
  37.  
  38. /* Call the back-end initialization function (we need to put as
  39. little in this function as possible so that if it's profiled, we
  40. don't lose anything) */
  41.  
  42. if (NULL != argc && NULL != argv) {
  43. err = ompi_mpi_init(*argc, *argv, required, &provided);
  44. } else {
  45. err = ompi_mpi_init(0, NULL, required, &provided);
  46. }
  47.  
  48. /* Since we don't have a communicator to invoke an errorhandler on
  49. here, don't use the fancy-schmancy ERRHANDLER macros; they're
  50. really designed for real communicator objects. Just use the
  51. back-end function directly. */
  52.  
  53. if (MPI_SUCCESS != err) {
  54. return ompi_errhandler_invoke(NULL, NULL,
  55. OMPI_ERRHANDLER_TYPE_COMM,
  56. err <
  57. 0 ? ompi_errcode_get_mpi_code(err) :
  58. err, FUNC_NAME);
  59. }
  60.  
  61. OPAL_CR_INIT_LIBRARY();
  62.  
  63. return MPI_SUCCESS;
  64. }

 首先看到的代码块是:

  1. int required = MPI_THREAD_SINGLE;
  2.  
  3. /* check for environment overrides for required thread level. If
  4. there is, check to see that it is a valid/supported thread level.
  5. If not, default to MPI_THREAD_MULTIPLE. */
  6.  
  7. if (NULL != (env = getenv("OMPI_MPI_THREAD_LEVEL"))) {
  8. required = atoi(env);
  9. if (required < MPI_THREAD_SINGLE || required > MPI_THREAD_MULTIPLE) {
  10. required = MPI_THREAD_MULTIPLE;
  11. }
  12. }

  

这明显和多线程设置相关,
查阅资料,参考http://web.mit.edu/course/13/13.715/build/mpich2-1.0.6p1/www/www3/MPI_Init_thread.html 或者 某软 的MPI资料,
发现这个required的变量还是比较好懂的。就是表示进程中只有主线程。由于本人现在接触的都是单线程,所以可以这里可以跳过。

接下来的这个代码块是:

  1. /* Call the back-end initialization function (we need to put as
  2. little in this function as possible so that if it's profiled, we
  3. don't lose anything) */
  4.  
  5. if (NULL != argc && NULL != argv) {
  6. err = ompi_mpi_init(*argc, *argv, required, &provided);
  7. } else {
  8. err = ompi_mpi_init(0, NULL, required, &provided);
  9. }

  

就是调用back-end初始化函数 ompi_mpi_init ,这个函数在 ompi_mpi_init.c定义了,但是很不幸,这个函数有600多行。
因此,本人决定,先暂时跳过这个函数。这个函数内部进行了许多重要的初始化操作,可能需要后续一点点刨出来。

  1. /* Since we don't have a communicator to invoke an errorhandler on
  2. here, don't use the fancy-schmancy ERRHANDLER macros; they're
  3. really designed for real communicator objects. Just use the
  4. back-end function directly. */
  5.  
  6. if (MPI_SUCCESS != err) {
  7. return ompi_errhandler_invoke(NULL, NULL,
  8. OMPI_ERRHANDLER_TYPE_COMM,
  9. err <
  10. 0 ? ompi_errcode_get_mpi_code(err) :
  11. err, FUNC_NAME);
  12. }

如果初始化函数返回的不是 MPI_SUCCESS, 就返回错误码,那这个函数在哪里呢?
在errhandler.h可以找到函数声明,返回和参数中一致的errcode————找了很久,最后用微软的黑科技findstr /S命令,终于找了对应的文件:
openmpi-3.0.1\ompi\errhandler\errhandler_invoke.c
我想了想,决定还是把这个函数的代码贴出来,虽然有60多行,无非也就是switch的几个case分支而已:

  1. int ompi_errhandler_invoke(ompi_errhandler_t *errhandler, void *mpi_object,
  2. int object_type, int err_code, const char *message)
  3. {
  4. MPI_Fint fortran_handle, fortran_err_code = OMPI_INT_2_FINT(err_code);
  5. ompi_communicator_t *comm;
  6. ompi_win_t *win;
  7. ompi_file_t *file;
  8.  
  9. /* If we got no errorhandler, then just invoke errors_abort */
  10. if (NULL == errhandler) {
  11. ompi_mpi_errors_are_fatal_comm_handler(NULL, NULL, message); //-------------注意到我们传入了NULL,所以Init失败后进入了这里
  12. return err_code;
  13. }
  14.  
  15. /* Figure out what kind of errhandler it is, figure out if it's
  16. fortran or C, and then invoke it */
  17.  
  18. switch (object_type) {
  19. case OMPI_ERRHANDLER_TYPE_COMM: // ompi/errhandler/errhandler.h:
  20. comm = (ompi_communicator_t *) mpi_object; // Enum used to describe what kind MPI object an error handler is used for
  21. switch (errhandler->eh_lang) {
  22. case OMPI_ERRHANDLER_LANG_C: // C语言
  23. errhandler->eh_comm_fn(&comm, &err_code, message, NULL);
  24. break;
  25.  
  26. case OMPI_ERRHANDLER_LANG_CXX:
  27. errhandler->eh_cxx_dispatch_fn(&comm, &err_code, message,
  28. (ompi_errhandler_generic_handler_fn_t *)errhandler->eh_comm_fn);
  29. break;
  30.  
  31. case OMPI_ERRHANDLER_LANG_FORTRAN:
  32. fortran_handle = OMPI_INT_2_FINT(comm->c_f_to_c_index);
  33. errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code);
  34. err_code = OMPI_FINT_2_INT(fortran_err_code);
  35. break;
  36. }
  37. break;
  38.  
  39. case OMPI_ERRHANDLER_TYPE_WIN:
  40. win = (ompi_win_t *) mpi_object;
  41. switch (errhandler->eh_lang) {
  42. case OMPI_ERRHANDLER_LANG_C:
  43. errhandler->eh_win_fn(&win, &err_code, message, NULL);
  44. break;
  45.  
  46. case OMPI_ERRHANDLER_LANG_CXX:
  47. errhandler->eh_cxx_dispatch_fn(&win, &err_code, message,
  48. (ompi_errhandler_generic_handler_fn_t *)errhandler->eh_win_fn);
  49. break;
  50.  
  51. case OMPI_ERRHANDLER_LANG_FORTRAN:
  52. fortran_handle = OMPI_INT_2_FINT(win->w_f_to_c_index);
  53. errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code);
  54. err_code = OMPI_FINT_2_INT(fortran_err_code);
  55. break;
  56. }
  57. break;
  58.  
  59. case OMPI_ERRHANDLER_TYPE_FILE:
  60. file = (ompi_file_t *) mpi_object;
  61. switch (errhandler->eh_lang) {
  62. case OMPI_ERRHANDLER_LANG_C:
  63. errhandler->eh_file_fn(&file, &err_code, message, NULL);
  64. break;
  65.  
  66. case OMPI_ERRHANDLER_LANG_CXX:
  67. errhandler->eh_cxx_dispatch_fn(&file, &err_code, message,
  68. (ompi_errhandler_generic_handler_fn_t *)errhandler->eh_file_fn);
  69. break;
  70.  
  71. case OMPI_ERRHANDLER_LANG_FORTRAN:
  72. fortran_handle = OMPI_INT_2_FINT(file->f_f_to_c_index);
  73. errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code);
  74. err_code = OMPI_FINT_2_INT(fortran_err_code);
  75. break;
  76. }
  77. break;
  78. }
  79.  
  80. /* All done */
  81. return err_code;
  82. }

可以看到,其实这里60多行的代码,就只是掉进了一个函数: ompi_errhandler_t 类的 eh_comm_fn 函数:

  1. struct ompi_errhandler_t {
  2. opal_object_t super;
  3.  
  4. char eh_name[MPI_MAX_OBJECT_NAME];
  5. /* Type of MPI object that this handler is for */
  6.  
  7. ompi_errhandler_type_t eh_mpi_object_type;
  8.  
  9. /* What language was the error handler created in */
  10. ompi_errhandler_lang_t eh_lang;
  11.  
  12. /* Function pointers. Note that we *have* to have all 4 types
  13. (vs., for example, a union) because the predefined errhandlers
  14. can be invoked on any MPI object type, so we need callbacks for
  15. all of three. */
  16. MPI_Comm_errhandler_function *eh_comm_fn;
  17. ompi_file_errhandler_fn *eh_file_fn;
  18. MPI_Win_errhandler_function *eh_win_fn;
  19. ompi_errhandler_fortran_handler_fn_t *eh_fort_fn;
  20.  
  21. /* Have separate callback for C++ errhandlers. This pointer is
  22. initialized to NULL and will be set explicitly by the C++
  23. bindings for Create_errhandler. This function is invoked
  24. when eh_lang==OMPI_ERRHANDLER_LANG_CXX so that the user's
  25. callback function can be invoked with the right language
  26. semantics. */
  27. ompi_errhandler_cxx_dispatch_fn_t *eh_cxx_dispatch_fn;
  28.  
  29. /* index in Fortran <-> C translation array */
  30. int eh_f_to_c_index;
  31. };

而这个 ompi_errhandler_t 对象的创建接口,来自于:

  1. OMPI_DECLSPEC ompi_errhandler_t *ompi_errhandler_create(ompi_errhandler_type_t object_type,
  2. ompi_errhandler_generic_handler_fn_t *func, //这就是 ompi_errhandler_t 结构体的 eh_comm_fn 函数
  3. ompi_errhandler_lang_t language);

因为在 MPI_Init 函数中调用时传入的是NULL(MPI的这些东西初始化失败,自然也不能传入一个ompi_errhandler_t结构了)

这篇文章就到此了,留下了3个分支没有继续跳进去深入了解:

1. ompi_mpi_init因为代码快过长,放在这里不合适

2. init失败,按照正常流程就会调用的: ompi_mpi_errors_are_fatal_comm_handler函数

3. ompi_errhandler_t 这个结构体包含的信息,这跟异常处理有关,对我们弄清楚以后并行计算实际运行可能发生的错误会有帮助

OpenMPI源码剖析1:MPI_Init初探的更多相关文章

  1. Spring源码剖析2:初探Spring IOC核心流程

    本文转载自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutor ...

  2. OpenMPI源码剖析:网络通信原理(二) 如何选择网络协议?

    因为比较常用的是 TCP 协议,所以在 opal/mca/btl/tcp/btl_tcp.h 头文件中找到对应的 struct mca_btl_tcp_component_t { mca_btl_ba ...

  3. OpenMPI源码剖析4:rte.h 头文件的说明信息

    上一篇文章中说道,我们在 rte.h 中发现了有价值的说明: 我们一块一块来分析,首先看到第一块,关于 Process name Object: * (a) Process name objects ...

  4. OpenMPI源码剖析:网络通信原理(一)

    MPI中的网络通信的原理,需要解决以下几个问题: 1. MPI使用什么网络协议进行通信? 2.中央数据库是存储在哪一台机器上? 3.集群中如果有一台机器挂掉了是否会影响其他机器? 参考: https: ...

  5. OpenMPI源码剖析3:try_kill_peers 和 ompi_rte_abort 函数

    接着上一篇的疑问,我们说道,会执行 try_kill_peers 函数,它的函数定义在 ompi_mpi_abort.c 下: // 这里注释也说到了,主要是杀死在同一个communicator的进程 ...

  6. OpenMPI源码剖析2:ompi_mpi_errors_are_fatal_comm_handler函数

    上一篇文章说道,初始化失败会有一个函数调用: ompi_mpi_errors_are_fatal_comm_handler(NULL, NULL, message); 所以这里简单地进入了 ompi_ ...

  7. Spring源码剖析1:初探Spring IOC核心流程

    本文大致地介绍了IOC容器的初始化过程,只列出了比较重要的过程和代码,可以从中看出IOC容器执行的大致流程. 接下来的文章会更加深入剖析Bean容器如何解析xml,注册和初始化bean,以及如何获取b ...

  8. Python源码剖析|百度网盘免费下载|Python新手入门|Python新手学习资料

    百度网盘免费下载:Python源码剖析|新手免费领取下载 提取码:g78z 目录  · · · · · · 第0章 Python源码剖析——编译Python0.1 Python总体架构0.2 Pyth ...

  9. Python源码剖析——02虚拟机

    <Python源码剖析>笔记 第七章:编译结果 1.大概过程 运行一个Python程序会经历以下几个步骤: 由解释器对源文件(.py)进行编译,得到字节码(.pyc文件) 然后由虚拟机按照 ...

随机推荐

  1. 多线程CSerialPort类的多串口通信实现

    多线程CSerialPort类的多串口通信实现  工作了之后才发现,之前在学校里真是狭隘封闭.坐井观天,拿之前发表的论文来说,工作后接触到了底层的串口.网口开发,对线程(也叫任务).操作系统时间片轮流 ...

  2. iOS 杂笔-26(苹果禁用热更新)

    iOS 杂笔-26(苹果禁用热更新) 苹果爸爸禁用热更新小伙伴们有什么想说的吗? 苹果爸爸禁用热更新小伙伴们有什么想说的吗? 苹果爸爸禁用热更新小伙伴们有什么想说的吗?

  3. 关于Date的冷门知识记录

    最近在做项目的时候,用到了Date.toLocaleString来处理当前日期.在这之前,我都是通过get*等方式来获取数据进行拼接.无意间,发现了toLocaleString方法.遂想写一篇文章来记 ...

  4. Ubuntu16 安装Anaconda3+tensorflow cpu版

    打开火狐浏览器,下载anaconda安装包,网址:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?C=M&O=D 下载完成,到Do ...

  5. 三层架构,Struts2,SpringMVC实现原理图

    三层架构,Struts2,SpringMVC实现原理图 三层架构实现原理 Struts2实现原理 SpringMVC实现原理

  6. shell定时统计Nginx下access.log的PV并发送给API保存到数据库

    1,统计PV和IP 统计当天的PV(Page View) cat access.log | sed -n /`date "+%d\/%b\/%Y"`/p |wc -l 统计某一天的 ...

  7. HTML+css3 图片放大效果

    <div class="enlarge"> <img src="xx" alt="图片"/> </div> ...

  8. JS 时间格式化,模拟PHP date,时间格式化封装函数

    Date.prototype.Format = function (fmt) { var o = { "Y": this.getFullYear(), "m": ...

  9. idea自动生成testNG.xml

    下载插件  Create TestNG Xml  安装插件 重启后就可以生成testNG.xml,打开xml,ctrl + ALT + L,格式化一下

  10. python学习笔记~INI、REG文件读取函数(自动修复)

    引入configparser,直接read整个INI文件,再调用get即可.但需要注意的是,如果INI文件本身不太规范,就会报各种错,而这又常常不可避免的.本文自定义函数通过try...except. ...