3. Отладка и профилирование Отладка в Euphoria намного проще, чем в большинстве других языков программирования. Обеспечиваемая широкая непрерывная проверка правильности хода программы позволяет интерпретаторам Euphoria автоматически отлавливать многие такие ошибки, которые в других языках потребовали бы для трассировки часов работы. Когда интерпретатор Euphoria хватает ошибку, вы всегда получаете короткий отчет на экране и детальный разбор ситуации в файле, который называется ex.err. Эти сообщения об ошибке всегда включают полное описание на английском языке того, что случилось, вместе с трассировкой обращений к стеку. В файле ex.err будет также распечатка значений всех переменных, и, при необходимости, список последних выполненных операторов. Для экстремально больших рядов выводится только частичная распечатка значений. Если название файла ex.err не подходит вам по каким-то причинам, вы можете назначить другое имя файла и расположить этот файл в любом каталоге на вашей системе, вызвав подпрограмму crash_file(). Вдобавок, программист может создать собственные типы, которые точно определят набор допустимых значений для каждой переменной, объявленной в программе. Сообщение об ошибке будет выработано в тот самый момент, когда переменная получила незаконное значение. Иногда программа будет делать вовсе не то, что вам нужно, без всяких сообщений об ошибках времени прогона. В любом языке программирования может быть хорошей идеей в таком случае еще раз внимательно изучить исходный код и обдумать алгоритм, который вы закодировали. Может оказаться полезным ввести в программу операторы print в стратегически важных точках кода, чтобы пронаблюдать за внутренней логикой хода вашей программы. Этот подход особенно удобен в интерпретирующих языках, подобных Euphoria, так как вы можете просто корректировать исходный текст и тут же запускать его на исполнение, не ожидая повторной компиляции и линковки программы. Интерпретатор Euphoria снабжает вас дополнительными мощными инструментами для отладки. Используя оператор trace(1), вы можете трассировать исполнение вашей программы на одном экране и наблюдать реальные выводы вашей программы на другом. trace(2) это то же самое, что и trace(1), но экран трассировки будет монохромным. И, наконец, включив trace(3), вы сможете протоколировать выполненные операторы в файле с названием ctrace.out. Полные трассировочные возможности являются частью пакета Euphoria Complete Edition, но доступны также и в пакете Public Domain Edition для программ размером до 300 операторов. Специальные операторы with trace / without trace задают области вашей программы, которые доступны для трассировки. Часто вы будете просто вставлять оператор with trace в самом начале вашего кода, чтобы сделать весь код готовым к трассировке. Иногда лучше поместить первый оператор with trace после всех типов, определенных вами, так чтобы вы не попадали в эти типы после каждого присваивания переменным нового значения. В другом случае вы можете вполне точно знать, какая подпрограмма или подпрограммы требуют трассировки, и вам нужно будет отобрать для трассировки только их. Конечно, если вы находитесь в окне трассировки, вы можете интерактивно пропустить отображение трассировки любой подпрограммы, нажав на клавиатуре клавишу со стрелкой вниз вместо клавиши Enter. Только строки, доступные для трассировки, могут появляться в файлах ctrace.out или ex.err как "Traced lines leading up to the failure" -- "Оттрассированные строки, предшествовавшие ошибке", когда случается ошибка времени прогона. Если вам нужна эта информация, а вы не получили ее, вы должны вставить в ваш код оператор with trace и перезапустить вашу программу. Исполнение программы будет менее быстрым, когда выполняются строки, откомпилированные с оператором with trace, особенно, когда используется trace(3). После того, как вы определили строки, которые нужно трассировать, ваша программа должна динамически включать (активизировать) трассировку выполнением оператора trace(). Вы можете просто сказать: with trace trace(1)в самом начале вашей программы, так что трассировка и начнется немедленно с началом прогона. Но в более общем случае вам нужно будет включать трассировку, когда процесс достиг определенной подпрограммы или выполнились какие-то дополнительные условия для начала трассировки, т.е. if x < 0 then trace(1) end if Выключить трассировку можно, выполнив оператор trace(0). Вы можете выключить ее также интерактивно, нажав в окне трассировки клавишу 'q'. Необходимо помнить, что with trace должно появляться вне любых подпрограмм, в то время как trace() могут появляться и внутри подпрограммы, и вне ее. Вам может потребоваться включение трассировки изнутри типа. Предположим, вы запустили свою программу, а она остановилась с сообщением и с файлом ex.err, из которого видно, что одна из ваших переменных получала странное, хотя и законное, значение. И вам необходимо понять, как это могло произойти. Просто создайте тип для этой переменной, в котором выполняется trace(1), если переменной присваивается то странное значение, которое вас так заинтересовало, т.е. type positive_int(integer x) if x = 99 then trace(1) -- как это могло случиться??? return 1 -- посмотрим else return x > 0 end if end type Когда будет выдано positive_int(), вы увидите именно тот оператор, который вызвал появление у вашей переменной странного значения, и вы сможете проверить значения других переменных. Вы сможете также проверить выходной экран программы, чтобы посмотреть, что происходило именно в этот момент. Если вы определите positive_int() так, чтобы выдавался 0 для странного значения (99) вместо 1, вы инициируете диагностическую распечатку в файл ex.err. Когда выполнен оператор trace(1) или trace(2), главный выходной экран вашей программы сохраняется в памяти и появляется экран трассировки. На нем отображается текст вашей программы с подсвеченным оператором, который будет выполнен следующим, и с несколькими операторами до и после него. Несколько строк внизу экрана отведены для вывода имен переменных и их значений. На верхней строке показаны команды, которые вы можете ввести в этой точке:
Когда вы трассируете программу, имена переменных и их значения автоматически появляются в нижней части экрана. По мере присваивания переменным новых значений вы будете видеть их имена и величины внизу экрана. Эти величины всегда имеют самое последнее присвоенное значение. Частные переменные автоматически стираются с экрана по завершении подпрограммы. Когда область вывода переменных заполняется, более ранние выводы убираются, чтобы освободить место новым переменным. Значение для длинных рядов отсекается после вывода 80 символов. Для вашего удобства числа, которые находятся в диапазоне кодов таблицы ASCII (32-127), отображаются вместе с соответствующими символами ASCII. Символ ASCII выводится в контрастном цвете (в кавычках на монохроматическом дисплее). Это сделано для всех переменных, так как Euphoria не знает в общем случае, о чем вы думаете, о числах или о символах ASCII. Вы найдете аналогичные символы ASCII (в кавычках) также и в файле ex.err. Этот прием, конечно, несколько загромождает экран, но информация ASCII, поверьте нам, часто бывает очень полезна. Экран трассировки выводится в том же графическом режиме, что и главный экран программы. Это делает переключение между ними более легким и быстрым. Когда трассируемая программа требует ввода с клавиатуры, появляется главный экран программы, это дает вам возможность произвести нормальный ввод. Такой прием хорошо работает с подпрограммой ввода gets() (чтение одной строки). Когда вызвана подпрограмма get_key() (быстрый съем состояния клавиатуры), вам дается 8 секунд для ввода символа, в противном случае считается, что для данного вызова get_key() ввод символа не отмечен. Такой порядок позволяет вам проверять как случаи ввода, так и случаи отказа от ввода символа в подпрограмме get_key().
Когда ваша программа вызывает trace(3), активизируется трассировка в файл. Файл, ctrace.out, будет создан в текущем каталоге. Этот файл содержит информацию о последних 500 операторах Euphoria, выполнявшихся вашей программой. Файл организован как круговой буфер, который имеет емкость максимум на 500 операторов. Каждый очередной записываемый оператор всегда предваряется сообщением "=== THE END ===". Так как буфер круговой, последний записанный оператор может оказаться в любом месте файла ctrace.out. Этот оператор, идущий вслед за сообщением "=== THE END ===", является 500-ым - последним. Данная форма трассировки поддерживается и интерпретатором, и транслятором с Euphoria на C редакции Complete Edition. Она особенно полезна при поиске ошибок машинного уровня, когда исключено получение обычного диагностического файла Euphoria ex.err. Просмотрев предпоследние нормально выполненные операторы, вы сможете сделать выводы о причинах отказа программы. Возможно, последним оператором оказался poke() в незаконную область памяти. Возможно, это был вызов функции C. В некоторых случаях это может быть ошибка собственно в интерпретаторе или трансляторе. Исходный код очередного оператора записывается в буфер ctrace.out и затем выталкивается в файл командой flush непосредственно перед выполнением этого очередного оператора, так что все данные за то, что отказ вызван именно этим последним вытолкнутым в файл оператором, оператором, который вы видите в ctrace.out.
3.2 Профилирование (только Complete Edition) Если вы в своей программе укажете with profile (DOS32, WIN32 или Linux), или with profile_time (только DOS32), интерепретатором будет создан специальный листинг вашей программы, так называемый профиль, когда закончится исполнение программы. Этот листинг выводится в файл ex.pro в текущем каталоге. Доступны два типа профилирования: профиль команд и профиль времени. Вы получаете профиль команд, когда задаете with profile. И вы получаете профиль времени, когда задаете with profile_time. Но вы не имеете возможности смешивать два типа профилирования в единственном прогоне вашей программы. Вам потребуются два различных прогона. Мы прогнали программу контроля производительности sieve.ex (каталог demo\bench) под обоими типами профилирования. Результаты можно видеть в файлах sieve1.pro (профиль команд) и sieve2.pro (профиль времени). Профиль команд точно показывает, сколько раз во время прогона программы выполнен каждый оператор. Если оператор не выполнен ни одного раза, поле счета будет пустым. Профиль времени (только DOS32) дает оценку, какую часть времени прогона заняло исполнение каждого оператора. Эта оценка выражена в процентах от времени профилирования. Если оператор никогда не выполнялся, поле его оценки останется пустым. Если вы встретите оценку 0.00, это будет означать, что оператор выполнялся, но недостаточно часто, чтобы получить счет величиной 0.01. В листинге профиля отражаются только те операторы, которые откомпилированы с with profile или с with profile_time. Обычно вы будете указывать или with profile, или with profile_time в самом начале вашего главного .ex-файла, так что сможете получить полный листинг. Просматривайте эти файлы листинга в редакторе Euphoria, чтобы иметь цветную подсветку операторов. Профилирование может помочь вам многими способами:
Иногда вы захотите сфокусировать внимание на каких-то отдельных действиях, выполняемых вашей программой. Например, разрабатывая игру Language War, мы пришли к выводу, что, хотя в общем игра исполнялась достаточно быстро, но в момент взрыва планеты при разбрасывании 2500 пикселов по всем направлениям имевшейся скорости не было достаточно, и темп игры заметно падал. Мы решили попытаться ускорить подпрограмму взрыва. Нас не заботил остальной код. Подход к проблеме заключался в том, чтобы вызвать profile(0) в начале игры Language War, сразу после with profile_time, выключая профилирование, а затем последовательно включать и выключать его, вызывая profile(1) в начале подпрограммы взрыва и profile(0) в конце этой подпрограммы. Действуя подобным образом, мы могли прогонять игру, создавая многочисленные взрывы, и документировать множество примеров только по эффекту взрыва. Если бы мы просто профилировали программу целиком, картина не была бы вполне ясной, так как низкоуровневые подпрограммы используются также для передвижения кораблей, вывода фазоров и т.д. Оператор profile() может помочь таким же самым образом и при необходимости получения конкретизированного профиля команд.
Профиль времени генерируется под управлением системного таймера, инициирующего соответствующие прерывания. Когда вы указываете в своей программе with profile_time, Euphoria начинает контролировать, какой оператор вашей программы выполнялся в момент каждого очередного прерывания от системного таймера. Эти прерывания обычно идут с частотой 18.2 раз в секунду, но если вызвать процедуру tick_rate(), частота может быть установлена значительно более высокая. Повышение частоты прерываний от таймера приводит к увеличению точности профиля времени, так как в этом случае он создается на основе большего числа отсчетов по ходу вашей программы. По умолчанию, если вы не вызывали tick_rate(), tick_rate(100) будет вызвана автоматически с началом профилирования. Вы можете установить частоту прерываний еще более высокую (скажем, 1000), но тогда начнется ухудшение общей производительности вашей программы. Каждый отсчет профиля времени требует 4-х байт в памяти и обычно создаваемый буфер расчитан на 25000 отсчетов. Если вам необходимо больше, чем 25000 отсчетов, вы можете потребовать: with profile_time 100000Тогда зарезервируется пространство под 100000 отсчетов (к примеру). Если буфер переполнится, вверху файла ex.pro будет выведено предупреждение. С частотой 100 отсчетов в секунду ваша программа может исполняться 250 секунд, прежде чем будет заполнен обычный буфер на 25000 отсчетов. Euphoria не может динамически увеличивать этот буфер за время обработки прерывания от таймера. Поэтому вы и должны задать его объем из своей программы, если это необходимо. После завершения выполнения каждого исполняемого оператора верхнего уровня Euphoria будет обрабатывать отсчеты, накопленные к этому моменту, и освобождать буфер для новых отсчетов. Таким образом, профиль реально может основываться на большем числе отсчетов, чем одномоментно размещается в заданном вами объеме буфера. Проценты времени, показанные на левом поле в файле ex.pro, расчитываются путем отнесения числа отсчетов, приходящихся на оператор, к общему числу взятых отсчетов, т.е. если на оператор приходится 50 отсчетов из общего числа 500, значение 10.0 (10 процентов) будет выведено на левом поле напротив оператора. Когда профилирование приостанавливается оператором profile(0), прерывания от таймера игнорируются, отсчеты не берутся, общее число отсчетов не увеличивается. Получая большее число отсчетов, вы можете повысить точность результата профилирования. Тем не менее, есть одна ситуация, когда так сказать нельзя, эта ситуация связана с самосинхронизацией вашей программы под таймер, если программа ждет именно прерывание от таймера, пытаясь выполнить подпрограмму time(). Операторы, исполняемые непосредственно после момента "скачка стрелки часов", могут никогда не дать ни одного отсчета, что будет приводить к довольно расплывчатой картине профиля, например, while time() < LIMIT do end while x += 1 -- этот оператор не даст ни одного отсчета Иногда вы будете удивлены значительным процентом времени, занимаемым оператором return. Это может происходить из-за накладных расходов на освобождение памяти, временно занятой частными переменными, использованными внутри функции. Значительное время может занимать также освобождение памяти, когда вы присваиваете новую величину большому ряду. Когда начинается перенесение кода и данных на диск в процессе своппинга, вы увидите большие потери времени, связанные с обращением к данным, перенесенным в своп-файл. Это будет касаться операторов доступа к элементам большого ряда, находящегося на диске.
|