Первые шаги разработки
Макросы в Calc
О проекте
Контакты
Полезные ссылки
Карта сайта
Вход для участников

Макросы, диалоги и библиотеки (Basic)

О производительности OOo Basic Печать
Автор Helen   
14.09.2008 г.

Когда-то давно у меня был компьютер ZX Spectrum 128 — мой самый первый компьютер. В те далёкие времена я ещё мало что знал о программировании, а большую часть времени за компьютером проводил играя в бесхитростные килобайтные игры с убогой, по нынешним временам, графикой. Игр было много, хороших и разных. Были и свои шедевры. Одним из них была Elite. Трёхмерные модели космических кораблей поражали воображение, а полная свобода действий и детальнейшая проработка игрового мира сделали игру легендой. Достаточно сказать, что во вселенной, в которой развивалось действие, было более 8000 планет, и каждая — с уникальными названием, характеристиками и описанием. И вот на этом остановимся чуть поподробней.

Название планеты состояло из шести символов, каждый символ кодировался одним байтом. Проведём нехитрые математические вычисления: 8000*6=48000. Да, да, да — только для хранения названий планет требовалось 48 килобайт памяти, при этом игра вполне прекрасно работала на машинах с аналогичным объёмом RAM. Тогда я ещё ничего не знал об алгоритмах архивирования данных и этот факт просто поражал меня.

Прошло много лет, я узнал, как сохранить 8000 названий планет двумя байтами, даже не прибегая к архивированию. Но и до сих пор я восхищаюсь тем, насколько хорошо был оптимизирован код игры. В то время, когда процессоры работали на частотах в десятки килогерц, а оперативная память мощнейших компьютеров была сравнима по объёму с оперативной памятью моей нынешней стиральной машины, на счету был каждый такт процессора и каждый байт памяти.

Сейчас производительности самого дешёвого компьютера достаточно не только для любых офисных задач, но и для большинства игр, выпущенных за год до его покупки. Процесс разработки приложений облегчился до невероятной степени, а требования к оптимизации кода сошли практически на нет, если конечно вы не создаёте приложения для сложных научных расчётов или трёхмерной графики.

Однако и сегодня иногда приходится работать в условиях ограниченных вычислительных ресурсов. Один из таких случаев — это написание макросов для офисных пакетов. Так как языки программирования, созданные для них, не предусматривают компиляции программ в машинный код, а исполняются встроенными в пакеты интерпретаторами, их требования к ресурсам резко возрастают, а производительность становится чрезвычайно низкой. Тут-то и приходится задумываться об оптимизации исходного кода. Особенно остро этот вопрос встаёт при программировании на языке Basic, встроенном в пакет OpenOffice.org, так как его производительность более, чем в 100 раз ниже прямого конкурента — VBA, по крайней мере в том, что касается математических расчётов.

Конечно, сложные, требующие большого количества вычислений, приложения — редкость для макросов в офисных пакетах. Но тем не менее такое случается: это может быть, например, функция для Calc. Применённая в достаточно большой таблице она может вызвать значительное замедление работы пакета. Или, например, блок анализа выделенного во Writer'е текста, требующий для обработки разбить его на отдельные предложения, а каждое из них — на отдельные слова; обработать эти данные, а потом вновь собрать в цельный текст.Эта статья посвящена именно оптимизации скорости выполнения кода на OOo Basic.

Переменные

Оптимизацию кода лучше начинать с самого начала —- с объявления переменных.
Сразу же обратите внимание, что вычисления с использованием переменных вдвое медленнее, чем при использовании констант. Поэтому старайтесь использовать константы вместо переменных, там, где это возможно.

Возможно, работая с другими языками программирования, вы сталкивались с тем, что переменные типа Variant (или аналогичного типа) работают несколько медленнее строго типизированных переменных из-за постоянных преобразований к подтипу. В OOo Basic это не так, и вы можете смело использовать универсальный тип, не опасаясь потери производительности, но только в том случае, если объявите такую переменную явно.

Переменные, не объявленные явно, могут быть медленнее в 10-15 раз! То же можно сказать и о числовых типах: Double и Single ничуть не медленнее Integer и Long. Но, однако, применение целочисленных типов всегда экономит память.

А вот использование статических переменных, объявленных на уровне процедуры с помощью слова Static, может замедлить работу кода почти в полтора раза. Потому не прибегайте к таким переменным без особой нужды — лучше пользуйтесь старым добрым Dim.

Переменные, объявленные на уровне модуля (публичные переменные), тоже несколько проигрывают в скорости классическому Dim внутри процедуры, но уже не так серьёзно как Static — «всего» около 20%. Но правда только в том случае, если они не объявлены с помощью слов Public и Global, так как такие переменные — недосягаемый лидер по медлительности: они почти на 70% уступают по скорости переменным уровня процедуры. Заметьте, что вы не сможете объявлять публичные переменные с помощью Dim, если в этой же библиотеке используете собственный тип данных описанный с помощью Type...End Type. В этих случаях используйте слово Public, иначе рискуете столкнутся с трудно обнаруживаемыми ошибками.

В следующей таблице представлены переменные и относительные скорости их работы, за единицу принята скорость обычной переменной, объявленной с помощью слова Dim на уровне процедуры:

 Ключевое слово Область видимостиСкорость операций
 вычисления[1] присвоения/считывания
 Const Внутри процедуры 1 0,55
 Const На уровне модуля 1 0,55
 Dim Внутри процедуры 1 1
 Dim На уровне модуля 1,18 1,18
 Private На уровне модуля 1,18 1,18
 Static Внутри процедуры 1,47 1,47
 Public На уровне модуля 1,68 1,68
 Global На уровне модуля 1,68 1,68

 
Вот что нужно сказать ещё о переменных. При преобразовании переменных из числовых типов в строки и наоборот неявное преобразование практически всегда быстрее, чем использование функций преобразования типов, каким бы странным это не казалось. Причём иногда более, чем в два раза.
Если в коде часто используется какое-либо свойство объекта, то лучше считать его в переменную, и использовать её, так как доступ к свойствам объектов во много раз медленнее доступа к значению переменной.
Вот такой участок кода:

y = ThisComponent
z = y.CharacterCount
For t = 0 to 10000
x = z
Next

будет втрое быстрее своего аналога:

y = ThisComponent
For t = 0 to 10000
x = y.CharacterCount
Next

считывающего свойство объекта в каждом шаге.

Математические операции

Добиться выигрыша при математических вычислениях очень сложно. Если вам уже знакомо утверждение о том, что многократное умножение быстрее возведения в степень, смело можете отбросить его, так как это характерно только для программ в машинном коде. В OOo Basic этот приём не пройдёт. Более того, умножение здесь — настолько медленная операция, что даже возведение в степень за минимальное число умножений, основанное на сканировании двоичной записи числа, медленнее возведения в степень. Исключение составляет лишь вычисление квадрата числа, так как однократное умножение всё-таки немного быстрее.

Простой и очевидный способ оптимизации вычислений — это сведение их к минимуму. Например, при проверки чётности числа можно воспользоваться следующим условием N/2 = Int(N/2), оно будет истинно только в случае, когда N — чётно. Однако, в этом условии целых две операции деления плюс операция округления. Если воспользоваться целочисленным делением, то от операции округления можно избавиться: N/2 = N2. Но самым быстрым и простым способом проверки делимости числа будет деление по модулю: N mod 2 = 0. Жаль только, что такое условие не всегда применимо в силу того, что операция деления по модулю допустима только для чисел не больше Long.

Также стоит избегать вычислений с нецелыми числами, так как такие операции заметно медленнее целочисленных, что в общем неудивительно.

Операции управления

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

Кроме того, это требует дополнительной оперативной памяти, особенно если параметры процедуры передаются в неё по значению. Так как для каждого параметра в вызванной процедуре будет создана новая переменная — копия передаваемой, но при этом старая переменная уничтожена не будет и останется в памяти. Иногда это может стать проблемой, особенно если вызываются несколько процедур наподобие матрёшки или при рекурсивном вызове, а в качестве передаваемого им параметра выступает, например, достаточно большой массив данных.
В связи со сказанным можно порекомендовать не использовать вызов процедур, особенно внутри цикла с большим количеством шагов, а помещать необходимый код непосредственно в цикле. Это, конечно усложнит разработку, но сильно увеличит производительность. В качестве примера здесь можно привести функции преобразования числа в текст из библиотеки CyrillicTools и функцию SUMTEX. Вторая более чем в 2,5 раза быстрее как раз в силу того, что выполняется внутри одной процедуры.

Выиграть, что либо в операциях условного выбора If...Then и Select Case намного сложнее, но стоит помнить, что первая конструкция несколько быстрее второй и при выборе из двух альтернативных результатов лучше, да и просто удобнее, использовать If...Then.
А вот если вам приходится использовать конструкцию If...Then в ситуации, когда некоторый код должен выполнится только в случае, если условие ложно, то здесь можно ускорить работу If...Then не менее чем на 20%, если вместо вполне очевидной и красивой конструкции:

If Not Условие Then
код
End If

применить конструкцию:

If Условие Then
Else
код
End If

Так как в этом случае не затрачивается время на операцию логического отрицания Not. То же самое касается построения любых условий. Например, мне неоднократно приходилось видеть строки типа Do While Not Условие, хотя по смыслу такая строка фактически эквивалентна строке Do Until Условие, но медленнее, да и просто длиннее.

Работу конструкции Select Case можно ускорить, вынося результаты, которые могут встречаться при выполнении чаще, в начало конструкции, так как проверка условия выполняется до тех пор, пока не будет встречен первый удовлетворяющий условию результат. То есть такой код:

For i = 0 To 1000
Select Case i
Case i > 20
код
Case i < 10
код
Case Else
код
End Select
Next

будет несколько быстрее, чем этот:

For i = 0 To 1000
Select Case i
Case i < 10
код
Case i > 20
код
Case Else
код
End Select
Next

так как в 980 случаях из 1000 будет проверено только одно — первое условие. То есть в течение исполнения первого кода будет проверено в общей сложности 1033 условия, а в течениие выполнения второго кода — 2003 условия, что почти вдвое больше.

Для операций циклов характерны все вышеприведённые замечания относительно условий. Касательно того, какую конструкцию цикла выбрать, можно сказать следующее: цикл For...Next примерно вдвое быстрее Do...Loop, но вдвое медленнее, чем For Each. Поэтому, если вам необходимо перебрать все значения некоторого массива по порядку, стоит воспользоваться конструкцией For Each Переменная In Массив, а не прибегать, как это часто делается, к For i = 0 To UBound (Массив). И уж, тем более, не нужно использовать Do...Loop без крайней необходимости.

Объекты

Я уже упоминал, что при работе с объектами полезно извлечь часто требуемое свойство в переменную. Теперь я расскажу о том, как извлечь его максимально быстро.

Во-первых, всегда стоит помнить о том, что самый простой и грамотный код — всегда самый быстрый. Например, в OOo Basic имеется возможность прямого считывания свойств объектов, однако из других языков эта возможность доступна не всегда. И потому часто можно видеть рекомендации получать свойства с помощью возвращающих их методов, так как в дальнейшем это может облегчить перенос кода на другую платформу. Но это также способствует и ускорению работы кода. Хотя и не намного — что-то около 10%. То есть старайтесь избегать конструкций вида Var = Object.Property, применяя вместо них Var = Object.getProperty, везде, где это возможно. Например, ThisComponent.getCurrentController вместо ThisComponent.CurrentController. И уж, тем более, не стоит использовать методы, получающие свойства по их имени, если в том нет крайней необходимости. Например, метод getPropertyValue интерфейса com.sun.star.beans.XPropertySet передаст свойство объекта почти вдвое медленней, чем если бы вы считали его напрямую.

Те же соображения касаются и установки значений свойств. Однако, здесь разница в скорости, между методом setProperty и прямым присвоением значения свойству настолько незначительна (около 5-6%, она и при считывании невелика), что ей можно смело пренебречь в угоду удобству написания кода.

Применение же конструкции With Object ... End With нисколько не ускоряет работу с объектом, хотя кто-то думает иначе.

Обсудить на форуме...

  • [1] Для констант — только считывание
Последнее обновление ( 09.09.2012 г. )
 
« Пред.   След. »

Главная arrow Макросы, диалоги и библиотеки (Basic) arrow О производительности OOo Basic

MyOOo.ru, 2008 — 2017. Хостинг предоставлен компанией Netangels