Fortran 隐含的 do 不起作用
Fortran implied do doesn't work
我正在使用 Fortran90,但对它不是很熟悉。
在代码的某些地方,我想在 t0 和 tf 之间创建一个由 n 个线性等距点组成的数组,所以我尝试了这个:
t = t0+(/(i,i=0,n-1)/)*(tf-t0)/(n-1)
与以下相同:
do while(i<=n)
t(i) = i-1
i=i+1
end do
t = t*(tf-t0)/(n-1)+t0
但是,如果 n 太大(n=2000001 或更大),第一个选项将不起作用。我收到错误:
forrtl: 严重 (170): 程序异常 - 堆栈溢出
这是为什么?我该怎么做才能使隐含的 do 循环适用于 n 大?
一些 Fortran 处理器使用硬件堆栈来存储在计算表达式期间创建的临时变量。当表达式涉及大型对象(例如大型数组)时,临时对象所需的存储空间可能会超过硬件堆栈可用的总存储空间,并且堆栈会溢出。
在第一种情况下,您的 Fortran 处理器可能正在创建一个大的临时文件来保存数组构造函数的结果。
不同的处理器在不需要临时变量的情况下评估表达式的能力不同 - 例如,您可能会发现某些处理器在第二个示例中也存在堆栈溢出问题 - 分配给的变量 t 也在右侧表达式可能足以让某些 Fortran 处理器要求将整个右侧求值为临时值。
您的选择是(可能是组合):
使用操作系统或编译器开关为硬件堆栈预留更多存储空间。
使用编译器开关或类似命令来指示处理器在堆而不是堆栈上创建临时文件。
以不同的方式重述整个操作,这样编译器就不再创建临时对象(正如您已经探索过的那样)。这可能包括在 do 循环中将操作重铸为一个元素一个元素的操作,而不是直接在数组上使用操作。
这些方法是特定于平台的。根据错误消息,我猜您正在 Windows 上使用 Intel Fortran(或祖先)- 如果是这样,请参阅 /heap-arrays
编译器选项和 Windows 链接器选项 /stack
。如果您在 linux 上,请参阅 ulimit
命令。
像这样的简单循环没有错:
do i=1,n
t(i)=t0+(i-1)*(tf-t0)/(n-1)
enddo
当人们尝试完全使用通常隐藏缺点的数组表达式时,我总是感到惊讶。
使用隐式循环,在堆栈中创建一个临时数组,而使用显式循环,每个元素单独处理,不创建临时数组。默认情况下,堆栈大小是有限的,因此数组足够大时会出现错误。
我将补充 IanH 提出的第三点,为了帮助决定是否重构代码以避免临时数组,值得考虑将 -Warray-temporaries 与 gfortran 和 -check arg_temp_created 或者,更好的是,-check all,用 ifort。
我正在使用 Fortran90,但对它不是很熟悉。
在代码的某些地方,我想在 t0 和 tf 之间创建一个由 n 个线性等距点组成的数组,所以我尝试了这个:
t = t0+(/(i,i=0,n-1)/)*(tf-t0)/(n-1)
与以下相同:
do while(i<=n)
t(i) = i-1
i=i+1
end do
t = t*(tf-t0)/(n-1)+t0
但是,如果 n 太大(n=2000001 或更大),第一个选项将不起作用。我收到错误:
forrtl: 严重 (170): 程序异常 - 堆栈溢出
这是为什么?我该怎么做才能使隐含的 do 循环适用于 n 大?
一些 Fortran 处理器使用硬件堆栈来存储在计算表达式期间创建的临时变量。当表达式涉及大型对象(例如大型数组)时,临时对象所需的存储空间可能会超过硬件堆栈可用的总存储空间,并且堆栈会溢出。
在第一种情况下,您的 Fortran 处理器可能正在创建一个大的临时文件来保存数组构造函数的结果。
不同的处理器在不需要临时变量的情况下评估表达式的能力不同 - 例如,您可能会发现某些处理器在第二个示例中也存在堆栈溢出问题 - 分配给的变量 t 也在右侧表达式可能足以让某些 Fortran 处理器要求将整个右侧求值为临时值。
您的选择是(可能是组合):
使用操作系统或编译器开关为硬件堆栈预留更多存储空间。
使用编译器开关或类似命令来指示处理器在堆而不是堆栈上创建临时文件。
以不同的方式重述整个操作,这样编译器就不再创建临时对象(正如您已经探索过的那样)。这可能包括在 do 循环中将操作重铸为一个元素一个元素的操作,而不是直接在数组上使用操作。
这些方法是特定于平台的。根据错误消息,我猜您正在 Windows 上使用 Intel Fortran(或祖先)- 如果是这样,请参阅 /heap-arrays
编译器选项和 Windows 链接器选项 /stack
。如果您在 linux 上,请参阅 ulimit
命令。
像这样的简单循环没有错:
do i=1,n
t(i)=t0+(i-1)*(tf-t0)/(n-1)
enddo
当人们尝试完全使用通常隐藏缺点的数组表达式时,我总是感到惊讶。
使用隐式循环,在堆栈中创建一个临时数组,而使用显式循环,每个元素单独处理,不创建临时数组。默认情况下,堆栈大小是有限的,因此数组足够大时会出现错误。
我将补充 IanH 提出的第三点,为了帮助决定是否重构代码以避免临时数组,值得考虑将 -Warray-temporaries 与 gfortran 和 -check arg_temp_created 或者,更好的是,-check all,用 ifort。