Io NVMe SSD上的GFortran非格式化I/O吞吐量
请帮助我了解如何使用(G)Fortran提高顺序、无格式I/O吞吐量,尤其是在使用NVMe SSD时 我写了一个小测试程序,见本文底部。这样做的目的是并行打开一个或多个文件(OpenMP),并将随机数数组写入其中。然后它刷新系统缓存(需要root,否则读测试很可能从内存中读取)打开文件并从中读取。时间以墙时间度量(尝试只包括与I/O相关的时间),性能数字以MiB/s表示。程序循环直到中止 我用于测试的硬件是三星970 Evo Plus 1TB SSD,通过2个PCIe 3.0通道连接。因此,从理论上讲,它应该能够进行~1500MiB/s的顺序读写。 预先使用“ddif=/dev/zero of=./testfile bs=1G count=1 of lag=direct”进行测试,结果约为750MB/s。不算太好,但还是比我用Gfortran得到的要好。取决于你问谁,dd无论如何都不应该用于基准测试。这只是为了确保硬件在理论上能够实现更多的功能 我的代码的结果往往随着文件大小的增大而变得更好,但即使使用1GiB,其最大写入速度也在200Mb/s左右,读取速度也在420MiB/s左右。使用更多线程(例如4个)会稍微提高写入速度,但仅会提高到270MiB/s左右。 我确保基准测试的运行时间很短,并在测试之间给SSD时间放松 我的印象是,即使只有一个线程,也应该可以使2个PCIe 3.0通道的带宽饱和。至少在使用未格式化I/O时。 代码似乎不受CPU限制,如果我将“values”字段的分配和初始化移出循环,top在单个内核上显示的使用率不到50%。考虑到我希望看到的数字至少要高出5倍,这对整体表现来说仍然不是一个好兆头。Io NVMe SSD上的GFortran非格式化I/O吞吐量,io,fortran,performance-testing,gfortran,nvme,Io,Fortran,Performance Testing,Gfortran,Nvme,请帮助我了解如何使用(G)Fortran提高顺序、无格式I/O吞吐量,尤其是在使用NVMe SSD时 我写了一个小测试程序,见本文底部。这样做的目的是并行打开一个或多个文件(OpenMP),并将随机数数组写入其中。然后它刷新系统缓存(需要root,否则读测试很可能从内存中读取)打开文件并从中读取。时间以墙时间度量(尝试只包括与I/O相关的时间),性能数字以MiB/s表示。程序循环直到中止 我用于测试的硬件是三星970 Evo Plus 1TB SSD,通过2个PCIe 3.0通道连接。因此,从理
我还尝试对open语句使用access=stream,但没有效果 那么问题出在哪里呢?
我的代码是否错误/未优化?我的期望太高了吗 使用的平台:
Opensuse Leap 15.1,内核4.12.14-lp151.28.36-default
2x AMD Epyc 7551、超微型H11DSI、三星970 Evo Plus 1TB(2xPCIe 3.0)
gcc版本8.2.1,编译器选项:-ffree line length none-O3-ffast math-funroll循环-flto
MODULE types
implicit none
save
INTEGER, PARAMETER :: I8B = SELECTED_INT_KIND(18)
INTEGER, PARAMETER :: I4B = SELECTED_INT_KIND(9)
INTEGER, PARAMETER :: SP = KIND(1.0)
INTEGER, PARAMETER :: DP = KIND(1.0d0)
END MODULE types
MODULE parameters
use types
implicit none
save
INTEGER(I4B) :: filesize ! file size in MiB
INTEGER(I4B) :: nthreads ! number of threads for parallel ececution
INTEGER(I4B) :: alloc_size ! size of the allocated data field
END MODULE parameters
PROGRAM iometer
use types
use parameters
use omp_lib
implicit none
CHARACTER(LEN=100) :: directory_char, filesize_char, nthreads_char
CHARACTER(LEN=40) :: dummy_char1
CHARACTER(LEN=110) :: filename
CHARACTER(LEN=10) :: filenumber
INTEGER(I4B) :: thread, tunit, n
INTEGER(I8B) :: counti, countf, count_rate
REAL(DP) :: telapsed_read, telapsed_write, mib_written, write_speed, mib_read, read_speed
REAL(SP), DIMENSION(:), ALLOCATABLE :: values
call system_clock(counti,count_rate)
call getarg(1,directory_char)
dummy_char1 = ' directory to test:'
write(*,'(A40,A)') dummy_char1, trim(adjustl(directory_char))
call getarg(2,filesize_char)
dummy_char1 = ' file size (MiB):'
read(filesize_char,*) filesize
write(*,'(A40,I12)') dummy_char1, filesize
call getarg(3,nthreads_char)
dummy_char1 = ' number of parallel threads:'
read(nthreads_char,*) nthreads
write(*,'(A40,I12)') dummy_char1, nthreads
alloc_size = filesize * 262144
dummy_char1 = ' allocation size:'
write(*,'(A40,I12)') dummy_char1, alloc_size
mib_written = real(alloc_size,kind=dp) * real(nthreads,kind=dp) / 1048576.0_dp
mib_read = mib_written
CALL OMP_SET_NUM_THREADS(nthreads)
do while(.true.)
!$OMP PARALLEL default(shared) private(thread, filename, filenumber, values, tunit)
thread = omp_get_thread_num()
write(filenumber,'(I0.10)') thread
filename = trim(adjustl(directory_char)) // '/' // trim(adjustl(filenumber)) // '.temp'
allocate(values(alloc_size))
call random_seed()
call RANDOM_NUMBER(values)
tunit = thread + 100
!$OMP BARRIER
!$OMP MASTER
call system_clock(counti)
!$OMP END MASTER
!$OMP BARRIER
open(unit=tunit, file=trim(adjustl(filename)), status='replace', action='write', form='unformatted')
write(tunit) values
close(unit=tunit)
!$OMP BARRIER
!$OMP MASTER
call system_clock(countf)
telapsed_write = real(countf-counti,kind=dp)/real(count_rate,kind=dp)
write_speed = mib_written/telapsed_write
!write(*,*) 'write speed (MiB/s): ', write_speed
call execute_command_line ('echo 3 > /proc/sys/vm/drop_caches', wait=.true.)
call system_clock(counti)
!$OMP END MASTER
!$OMP BARRIER
open(unit=tunit, file=trim(adjustl(filename)), status='old', action='read', form='unformatted')
read(tunit) values
close(unit=tunit)
!$OMP BARRIER
!$OMP MASTER
call system_clock(countf)
telapsed_read = real(countf-counti,kind=dp)/real(count_rate,kind=dp)
read_speed = mib_read/telapsed_read
write(*,'(A29,2F10.3)') ' write / read speed (MiB/s): ', write_speed, read_speed
!$OMP END MASTER
!$OMP BARRIER
deallocate(values)
!$OMP END PARALLEL
call sleep(1)
end do
END PROGRAM iometer
代码中的错误在于,在计算
mib_write
时,您忘记了考虑real(sp)
变量(4字节)的大小。因此,您的结果是一个因子4太低。例如,将其计算为
mib_written = filesize * nthreads
一些次要NIT,一些特定于GFortran:
- 不要重复调用
,尤其是不要从每个线程调用。如果要调用它,请在程序开始时调用一次随机种子
- 您可以使用
让编译器运行时为每个文件分配唯一的单元号open(newunit=tunit,…)
- 如果您想要“标准”64位整数/浮点类型,可以使用
内部模块中的变量iso\u fortran\u env
和int64
real64
- 对于使用较大文件进行测试,需要将
设置为alloc\u size
类型int64
- 使用标准的
内部参数,而不是非标准的get_命令_参数
getarg
比默认值(顺序)稍快,因为不需要处理记录长度标记access='stream'
参数模块):
基准是棘手的。你到底想测量什么?它看起来像是您在测量打开、写入或读取以及关闭文件。所以,你不是在测量原始的书写或阅读速度。此外,通过gfortran和OpenMP,您可以衡量pthread库的性能。我对基准测试的写/读部分更感兴趣。但以我有限的经验,我无法确定在关闭设备之前,数据是否已写入磁盘。因此,我将开放式和封闭式语句包括在测量间隔中。随着文件越来越大,打开/关闭语句本身的开销应该最小化。谢谢你的有用评论,也谢谢你抓住了我愚蠢的错误。因子4是我将字段大小直接用作输入参数而不是文件大小时留下的。还有什么我能做的吗?就像修补buffersize或任何其他我从未听说过的参数一样?@MechEng:当你在大块执行IO时,GFortran运行库会绕过缓冲区,直接读取/写入数据。GFortran没有选择使用O_DIRECT绕过操作系统缓冲,但这是一种特殊用途。顺便说一句,我认为每次迭代调用随机数对于实际测试是必要的。出于同样的原因,像FIO这样的工具也有“重新填充缓冲区”这样的选项。否则,SSD控制器会开始压缩数据,导致类似于数据由零组成的速度。@MechEng:当然,这样可能更安全。我在自己的测试中没有注意到任何影响,但我只有一个SATA SSD,也许更高级的控制器有更奇特的重复数据消除方案。
PROGRAM iometer
use iso_fortran_env
use omp_lib
implicit none
CHARACTER(LEN=100) :: directory_char, filesize_char, nthreads_char
CHARACTER(LEN=40) :: dummy_char1
CHARACTER(LEN=110) :: filename
CHARACTER(LEN=10) :: filenumber
INTEGER :: thread, tunit
INTEGER(int64) :: counti, countf, count_rate
REAL(real64) :: telapsed_read, telapsed_write, mib_written, write_speed, mib_read, read_speed
REAL, DIMENSION(:), ALLOCATABLE :: values
INTEGER :: filesize ! file size in MiB
INTEGER :: nthreads ! number of threads for parallel ececution
INTEGER(int64) :: alloc_size ! size of the allocated data field
call system_clock(counti,count_rate)
call get_command_argument(1, directory_char)
dummy_char1 = ' directory to test:'
write(*,'(A40,A)') dummy_char1, trim(adjustl(directory_char))
call get_command_argument(2, filesize_char)
dummy_char1 = ' file size (MiB):'
read(filesize_char,*) filesize
write(*,'(A40,I12)') dummy_char1, filesize
call get_command_argument(3, nthreads_char)
dummy_char1 = ' number of parallel threads:'
read(nthreads_char,*) nthreads
write(*,'(A40,I12)') dummy_char1, nthreads
alloc_size = filesize * 262144_int64
dummy_char1 = ' allocation size:'
write(*,'(A40,I12)') dummy_char1, alloc_size
mib_written = filesize * nthreads
dummy_char1 = ' MiB written:'
write(*, '(A40,g0)') dummy_char1, mib_written
mib_read = mib_written
CALL OMP_SET_NUM_THREADS(nthreads)
!$OMP PARALLEL default(shared) private(thread, filename, filenumber, values, tunit)
do while (.true.)
thread = omp_get_thread_num()
write(filenumber,'(I0.10)') thread
filename = trim(adjustl(directory_char)) // '/' // trim(adjustl(filenumber)) // '.temp'
if (.not. allocated(values)) then
allocate(values(alloc_size))
call RANDOM_NUMBER(values)
end if
open(newunit=tunit, file=filename, status='replace', action='write', form='unformatted', access='stream')
!$omp barrier
!$omp master
call system_clock(counti)
!$omp end master
!$omp barrier
write(tunit) values
close(unit=tunit)
!$omp barrier
!$omp master
call system_clock(countf)
telapsed_write = real(countf - counti, kind=real64)/real(count_rate, kind=real64)
write_speed = mib_written/telapsed_write
call execute_command_line ('echo 3 > /proc/sys/vm/drop_caches', wait=.true.)
!$OMP END MASTER
open(newunit=tunit, file=trim(adjustl(filename)), status='old', action='read', form='unformatted', access='stream')
!$omp barrier
!$omp master
call system_clock(counti)
!$omp end master
!$omp barrier
read(tunit) values
close(unit=tunit)
!$omp barrier
!$omp master
call system_clock(countf)
telapsed_read = real(countf - counti, kind=real64)/real(count_rate, kind=real64)
read_speed = mib_read/telapsed_read
write(*,'(A29,2F10.3)') ' write / read speed (MiB/s): ', write_speed, read_speed
!$OMP END MASTER
call sleep(1)
end do
!$OMP END PARALLEL
END PROGRAM iometer