GFortran неформатированная пропускная способность ввода / вывода на NVMe SSD - PullRequest
1 голос
/ 02 февраля 2020

Пожалуйста, помогите мне понять, как я могу улучшить последовательную, неформатированную пропускную способность ввода / вывода с помощью (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

1 Ответ

2 голосов
/ 07 февраля 2020

Ошибка в вашем коде состоит в том, что при вычислении mib_written вы забыли принять во внимание размер переменной real(sp) (4 байта). Таким образом, ваши результаты в 4 раза ниже. Например, вычислите его как

mib_written = filesize * nthreads

Некоторые незначительные гниды, некоторые задают c для GFortran:

  • Не повторяйте вызов random_seed, особенно не из каждого потока. Если вы хотите вызвать его, вызовите его один раз в начале программы.
  • Вы можете использовать open(newunit=tunit, ...), чтобы позволить среде выполнения компилятора выделять уникальный номер модуля для каждого файла.
  • Если вам нужны «стандартные» 64-битные целочисленные / с плавающей точкой, вы можете использовать переменные int64 и real64 из модуля iso_fortran_env intrinsi c.
  • Для тестирования с большими файлами, вам нужно сделать alloc_size вида int64.
  • Использовать стандартный get_command_argument intrinsi c вместо нестандартного getarg.
  • access='stream' немного быстрее, чем по умолчанию (последовательный), так как нет необходимости обрабатывать маркеры длины записи.

Ваша тестовая программа с этими исправлениями (и модуль parameters, встроенный в основную программу) ниже:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...