前言
本文译自Intel® Developer Zone上文章Determining Root Cause of Segmentation Faults SIGSEGV or SIGBUS errors。linux
正文
问题:当我运行由Intel Fortran编译器编译的代码时,在Linux平台获得’sigsegv’ 错误提示(或Mac OS X平台sigbus提示)。这份代码在<先去编译器/平台>上运行多年没出 问题。这是Intel编译器一个bug吗?shell
运行环境:linux 或 Mac OS X编程
根本缘由:有许多可能缘由。段错误(segmentation fault)(Mac OS X下是bus error)是一个有多种缘由的通用错误。下面咱们描述潜在的缘由并给出建议以避 免段错误。数组
可能缘由 #1 Fortran指定栈空间耗尽:解决方法 -heap-arrays编译选项bash
Intel Fortran编译器使用栈空间分配许多数组数据的临时或中间副本。app
非OpenMP和非自动并行应用:若是你的程序未使用OpenMP或 Auto-parallelization(-parallel编译开关)且编译器版本是Linux v9.1.037(或全部Mac OS 编译器),那么能够尝试 -heap-arrays
编译选项。OpenMP或 Auto-parallelization用户如用低于v9.1.0137的Linux 编译器请阅读可能缘由 #2关于不限制栈大小的提示。函数
1
|
-heap-arrays |
若是这个解决了sigsegv或bus error错误的话,能够不用往下读了。你可能想读pdf附件 学习关于临时数组什么时候何处被建立内容。改变一点代码能够避免一些临时数组,从而减小 对临时副本的需求(改善性能)。同时,-heap-arrays
编译器选项有一个可选参数[size] 来指定大于[size]的数组分配到堆(heap)中的阈值大小,单位为Kbytes,其它小于等于[size]的 分配到堆栈中。例如:工具
1
|
-heap-arrays 10 |
将全部大于10Kbytes的自动和临时数组放入堆中。性能
可能缘由 #2 堆栈空间耗尽。 解决方法:增长OpenMP应用或其它应用的堆栈大小学习
首先尝试增长Linux和Mac OS X的shell堆栈限制。然而,该选项可能对OpenMP或自动并行 代码的数据共享产生没法预料的影响。所以,建议OpenMP和自动并行用户不用 -heap-arrays
,转而尝试关掉shell堆栈大小的限制。
Linux, bash: ulimit -s unlimited
Linux, csh/tcsh: unlimit stacksize
你能够用如下方法检查你的堆栈大小限制,并找到你的shell环境的stack size
限制。
bash: ulimit -a
csh: limit
注意:若是你在一个批处理子系统下运行程序,可能须要将上面命令加入到我的启动配置 文件中(~/.bashrc、~/.profile或~/.cshrc)
对于Mac OS X系统,shell堆栈大小有一个硬上限。对于大多系统,
bash: ulimit -s 65532
即设置堆栈限制为64MB。
一个可替代方法是使用一个连接选项增长执行者默认shell堆栈大小,方法记录在此:/en-us/articles/intel-fortran-compiler-increased-stack-usage-of-80-or-higher-compilers-causes-segmentation-fault
从新运行你的程序,若是这个方法解决此问题了,能够不用往下读了。若是你的应用仍然 产生sigsegv或bus error,继续读吧。
可能缘由 #2 主要的:因为堆或通用内存耗尽致使堆栈耗尽
在进程内存映射中,堆和堆栈相互向对方增加。若是他们碰撞了,这也能引发一个关于堆分配或下一个堆栈分配的段错误。
也可能应用耗尽全部物理内存+swap缓冲区。记住,对于64位应用,虚拟内存其实是 unlimited。然而,事实上可消费的内存大小有一个上限,物理ram+swap空间(典型的是 物理内存大小的2倍)。能够经过 free
命令得到该信息。物理内存也能够经过cat /proc/meminfo
中’MemTotal’项和’SwapTotal’项看到。系统自己也须要一些空间,因此 经验法则是尽量保持应用的内存占用(memory footprint)在MemTotal的80%左右而且不要超过 MemTotal+SwapTotal。
使用-g -traceback
编译连接来定位代码终止的地方。
可能缘由 #3 因为用户代码错误致使堆栈溢出
有许多用户代码错误可能引发堆栈溢出并致使运行时sigsegv或bus error。因为段错误可 能发生在与堆栈最初溢出地方不相关的程序后面部分,从而致使错误很难发现。
第一步尝试隔离代码错误发生的地方。经过产生一个执行’traceback’来获得。使用ifort 驱动和下面这些选项编译、连接:
1
|
-g -traceback |
当代码出错,一般会获得关于错误发生时的调用堆栈的报告。若是没有获得一个堆栈 traceback,确保在编译和连接时都用到了-g
而且确保编译时使用了-traceback
。有 这样的状况:当程序在内核空间时段错误发生,所以没有用户堆栈用于trace back。 Intel正致力于在将来版本中改善这点。
trace back报告从下往上读。找到最上面的子程序或函数并经过行号来隔离出哪条指令引 起错误的。检查这条语句的用户代码错误。若是没有明显用户错误,继续往下。
可能缘由 #4 数组越界. 解决方法:-check bounds
-check bounds
编译选项提供数组访问和字符串表达式运行时检查保证索引在数组边界 内。这个检查有助于找到索引超出数组申明大小的状况。这个选项对性能影响很大,影响 程度取决于应用中多少数组访问被执行。同时,-check bounds
数组越界检查不对最外 维指定为*的虚参数组或上下维都为1的数组执行。为开启边界检查,用下面选项编译:
1
|
-check bounds -g |
并运行程序。检查在运行时执行,而不是在编译时执行。若是这样能发现错误,停下来。 没有,继续往下读。
可能缘由 #5 把函数看成子程序调用(calling)或把子程序看成函数调用(invoking)
当用户作相似于这样的事,用户代码错误:
1 2 3 4 5 6 7 8 9 10 |
--- main program --- ... call ThisIsIllegal( some_arguments ) ... --- end main program --- --- ThisIsIllegal --- integer function ThisIsIllegal( some_arguments ) ... --- end ThisIsIllegal --- |
上面例子中,主程序以子程序调用方式调用ThisIsIllegal,然而ThisIsIllegal申明是函 数。这会引发堆栈溢出。为测试这些状况,尝试使用编译选项
1
|
-fp-stack-check -g -traceback |
用这些选项编译并运行。若是堆栈因为相似上面的缘由崩溃,代码将推出并给出一个 stack trace。
能够用一个编译时检查来检查程序接口:
1
|
-gen-interfaces -warn interfaces |
编译时检查将为你的程序产生INTERFACE块。-warn
接口随后将使用这些编译器产生的接口并 检查程序调用确保参数和接口在被调用者和调用者之间匹配。注意这个检查只对Fortran 源代码起做用。对混编程序不检查接口。
可能缘由 #6 传递非连续数组部分引发的大的临时数组. 解决方法:使用 -check arg_temp_created 侦测并经过包含显式接口和assumed shaped 数组代码修复
考虑这个例子:
1 2 3 4 5 6 7 |
--- main program --- real(8) :: f(1800,3600,1) external sub ... call sub( f(1:900,:,:) ) ... --- end main program --- |
子程序‘sub’在单独的编译源文件中:
1 2 3 4 5 |
--- external subroutine "sub" --- subroutine sub( f ) real(8) :: f(900,3600,1) ... --- end subroutine "sub" --- |
这种状况下,‘sub’指望一个连续数组,大小为90036001。然而,调用传递一个内存中 非连续的数组。这种状况下,编译器将在调用时产生一个临时数组复制数组”f”非连续块 元素使之成为连续数组,这这是”sub“所期待的。除非指定 -heap-arrays
,不然这个临时数组分配在堆栈中。
为了检测代码里是否发生这样的状况,用下面选项编译:
1
|
-check arg_temp_created |
并运行程序。当临时参数被建立,将输出信息。为了解决这个问题,建立一个显式接口, 并在”sub“使用一个assumed shaped数组将移除临时数组的须要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- main --- real(8) :: f(1800,3600,1) interface subroutine sub(f) real(8) :: f(:,:,:) end subroutine sub end interface ... call sub( f(1:900,:,:) ) ... --- end main program --- --- "sub" --- subroutine sub( f ) real(8) :: f(:,:,:) ... end subroutine sub |
记住,虽然这样避免了临时数组,编译器知道”sub”内数组”f”多是非连续的。所以,一 些使用”f”的语句的优化可能关闭,进而影响性能。
不属于以上状况:解决方法-进一步深刻分析
99%的sigsegv或bus error错误缘由是上面列举的状况。然而,也有其余可能状况致使段 错误。
若是你的应用连接外部库,确保库和编译器兼容。外部库是否用Intel编译器编译的?如 果是,是否主要版本一致-即库用Intel Fortran v9.1编译的,但你的应用用Intel Fortran v10.x或v11.x编译的? Intel只保证主版本内兼容(9,10,11就是主版本例子)
若是外部库来自于软件销售商或工具:该销售商是否明确Intel编译器兼容,若是确认, 他们用哪一个版本验证他们的库?你应该只是用销售商验证过的Intel编译器版本。
当全部都失败了…
提交给用户论坛吧!