2013-03-26 7 views
5

Я пишу код линейной алгебры (в Fortran 2003, но это была бы такая же проблема в Fortran 90 или C), которая требует нескольких векторов работы для выполнения вычислений. Моя идея для решения этой задачи - сделать работу array w(:,:), который является частным для модуля линейной алгебры, то есть «скрытого глобального», как определено в this discussion, почему истинные глобальные переменные ужасны.скрыты глобалы плохой практики программирования?

Я представляю, что это как большая проблема для решения на доске, и для каждой части проблемы вы выбираете область доски, чтобы решить ее.

В соответствии с этой аналогией я мог бы также иметь кучу маленьких досок: определить тип данных work_array и передать их решателям по мере необходимости. (PETSc эффективно использует этот подход через другой уровень абстракции: solver - это тип данных, который включает в себя некоторые указатели на методы, используемые как и несколько рабочих векторов.) Когда есть вложенные вызовы от одного решателя к другому, это получает клещ сложный, поэтому мне нравится первый способ лучше. Это также не требует такого же неправильного направления.

Любые мысли о том, какой подход способствует лучшей практике программирования?

EDIT: Я также не думаю, что это будет проблемой, когда я начну использовать OpenMP, который я уже сделал в старом воплощении этого кода. Каждый поток получает доступ к своей части неизвестных, а не к другим темам после установки проблемы. Тем не менее, проблемы параллелизма, вероятно, являются хорошей причиной не использовать статические переменные в целом.

Если мне приходится динамически выделять пространство для массивов царапин каждый раз, когда я звоню в решатель, который часто не будет налагать много накладных расходов?

+2

Что делать, если несколько потоков пытались получить к нему доступ сразу? – Mysticial

+1

Какая польза? Если рабочее пространство мало, сделайте его автоматическим. Если он огромный, malloc и бесплатно по мере необходимости. –

ответ

6

Если вы выполняете какие-либо нетривиальные вычисления в рабочем пространстве, стоимость malloc и free будет зависеть от стоимости вычислений, выполняемых в выделенном пространстве, а служебные данные будут приблизительно равны нулю. Единственный раз, когда избегание распределения имеет смысл, поскольку стратегия оптимизации заключается в том, что объем работы, выполняемой в буфере, настолько мал, что время для получения буфера может доминировать (или, по крайней мере, может не быть во власти другого термина). Основная ситуация, когда это может произойти, - это построение строк.

С другой стороны, глобальное состояние имеет много затрат:

  1. Это исключает многопоточного использования.
  2. Если состояние должно сохраняться между несколькими вызовами, оно не позволяет использовать библиотеку (библиотека не может использоваться более чем одной частью программы за раз, поскольку они могут сжимать работу друг друга).
  3. Это исключает использование рекурсивного/повторного участия.
  4. Он использует память всякий раз, когда библиотека связана, даже если функции никогда не вызываются.
  5. Даже если вы прилагаете усилия для решения некоторых из этих проблем, это серьезный код запах и, следовательно, стоимость в человека, т.е.он будет тратить время на следующего человека, который читает ваш код, пытаясь определить, действительно ли глобальное состояние вводит какие-либо ошибки или ограничения использования кода.
+0

Хорошо, вы меня убедили. – korrok

5

Самая большая опасность «скрытых глобалов» (в мире C они называются static) появляется, когда вы пишете параллельные программы. Как только вы отправляетесь на многопоточность, одной доски недостаточно, каждый поток нуждается в ее собственной. Для подобных ситуаций динамическое распределение более уместно. Если вы не беспокоитесь о многопоточности, наличие «скрытой глобальной» переменной в модуле отлично.

1

Что касается стоимости размещения: вы можете иметь производный тип, содержащий все рабочие массивы (в вашем случае массив w(:,:)). Вы можете иметь один инициализацию вызова, который распределяет их до нужного размера, а затем передать полученный тип с выделенными массивами в этом решатель так часто, как это возможно, что-то в следующем духе:

module test 
    implicit none 

    type :: buffer 
     integer, allocatable :: work(:,:) 
    end buffer 

contains 

    subroutine init(mybuffer, whatever_else_you_need_for_determinig_allocation_size) 
     type(buffer), intent(out) :: mybuffer 

     allocate(mybuffer%work(dim1, dim2)) 
    end subroutine init 

    subroutine solver(mybuffer, whatever_else_you_need_for_the_solver) 
     type(buffer), intent(inout) :: mybuffer 

      ! You can access mybuffer%work here as it is allocated 

     end subroutine solver 

end module test 

Но как уже указывалось, что стоимость распределения будет обычно незначительной в отношении затрат, которые вы тратите на решение.

Смежные вопросы