Пожалуйста, помогите мне понять, как я могу улучшить последовательную, неформатированную пропускную способность ввода / вывода с помощью (G) Fortran, особенно при работе с твердотельными накопителями NVMe.
Я написал небольшую тестовую программу, см. Нижнюю часть этого поста. Для этого нужно открыть один или несколько файлов параллельно (OpenMP) и записать в него массив случайных чисел. Затем он сбрасывает системные кэши (требуется root, в противном случае тест чтения, скорее всего, будет читать из памяти), открывает файлы и читает из них. Время измеряется в настенном времени (при попытке включить только времена, связанные с вводом / выводом), а показатели производительности приведены в миБ / с Программа зацикливается до тех пор, пока не прервется.
Аппаратное обеспечение, которое я использую для тестирования, - это твердотельный накопитель Samsung 970 Evo Plus 1 ТБ, подключенный через 2 линии P CIe 3.0. Таким образом, теоретически он должен иметь возможность последовательного чтения и записи ~ 1500 МБ / с. Предварительное тестирование с "dd if = / dev / zero of =. / Testfile bs = 1G count = 1 oflag = direct" дает ~ 750 МБ / с. Не слишком хорошо, но все же лучше, чем то, что я получаю с Gfortran. И в зависимости от того, кого вы спрашиваете, dd не должен использоваться для тестирования в любом случае. Это просто для того, чтобы убедиться, что аппаратное обеспечение теоретически способно на большее.
Результаты с моим кодом имеют тенденцию улучшаться при большем размере файла, но даже при 1 ГБ он достигает примерно 200 МБ / с записи, 420 МБ / с читать. Использование большего количества потоков (например, 4) немного увеличивает скорость записи, но только до 270 МБ / с. Я позаботился о том, чтобы эталонные тесты были короткими, и у SSD было время расслабиться между тестами.
У меня сложилось впечатление, что должна быть возможность насыщать 2 P CIe 3,0 дорожек пропускная способность, даже с одним потоком. По крайней мере, при использовании неформатированного ввода-вывода. Код, похоже, не ограничен процессором, top показывает менее 50% использования на одном ядре, если я перенес выделение и инициализацию поля «значения» из l oop. Что все еще не сулит ничего хорошего для общей производительности, учитывая, что я хотел бы видеть числа, которые как минимум в 5 раз выше.
Я также пытался использовать access = stream для операторов open, но безрезультатно.
Так в чем проблема?
Мой код неверный / неоптимизированный? Мои ожидания слишком высоки?
Используемая платформа:
Opensuse Leap 15.1, ядро 4.12.14-lp151.28.36-default
2x AMD Epy c 7551, Supermicro H11DSI, Samsung 970 Evo Plus 1 ТБ (2xP CIe 3.0)
g cc версия 8.2.1, опции компилятора: -ffree-line-length-none -O3 -ffast-math -funroll-loops -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