1.9. IVF 并行计算与 MKL

IVF 2011 提供了多种并行计算方案,从自动并行到显式 OpenMP 和 Intel MKL。

1.9.1. Intel MKL 数学库

Intel Math Kernel Library (MKL) 提供高度优化的数学函数。

1.9.1.1. 配置 MKL

项目属性 → Intel Performance Libraries → Use MKL → Parallel

或链接器设置:
附加依赖项: mkl_intel_lp64.lib mkl_core.lib mkl_intel_thread.lib
           libiomp5md.lib

1.9.1.2. BLAS 矩阵运算

program mkl_demo
    use blas95              ! Fortran 95 BLAS 接口
    implicit none
    integer, parameter :: n = 3
    real(8) :: a(n, n), b(n, n), c(n, n)

    ! 初始化矩阵
    a = reshape([1.0_dp, 2.0_dp, 3.0_dp, &
                 4.0_dp, 5.0_dp, 6.0_dp, &
                 7.0_dp, 8.0_dp, 9.0_dp], [n, n])
    b = reshape([9.0_dp, 8.0_dp, 7.0_dp, &
                 6.0_dp, 5.0_dp, 4.0_dp, &
                 3.0_dp, 2.0_dp, 1.0_dp], [n, n])

    ! 矩阵乘积 C = A * B
    call gemm(a, b, c)
    write(*, *) "矩阵乘法结果:"
    write(*, '(3F8.2)') c

    ! 线性方程组 Ax = b
    real(8) :: b_vec(n), x(n)
    b_vec = [1.0_dp, 2.0_dp, 3.0_dp]
    call gesv(a, b_vec, x)          ! 求解 Ax = b
    write(*, *) "解:", x
end program

1.9.1.3. LAPACK 线性代数

program lapack_demo
    implicit none
    integer, parameter :: n = 3
    real(8) :: a(n, n), w(n)         ! w = 特征值
    integer :: info

    a = reshape([2.0_dp, -1.0_dp, 0.0_dp, &
                -1.0_dp, 2.0_dp, -1.0_dp, &
                 0.0_dp, -1.0_dp, 2.0_dp], [n, n])

    ! 计算特征值 (dsyev = double symmetric eigenvalue)
    call dsyev("N", "U", n, a, n, w, work, lwork, info)

    if (info == 0) then
        write(*, *) "特征值:", w
    end if
end program

小技巧

MKL 的向量数学库(VML)可以直接应用于数组:

include 'mkl_vml.f90'
real(8) :: x(1000), y(1000)
x = [ (i * 0.01_dp, i = 1, 1000) ]
call vdsin(1000, x, y)  ! 批量 sin 计算

1.9.2. OpenMP 并行

IVF 2011 完全支持 OpenMP 3.0。

1.9.2.1. 启用 OpenMP

项目属性 → C/C++ → 语言 → OpenMP 支持 → 是 (/Qopenmp)
或命令行: ifort /Qopenmp program.f90

1.9.2.2. 并行 do 循环

program omp_demo
    use omp_lib            ! OpenMP 库模块
    implicit none
    integer, parameter :: n = 10000000
    real(8), allocatable :: a(:), b(:), c(:)
    real(8) :: start, end_time
    integer :: i

    allocate(a(n), b(n), c(n))
    a = 1.0_dp; b = 2.0_dp

    ! 计时
    start = omp_get_wtime()

    ! 并行 for 循环
    !$omp parallel do
    do i = 1, n
        c(i) = a(i) * b(i) + sin(a(i)) * cos(b(i))
    end do
    !$omp end parallel do

    end_time = omp_get_wtime()
    write(*, '(A, F8.4, A)') "并行耗时: ", end_time - start, " 秒"

end program

重要

OpenMP 指令格式: - !$omp:通常指令 - !$omp parallel do:并行 do 循环 - !$omp end parallel do:结束(可选,由缩进推断)

1.9.2.3. 规约操作

real(8) :: sum, a(n)
a = [ (i * 1.0_dp, i = 1, n) ]

sum = 0.0_dp
!$omp parallel do reduction(+:sum)
do i = 1, n
    sum = sum + a(i)
end do
!$omp end parallel do
write(*, *) "总和:", sum

1.9.2.4. 并行区域

integer :: thread_id, num_threads

!$omp parallel private(thread_id) shared(num_threads)
    thread_id = omp_get_thread_num()
    !$omp master
        num_threads = omp_get_num_threads()
        write(*, *) "线程数:", num_threads
    !$omp end master
    write(*, '(A, I3, A, I3)') "线程 ", thread_id, " / ", num_threads - 1
!$omp end parallel

1.9.3. do concurrent(Fortran 2008+)

! 编译器自动并行化提示
integer :: i, j
real :: mat(1000, 1000)

do concurrent (i = 1:1000)
    do concurrent (j = 1:1000)
        mat(i, j) = sin(i * 0.01_dp) * cos(j * 0.01_dp)
    end do
end do

! do concurrent 要求:
! 1. 循环间无依赖
! 2. 不能有 I/O 语句
! 3. 不能有 return/exit
! 4. 不能修改循环外变量(除赋值外)

备注

do concurrent 是**语义提示**,编译器可能自动并行, 但不保证一定并行。与 OpenMP 结合使用效果更佳。

1.9.4. 自动并行化(IVF 编译器)

IVF 可以自动分析循环并生成并行代码:

ifort /Qparallel program.f90       ! 自动并行
ifort /Qpar-report:2 program.f90   ! 显示并行报告

小心

自动并行依赖编译器的分析结果,复杂循环可能无法自动并行。 对于关键循环,建议使用显式的 OpenMP 指令。

1.9.5. 并行性能建议

建议

说明

大粒度并行

每个线程分配足够大的工作量,减少调度开销

避免假共享

不同线程的变量不要在同一缓存行上

减少同步

用 reduction 代替显式临界区

数据本地化

使用 private 声明临时变量,避免竞争

绑定线程

设置 KMP_AFFINITY 环境变量绑定物理核心

均衡负载

用 schedule(dynamic) 处理不均匀循环

嵌套并行谨慎

外层已并行时,内层不再开新线程

预热

在大循环前执行小计算让线程池激活

1.9.6. 环境变量

set OMP_NUM_THREADS=4        ! 设置线程数
set KMP_AFFINITY=granularity=fine,compact,1,0  ! 线程亲核性
set MKL_NUM_THREADS=4        ! MKL 线程数
set KMP_BLOCKTIME=200        ! 线程等待时间(毫秒)