Повышение производительности
В любой системе программирования, а собенно в Euphoria, вы должны выполнить на практике измерения перед тем, как делать заключения о производительности. Euphoria обеспечивает два метода анализа и коррекции производительности: профилирование по счётчику и профилирование по времени (только для DOS32). Прочтите refman.doc, если решите освоить этот инструментарий. Результаты профилирования зачастую могут оказаться для вас сюрпризом. Сконцентрируйте своё внимание и усилия на тех местах в вашей программе, которые занимают большой процент общего времени прогона (или, по крайней мере, выполняются большое количество раз). Нет необходимости переписывать ту секцию кода, которая занимает 0.01% от общего времени. Обычно обнаруживаются одно или не слишком много мест, где доводка кода может дать заметную разницу в скорости. Вы можете измерить скорость своего кода с использованием функции time(). Например: atom t t = time() for i = 1 to 10000 do -- испытуемый участок кода end for ? time() - t Испытав участок кода, как показано выше, вы можете обдумать затем различные пути ускорения вычислений, учитывая данные подобного хронометража для новых вариантов кода.
Профилирование покажет вам горячие точки в вашей программе. Обычно они находятся внутри циклов. Посмотрите на каждое вычисление внутри цикла и спросите себя, действительно ли вам нужно это повторять каждый раз до конца цикла, или это можно сделать всего один раз, до входа в цикл.
Операция сложения заметно быстрее операции умножения. Иногда вы можете заменить умножение на переменную цикла сложением. Что-то вроде этого примера: for i = 0 to 199 do poke(screen_memory+i*320, 0) end forкоторый заменяется на: x = screen_memory for i = 0 to 199 do poke(x, 0) x = x + 320 end for Сохранение результатов в переменных
Если у вас есть маленькая и быстрая подпрограмма, но её приходится вызывать большое число раз, вы сэкономите время, выполняя операции этой подпрограммы по очереди, а не вызывая подпрограмму. Ваш код станет при этом менее компактным и читать его будет труднее, так что устраивать очередь нужно осмотрительно, только в тех местах, где происходит слишком большое число вызовов подпрограммы.
Euphoria позволяет вам оперировать с большим рядом данных, используя одну команду. Это избавляет вас от написания цикла, в котором вы обрабатываете элемент за элементом последовательно. Например, вы можете делать: x = {1,3,5,7,9} y = {2,4,6,8,10} z = x + yвместо: z = repeat(0, 5) -- если необходимо for i = 1 to 5 do z[i] = x[i] + y[i] end for В большинстве интерпретирующих систем матричные (векторные) операции с массивами выполняются значительно быстрее, чем если бы массив обрабатывался в цикле операций, элемент за элементом. Это происходит из-за того, что интерпретаторы имеют значительные накладные расходы на каждую операцию, которую они выполняют. Euphoria здесь выделяется экономичностью. Euphoria очень аскетична, с небольшими интерпретационными расходами, поэтому операции на её массивах (рядах) выигрывают не всегда. И единственным выходом для выбора между двумя альтернативами является хронометрирование обоих путей. Цена на один элемент обычно ниже, когда вы обрабатываете ряд одной командой, но на динамическое управление размером рядов тратится время, которое может перевешивать ситуацию в пользу цикла.
Euphoria автоматически оптимизирует некоторые специальные случаи. В примерах ниже x и y могут быть переменными или произвольными выражениями: x + 1 -- быстрее, чем общее x + y 1 + x -- быстрее, чем общее y + x x * 2 -- быстрее, чем общее x * y 2 * x -- быстрее, чем общее y * x x / 2 -- быстрее, чем общее x / y floor(x/y) -- если x и y целые, быстрее, чем x/y floor(x/2) -- быстрее, чем floor(x/y) Далее в примерах x - простая переменная, y - любая переменная или выражение: x = append(x, y) -- быстрее, чем общее z = append(x, y) x = prepend(x, y) -- быстрее, чем общее z = prepend(x, y) x = x & y -- если x много больше, чем y, -- быстрее, чем общее z = x & yКогда вы пишете цикл, который увеличивает ряд путём добавления или сцепления новых элементов с исходным рядом, время будет, в общем случае, расти пропорционально квадрату числа (N) элементов, которое вы добавляете. Тем не менее, если вы можете использовать одну из особых оптимизированных форм функций append(), prepend() или операции сцепления, перечисленных выше, время будет расти пропорционально просто N (приблизительно). Это обстоятельство помогло бы сберечь вам огромный кусок времени, когда вы создаёте экстремально длинный ряд. (Вы также могли бы использовать функцию repeat(), чтобы зарезервировать память под максимальный необходимый размер ряда, а затем заполнять ряд элементами в цикле, как это обсуждается ниже.)
Чтобы повысить скорость, преобразовывайте: левая_сторона = левая_сторона операция выражениев форму: левая_сторона операция= выражениевсякий раз, когда левая_сторона содержит, как минимум, 2 индекса, или, как минимум, один индекс и отрезок. В более простых случаях обе формы имеют одну и ту же скорость (или почти одну и ту же).
Вывод текста на экран с помощью puts() или printf() не блещет скоростью. Если необходимо, под DOS32, вы можете выводить текст гораздо быстрее путём прямой записи в видеопамять, или с использованием display_text_image(). Имеются очень большие накладные расходы с каждым puts() на экран, и относительно малая возрастающая цена на символ. И эти расходы с exw (WIN32) особенно высоки (по крайней мере, под Windows 95/98/ME). Linux и FreeBSD находятся где-то между DOS32 и WIN32 по скорости вывода текста. Как бы то ни было, имеет смысл сначала построить длинную строку, а уже затем вызвать puts(), вместо того, чтобы вызывать puts() для каждого символа. Однако построение строки, превышающей по длине ширину экрана, не даёт выигрыша. Медлительность вывода текста на экран находится в зоне ответственности операционной системы.
Некоторые общие подпрограммы являются экстремально быстрыми. Вы, вероятно, не смогли бы сделать работу быстрее другим путём, даже с привлечением C или языка ассемблера. Вот эти некоторые подпрограммы:
Другие подпрограммы достаточно быстры, но работа могла бы быть сделана и побыстрее, если скорость имеет ключевое значение. x = repeat(0,100) for i = 1 to 100 do x[i] = i end forявляется более быстрым решением, чем: x = {} for i = 1 to 100 do x = append(x, i) end forпоскольку append() должна выделять и перевыделять пространство в памяти по мере роста x в размере. С repeat() пространство под x выделяется один раз в начале (заметим, что append() достаточно хороша, чтобы не выделять пространство с каждым добавлением длины x. Она выделяет несколько больше, чем это в данный момент строго необходимо, чтобы уменьшить число перевыделений). Вы можете заменить: remainder(x, p)на: and_bits(x, p-1)для повышения скорости, когда p является положительной степенью 2. Но x должно быть неотрицательным целым, которое не превышает 32-х разрядов. Функция arctan() быстрее, чем arccos() или arcsin().
Функция Euphoria find() является наибыстрейшим средством поиска величины в ряде длиной не больше 50 элементов. Для более длинных рядов рекомендуется опробовать хэш-таблицу (demo\hash.ex) или двоичное дерево (demo\tree.ex).
В большинстве случаев вы можете, не раздумывая, использовать сортировку Шелла, функцию в библиотеке include\sort.e. Если у вас имеется громадный объём данных, требующих сортировки, попробуйте одну из сортировок билиотеки demo\allsorts.e (например, сортировку great). Если ваши данные не умещаются в памяти, не полагайтесь на способность Euphoria к автоматическому свопингу данных на диск. Вместо этого сортируйте по несколько тысяч записей за один раз, выводя результаты последовательно во временные файлы. А затем соедините все отсортированные временные файлы в один большой результирующий отсортированный файл. Если ваши данные состоят только из целых и лежат все в довольно узком диапазоне, попробуйте сортировку bucket в библиотеке demo\allsorts.e.
По мере роста скорости процессоров разрыв между скоростью буферной памяти на кристалле и скоростью главного ОЗУ становится всё больше. У вас может быть 256 Mb ОЗУ на компьютере, но буфер на кристалле будет около 8K (данные) плюс 8K (инструкции) с процессором Pentium, или 16K (данные) плюс 16K (инструкции) с Pentium MMX или Pentium II/III. Большинство машин будет иметь также буфер "уровня-2" на 256K или 512K. Алгоритм, который шагает вдоль длинного ряда, состоящего из пары тысяч элементов или больше, много раз, от начала к концу, выполняя одну небольшую операцию над каждым элементом, не обеспечит полноценное использование буфера данных на кристалле. Здесь могло бы помочь однократное прохождение ряда, с применением всех необходимых операций к каждому элементу до перехода к следующему элементу. Эта же рекомендация актуальна и для случая свопинга, когда данные начинают перекачиваться из ОЗУ на диск. Эти буферные эффекты не так заметны с Euphoria, как с низкоуровневыми компилируемыми языками, но они всё-таки доступны для измерений.
Euphoria позволяет вам вызывать подпрограммы, написанные на 32-х разрядном машинном коде Intel. На платформах WIN32, Linux и FreeBSD вы можете вызывать функции C в .dll- или .so-файлах, и эти функции C могут вызывать ваши подпрограммы Euphoria. Вам могли бы понадобиться вызовы подпрограмм C или машинного кода из-за чего-то, что не предусмотрено непосредственно в Euphoria, или нужна особенно большая скорость. Чтобы поднять скорость, машинный код или функция C нуждаются в значительном объёме работы по каждому вызову, иначе накладные расходы на подачу аргументов и собственно вызов будут превалировать, и не дадут вам слишком большой выгоды. Многие программы имеют некоторые внутренние операции в своём ядре, которые занимают наибольшую часть времени процессора. Если вы сможете запрограммировать эти операции на C или в машинном коде, оставив основную массу кода на Euphoria, вы получите сопоставимую с C скорость, не жертвуя безопасностью и гибкостью Euphoria.
Начиная с версии 3.0, полный транслятор с Euphoria на C включён в общедоступный пакет системы. Транслятор преобразует любую программу Euphoria в набор файлов исходного кода C, которые могут быть откомпилированы с использованием компилятора C. Исполняемый файл, который вы получаете на выходе транслятора, работает так же, как и программа под управлением интерпретатора, но значительно быстрее. Выигрыш по скорости в этом случает может быть в диапазоне от нескольких процентов до пятикратного и более.
|