Советы на тему о производительности Euphoria
В любом языке программирования, а особенно в 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(), чтобы сразу достичь максимальной длины ряда, а затем заполнять ряд элементами в цикле, как описано ниже.)
Для ускорения преобразуйте: left-hand-side = left-hand-side op expressionв: left-hand-side op= expressionвсякий раз, когда left-hand-side содержит как минимум 2 индекса или, по крайней мере, один индекс и один отрезок. Во всех более простых случаях обе формы исполняются за одно и то же время (или очень близкое).
Вывод текста на экран с использованием puts() или printf() скорее медленный, чем быстрый. Если необходимо, под DOS32, вы можете выполнять его намного быстрее, применив прямую запись в видеопамять, или воспользовавшись процедурой display_text_image(). Накладные расходы на каждый оператор puts() при выводе на экран очень велики, но относительно невелика возрастающая цена на каждый очередной символ. Эти накладные расходы особенно большие с exw(по крайней мере, под Windows 95/98). Linux по скорости вывода текста находится где-то между 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 (к примеру, грейт-сортировку). Если ваши данные не размещаются в памяти, не полагайтесь на автоматический Euphoria своппинг памяти на диск. Напротив, сортируйте по несколько тысяч записей и записывайте их в последовательность временных файлов. Затем объедините отсортированные временные файлы в один большой отсортированный файл. Если ваши данные состоят только из целых чисел, и они все находятся в пределах довольно узкого диапазона, попробуйте бакет-сортировку из файла demo\allsorts.e.
По мере роста скорости процессоров разрыв по скорости между кэш-памятью кристалла и основной памятью или DRAM (dynamic random access memory) становится даже больше. У вас может быть на машине 32 Мб DRAM, но на кристалле вы будете иметь что-то около 8K (данные) плюс 8K (инструкции) для Pentium, или 16K (данные) плюс 16K (инструкции) для Pentium с MMX или Pentium II/III. Большинство машин будет также иметь кэш-память "уровня-2" объемом 256K или 512K. Алгоритм, который шагает вдоль длинного ряда, состоящего из пары тысяч (4-байтных) элементов или даже больше, много раз, от начала к концу и обратно, выполняя одну небольшую операцию над каждым элементом, не позволит полноценно использовать кэш-память данных на кристалле. Может оказаться более выгодным один проход с выполнением всех заданных операций над каждым элементом с последующим переходом к следующему элементу. Такой же по сути аргумент актуален, когда ваша программа начинает своппинг, и только что использованные данные переносятся на диск. Эти эффекты буферизации не так заметны в Euphoria, как в компилирующих языках низкого уровня, но они поддаются измерениям.
Euphoria позволяет вам вызывать процедуры, написанные в кодах 32-битной машины Intel. Под WIN32 и Linux вы можете вызывать процедуры C, размещенные в .dll-файлах или .so-файлах, а эти процедуры C могут вызывать ваши процедуры Euphoria. Вам может потребоваться вызов C или машинного кода, если что-то не может быть исполнено непосредственно в Euphoria, или вам нужно разогнать свою программу. Что касается подъема скорости, машинный код или процедура C потребуют значительного объема работы на каждый вызов. Таким образом, накладные расходы на задание аргументов и производство вызова будут доминировать, и вы можете не выиграть всего того, на что расчитывали, затевая эту гонку. Многие программы выполняют некоторые внутренние скрытые операции в своем ядре, занимающие основное время процессора. Если вы можете закодировать все это на C или в машинном коде, выйдя за пределы программы на Euphoria, вы сможете получить скорость, свойственную C, не жертвуя безопасностью и гибкостью Euphoria.
Вы можете получить транслятор с Euphoria на C на Web-узле RDS. Он переведет любую программу Euphoria в набор исходных файлов C, которые вы затем сможете откомпилировать с использованием компилятора C. Исполняемый файл, который вы получаете с использованием транслятора, выполняет ту же самую работу, но быстрее, чем программа под управлением интерпретатора. Ускорение может составить от нескольких процентов до пятикратного и более.
|