Файл: А. Б. Шнейвайс основы программирования.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 06.12.2023

Просмотров: 322

Скачиваний: 6

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

COMP:=gfortran
OPT:=-c -Wall -O0
PATTERN:=*.f90
SOURCE:=$(wildcard $(PATTERN))
OBJ:=$(patsubst %.f90,%.o, $(SOURCE))
MAIN:=probtask
$(MAIN)
: $(OBJ)
[
TAB
]
$(COMP) $^ -o $@
%.mod %.o : %.f90
# Обратим внимание, что цели %.mod
[
TAB
]
$(COMP) $(OPT) $<
# %.o достигаются одной командой!
$(MAIN).o
:
$(MAIN).hdr myunit.mod
RESULT : $(MAIN) $(MAIN).par
[
TAB
]
./$^
[
TAB
]
cat RESULT
CLEAR :
[
TAB
] rm -f *.o *.mod $(MAIN)
make CLEAR
# Протестируем этот Makefile:
rm -f *.o *.mod probtask # Уничтожили *.o *.mod и исполнимый файл.
make
# После запуска make gfortran -c -Wall -O0 myunit.f90
# всё работает, т.е.
gfortran -c -Wall -O0 probtask.f90
# пока модификация ничего gfortran probtask.o myunit.o -o probtask # не испортила.
make
# При повторном запуске make make: ‘probtask’ не требует обновления.
# реакция утилиты адекватная.
touch myunit.f90
# Моделируем модификацию модуля!!!
make
# Пока gfortran -c -Wall -O0 myunit.f90
#
всё хорошо!
gfortran probtask.o myunit.o -o probtask rm *.mod
# Случайно удалили myunit.mod make
# make продолжает работу:
gfortran -c -Wall -O0 myunit.f90
# находит правило достижения gfortran -c -Wall -O0 probtask.f90
# цели и корректно выполняет gfortran probtask.o myunit.o -o probtask # её.
Кажется, что решение make: ‘probtask’ не требует обновления.
# найдено. Однако, есть нюанс:
touch myunit.f90
# Изменим myunit.f90 и make
# запустим make ещё раз.
gfortran -c -Wall -O0 myunit.f90
#
Исполнимый файл получен.
gfortran probtask.o myunit.o -o probtask #
Снова запустим make make
# И что же видим? Вместо gfortran -c -Wall -O0 myunit.f90
# сообщения о ненужности gfortran probtask.o myunit.o -o probtask # обновления получаем новую
# перекомпиляцию! Непорядок!!!
Эффект обнаружен студентами астрономического отделения, а его при- чина и способ устранения, излагаемые ниже, сообщены Никифоровым
И.И., сотрудником кафедры небесной механики и звёздной астрономии
НИАИ имени В.В.Соболева.
354

11.3.5
Выяснение причины сбоя 2-ой коррекции
Для уяснения причины происходящего отследим времена модификации файлов myunit.o и myunit.mod. Повторим предыдущую схему, но при этом выведем упомянутые времёна. После make CLEAR и ls -l имеем:
make CLEAR
rm -f *.o *.mod probtask make gfortran -c -Wall -O0 myunit.f90
gfortran -c -Wall -O0 probtask.f90
gfortran probtask.o myunit.o -o probtask make make: ‘probtask’ не требует обновления.
# Всё, как и ожидалось.
ls -l
-rw-r--r--. 1 aw aw
196 фев 20 12:16 myunit.f90 # Видно, что время
-rw-rw-r--. 1 aw aw
1239 фев 20 12:24 myunit.mod # модификации и
-rw-rw-r--. 1 aw aw
1416 фев 20 12:24 myunit.o
# myunit.mod и myunit.o
-rwxrwxr-x. 1 aw aw 13698 фев 20 12:24 probtask
# одно и то же
Изменим время модификации модуля (иммитация подправки модуля),
повторим вызов make и отследим времена:
touch myunit.f90
make gfortran -c -Wall -O0 myunit.f90
#<---= Это не вызывает возражения gfortran probtask.o myunit.o -o probtask
#
--"--"--"--"--"--"--"--"-- ls -l
#
Время модификации
-rw-r--r--. 1 aw aw
196 фев 20 12:25 myunit.f90
-rw-rw-r--. 1 aw aw
1239 фев 20 12:24 myunit.mod
#
<---=
НЕ ИЗМЕНИЛОСЬ!
-rw-rw-r--. 1 aw aw
1416 фев 20 12:25 myunit.o
#
<---=
ИЗМЕНИЛОСЬ.
-rwxrwxr-x. 1 aw aw 13698 фев 20 12:25 probtask make gfortran -c -Wall -O0 myunit.f90
gfortran probtask.o myunit.o -o probtask ls -l
-rw-rw-r--. 1 aw aw
1239 фев 20 12:24 myunit.mod
#
<---=
НЕ ИЗМЕНИЛОСЬ!
-rw-rw-r--. 1 aw aw
1416 фев 20 12:26 myunit.o
#
<---=
ИЗМЕНИЛОСЬ.

Почему модуль перекомпилируется, если его модификация более НЕ ПРОВОДИЛАСЬ?
Причина: разные времена модификации myunit.mod и myunit.o.
355


11.3.6 3-я попытка коррекции make-файла
Дополним команду компиляции шаблонного правила
%.o %.mod : %.f90
командой touch $@.
COMP:=gfortran
OPT:=-c -Wall -O0
PATTERN:=*.f90
SOURCE:=$(wildcard $(PATTERN))
OBJ:=$(patsubst %.f90,%.o, $(SOURCE))
MAIN:=probtask
$(MAIN)
: $(OBJ)
[
TAB
]
$(COMP) $^ -o $@
%.mod %.o : %.f90
[
TAB
]
$(COMP) $(OPT) $<
[
TAB
]
touch $@
# <--=
Коррекция !!!
$(MAIN).o
:
$(MAIN).hdr myunit.mod
RESULT : $(MAIN) $(MAIN).par
[
TAB
]
./$^
[
TAB
]
cat RESULT
CLEAR :
[
TAB
] rm -f *.o *.mod $(MAIN)
make CLEAR
# Результат 3-ей коррекции:
rm -f *.o *.mod probtask make gfortran -c -Wall -O0 myunit.f90
touch myunit.mod gfortran -c -Wall -O0 probtask.f90
touch probtask.o gfortran probtask.o myunit.o -o probtask make
# Теперь реакция утилиты make make: ‘probtask’ не требует обновления.
# соответствует ожидаемой.
ls -l
-rw-r--r--. 1 aw aw
196 фев 20 12:25 myunit.f90
-rw-rw-r--. 1 aw aw
1239 фев 20 13:04 myunit.mod
-rw-rw-r--. 1 aw aw
1416 фев 20 13:04 myunit.o touch myunit.f90
$ make gfortran -c -Wall -O0 myunit.f90
touch myunit.mod gfortran -c -Wall -O0 probtask.f90
touch probtask.o gfortran probtask.o myunit.o -o probtask make make: ‘probtask’ не требует обновления.
356

11.4
Информация к размышлению
1. Старый ФОРТРАН не имел единицы компиляции module, исполь- зуя лишь program, function и subroutine. Сборка программного продукта прекрасно обеспечивалась утилитой make.
2. Современный ФОРТРАН использует (причём с несомненной выго- дой) и программные единицы вида module, предназначенные для доступа к объектам, объявленных в них, тех программных единиц,
в которых есть операторы подключения соответствующих module.
Утилита make заслуженно востребована и при сборке программ с единицами компиляции вида module.
3. При компиляции внешней процедуры вида (function, subroutine или program) образуется один объектный файл (с расширением .o).
4. При компиляции module образуются два файла:
(a) первый с расширением .mod. Через его посредство компилятор узнаёт имена объектов модуля, которые может импортировать программная единица, использующая модуль.
(b) второй с расширением .o обычный объектный файл, который используется при сборке исполнимого файла.
5. Модификация процедур function и subroutine может быть двоя- кой: модификация тела или модификация заголовка.
6. Первая никак не затрагивает тело программы, вызывающей про- цедуру. Поэтому от перекомпиляции процедуры никак не зависит объектный файл программы, вызывающей процедуру.
7. Вторая же (модификация заголовка) естественно затронет тело вы- зывающей программы через оператор вызова процедуры, так как изменится интерфейс последней. В этом случае придётся переком- пилировать и программу, вызывающую процедуру.
357

8. Аналогичная ситуация возникает и при использовании модулей.
Если модульный интерфейс прежний (т.е. изменения не касались интерфейсов процедур, типов или объектов), то после перекомпи- ляции модуля не нужно перекомпилировать процедуры, использу- ющие этот модуль (ведь информация, хранимая в файле с расши- рением .mod, — прежняя).
Объектный же файл модуля нужно пересоздать, так как изменения в исходнике тела модульной процедуры проводились. В этом случае время создания файла с расширением .mod при очередном запуске make-файла может оказаться гораздо более ранним по сравнению со временем модификации соответствующего объектного.
9. Условия необходимости модификации файла с расширением mod вырабатываются компилятором так, чтобы по мере возможности не перекомпилировать исходники, зависимость которых от модуля всё- таки позволяет эти исходники не перекомпилировать. Соответству- ющий пример уже приводился выше (см. пункт 3 раздела 11.3.4).
Выводы
• При написании make-файлов ФОРТРАН-программ, использующих единицы компиляции вида module, следует учитывать, что времена модификации файлов с расширениями *.mod и *.o для одного и того же модуля могут быть различны. Иногда это приводит (при повторных запусках make-файла) к излишним перекомпиляциям и самого модуля, и единиц компиляции, подсоединяющих его.
• Если последний факт неважен, то команду touch $@ к шаблонному правилу можно не добавлять.
• Однако, если хотим (для подстраховки), чтобы после любой мо- дификации модуля наряду с его компиляцией всегда происходила и перекомпиляция процедур или модулей, использующих его, то ко- манду touсh $@ целесообразно добавить, обновляя время модифи- кации соответствующего файла с расширением .mod, а значит и,
требуя тем самым перекомпиляцию программных единиц, подсоеди- няющих данный модуль через оператор use.
358


11.4.1

1   ...   15   16   17   18   19   20   21   22   23

О чем узнали из приложения N II ?
1. Простые переменные, автоматические переменные, шаблоны утили- ты make и ее встроенные функции позволяют добиться наиболее краткой и универсальной формы записи make-файла, размер ко- торого не зависит от количества исходных и объектных модулей используемых программных единиц.
2. Утилита make допускает две формы оператора присваивания = и
:=. Первая обеспечивает заполнение переменной в момент ее ис- пользования; вторая – в момент работы оператора присваивания.
3. Имя простой make-переменной придумываем сами. Например, спи- сок имён исходных файлов с независимыми программными едини- цами удобно поместить в переменную с именем SOURCE, а список соответствующих объектных – в переменную OBJECT, имя ком- пилятора – в переменную COMP и т.д.
4. Автоматические переменные утилиты make:
• $∧ – содержит список зависимостей конкретного правила;
• $@ – содержит имя цели конкретного правила;
• $< – содержит для конкретного правила первую зависимость из всех указанных для него зависимостей.
5. Шаблон – это символ, которому сопоставляется целое семейство раз- личных имен. Например, в среде оболочки удобно пользоваться шаб- лоном, обозначаемым символом *. Так выражение *.o толкуется оболочкой как любое имя файла с расширением o.
6. Наряду с общеупотребительными шаблонами утилита make допус- кает использование шаблона, обозначаемого значком %, который нацелен на замену набора символов в правой части заголовка пра- вила текстом, обозначенным значком % в левой части, например:
%.o : %.f90 359

7. wildcard и patsubst – встроенные функции утилиты make:
а) wildcard – генерирует список имён файлов в соответствие с указанным ей шаблонам:
PATTERN:=*.f90
SOURCE :=$(wildcard $(PATTERN))
б) patsubst – нацелена на замену слов в исходной строке, удовле- творяющих заданному шаблону, новым вариантом слова, напри- мер:
OBJ
:=$(patsubst %.f90, %.o, $(SOURCE))
Если в переменной $(SOURCE) встретятся имена файлов с расширением .f90, то patsubst в качестве результата получит строку, состоящую из имён соответствующих объектных файлов согласно требуемому шаблону %.o.
8. При компиляции программной единицы module генерируются два файла: один объектный файл с расширением .o, другой вспомога- тельный с расширением .mod. Первый необходим для сборки ис- полнимого файла. Второй — для компиляции программных единиц,
подключающих модуль.
9. При использовании программных единиц module в правилах make- файла описывающих цели, зависящие от подключаемых модулей,
следует указывать зависимость от соответствующих файлов с рас- ширением .mod
360


12
Приложение III. О подсчете времени.
12.1
Утилита time.
В UNIX-среде есть утилита time , которая позволяет выяснить время,
затраченное на выполнение команды, указанной в качестве аргумента этой утилиты. Пусть хотим узнать:
$ time date
// "Сколько времени работает команда date?"
Срд Апр 22 14:55:51 MSD 2009
// "По мнению" команды time:
real
0m0.003s
//
1) астрономическое время работы равно 0,003 секунды;
user
0m0.001s
//
2) время центрального процессора, затраченное на
//
выполнение пользовательской части программы;
sys
0m0.002s
//
3) время нужное для выполнения системных функций.
Реальное время работы команды date команда time оценила в 0,003
секунды. Аналогично узнается время работы любой программы. Пусть,
например, есть программа program tsttime1; implicit none ! Пустое тело цикла повторяется n раз integer i, n /1000000000/
! (n задается на этапе компиляции).
write(*,*) ’ n=’,n
! Временные затраты tsttime1 по команде do i=1,n; enddo
! time при n=1000000000, 2 000000000
end
! таковы:
$ gfortran tsttime1.f
#
Видно, что в данном случае общее время
$ time ./a.out
#
равно к сумме времен n=
1000000000
n=
2000000000
вызова и исполнения.
real
0m2.488s real
0m4.896s user
0m2.484s user
0m4.892s sys
0m0.004s sys
0m0.004s program tsttime2; implicit none
! Если n вводить с экрана,
integer i, n
! то результат существенно ИНОЙ:
write(*,*) ’введи число повторов’
!
n : 1000000000 2000000000
read(*,*)n; write(*,*) ’ n=’,n
! real
9.257s
12.654s do i=1,n; enddo
! user
2.380s
4.710s end
! sys
0.003s
0.003s
Видно, что время real складывается не только из времен (user) и (sys),
но еще и из времени, которое пользователь затратил на придумывание вводимого значения n, на его набор и на нажатие клавиши enter. Функ- ция time удобна, когда надо выяснить время работы своей программы от запуска до завершения. При необходимости замерить время, требуемое каким-то фрагментом программы следует пользоваться соответствую- щими конкретному языку программирования функциями.
361

12.2
C-функция clock() и макрос CLOCKS_PER_SEC.
Функция clock() находит значение типа long int (или clock_t) равное числу временных импульсов прошедших с момента запуска программы.
CLOCKS_PER_SEC – число временных интервалов в секунду.
Для оценки временных затрат на работу фрагмента программы запом- ним в переменных (например, t1 и t2) значения, полученные clocks()
до и после завершения его работы соответственно, а перевод в секунды осуществим по формуле ((double)(t2-t1))/(CLOCKS_PER_SEC).
Оценим для примера время работы трех пустых циклов из программы:
#include
#include
// хранит прототип clock(), тип clock_t и CLOCK_PER_SEC.
int main()
{ long int n, i, t0 = clock(); clock_t t1, t2, t3, t4, t5, t6;
double tcl_12, tcl_23, tcl_45, tcl_06;
printf("t0=%7ld
CLOCKS_PER_SEC= %d\n", t0, CLOCKS_PER_SEC);
n=1000000000; t1=clock(); for (i=0;i<=n;i++); t2=clock();
for (i=0;i<=n;i++); t3=clock();
n=2000000000; t4=clock(); for (i=0;i<=n;i++); t5=clock();
tcl_12=((double)(t2-t1))/CLOCKS_PER_SEC;
tcl_23=((double)(t3-t2))/CLOCKS_PER_SEC;
tcl_45=((double)(t5-t4))/CLOCKS_PER_SEC;
printf("введи параметр\n"); scanf("%d",&i);
t6=clock(); tcl_06=((double)(t6-t0))/CLOCKS_PER_SEC;
printf("%15s
Число импульсов к моменту
:
Время исполнения\n"," ");
printf("%15s запуска завершения
:\n"," ");
printf(" Первого цикла : %8ld
%ld
:
%f\n", t1,t2,tcl_12);
printf(" Второго цикла :
%ld
%ld
:
%f\n", t2,t3,tcl_23);
printf(" Третьего цикла :
%ld
%ld
:
%f\n", t4,t5,tcl_45);
printf(" Всего расчета
: %8ld
%ld
:
%f\n", t0,t6,tcl_06);
return 0; }
$ gcc tsttime3.c
// Сравним результаты её
$ time ./a.out
// работы с результатами t0=
0
CLOCKS_PER_SEC= 1000000
// функции time.
введи параметр
888
Число импульсов к моменту
:
Время исполнения запуска завершения
:
Первого цикла :
0 1580000
:
1.580000
Второго цикла :
1580000 3140000
:
1.560000
Третьего цикла :
3140000 6260000
:
3.120000
Всего расчета
:
0 6260000
:
6.260000
real
0m12.903s
Реальное время зависит от продолжительности, например,
user
0m6.265s раздумий над значением вводимого данного (для этого и sys
0m0.004s включен scanf). Ясно видно, что функция clock() отмеряет не реальное время, а исключительно процессорное.
362