SolarNet Irc Network logo  
Твое место под солнцем!

Литература => Оптимизация TCL скриптов:


Оптимизация скриптов.


1. Быстрая замена элементов в списках

proc K {x y} {set x}
set theList [lreplace [K $theList [set theList {}]] 7 42]

Что здесь происходит? При использовании [lreplace] обычным способом, создаётся дубликат всего списка,
производятся какие-либо манипуляции, затем дубликат копируется назад в переменную и, наконец, дубликат
уничтожается.

Если вы используете вышеприведённый метод с так называемым К-комбинатором (важное понятие функционального
программирования), то манипуляции со списком проводятся минуя создание промежуточного дубликата списка.

На моей машине такой способ быстрее почти в 50 раз! Естественно, при манипуляциях большими списками:

% proc K {a b} {set a}
% for {set i 0} {$i<100000} {incr i} {
     lappend a $i; lappend b $i
}
% time {set a [lreplace $a 40 80]}
24879 microseconds per iteration
% time {set b [lreplace [K $b [set b {}]] 40 80]}
537 microseconds per iteration
--------------------------------------------------------------------------------------
На моей машине прирост производительности не превысил 2,5 раз при таком же размере списка. Конечно,
не стОит бросаться переписывать ваши скрипты, т.к. для ощутимого прироста производительности они
должны работать с очень большими списками. Но, с другой стороны, если ваш скрипт манипулирует
списками в десятки тысяч элементов, то будет преступлением - пренебреч этим способом оптимизации!
--------------------------------------------------------------------------------------

2. Преобразования типов

Типы данных в Tcl 8.0 и выше представляются внутренним объектом Tcl_Obj. По возможности, не изменяйте
тип этого представления, например используйте:

if {[llength $alist] == 0}
но не:
if {[string compare {} $alist] == 0}

(во втором выражении внутреннее представление аргумента-списка $alist меняется на строку,
что ведёт к неоправданным затратам производительности)

Это правило не должно относиться к триггерам биндов дропа (которые обычно не являются узким местом
производительности), но всёже:

foreach {foo bar moo} [split $args] {break}

быстрее и компактней, чем:

set foo [lindex [split $args] 0]
set bar [lindex [split $args] 1]
set moo [lindex [split $args] 2]

3. Фигурные скобки

Все выражения в Tcl должны быть заключены в фигурные скобки. У меня была плохая привычка писать:

if [command $x $y $z] {
    # stuff
}

Не делайте этого, используйте:

if {[command $x $y $z]} {
    # stuff
}

--------------------------------------------------------------------------------------
В отличие от оптимизации замены в списках, этот вид оптимизации важен практически для всех, так
как вычисления и условные операторы в циклах использует в своей практике любой скриптер. Возьмите себе
за правило заключать все выражения в {}. Вот простой пример демонстрирующий разницу производительности:

% proc a {n} {
   for {set i 0} {$i < $n} {incr i} {
     if [llength $i] { continue }
   }
}
% proc b {n} {
   for {set i 0} {$i < $n} {incr i} {
     if [llength $i] { continue }
   }
}
% time {a 10000}
95375 microseconds per iteration
% time {b 10000}
59353 microseconds per iteration

Результаты говорят сами за себя...
--------------------------------------------------------------------------------------

4. Процедуры

По возможности, оформляйте весь код в процедуры, т.к. инлайн Tcl код не так хорошо оптимизирован
как скомпилированные в байт-код процедуры:

% time {for {set i 0} {$i<10000} {incr i} {lappend a $i}} 100
35300 microseconds per iteration
% proc init_me {} {
   global b
     for {set i 0} {$i<10000} {incr i} {lappend b $i}
   }
% time {init_me} 100
21888 microseconds per iteration

5. Инлайн-регэкспы

Не злоупотребляйте инлайновыми регулярными выражениями:

regexp "\[ \t\n\r\]" $str

Вместо этого сохраните выражение в переменной, а затем используйте её:

set ws "\[ \t\n\r\]"
...
regexp $ws $str

При такой записи в переменной $ws хранится скопилированное выражение представленное объектом
Tcl_Obj и просто выполняется по требованию, тогда как использование первой формы записи приведёт
к компиляции выражения при каждом вызове (если кэш интерпретатора будет исчерпан)

6. Catch

Не используйте [catch] слишком часто, она медленна и должна использоваться только если вы уверены,
что она будет вызываться достаточно редко. Например, эта "ленивая" строка кода в 10 раз медленнее,
чем конструкция [info exists], которая выполняет ту же задачу:

catch {set b $a}

if {[info exists a]} {set b $a}

7. Вызовы процедур

Если вы используете результат процедуры/команды несколько раз, то лучше воспользоваться промежуточной
переменной, а не выполнять команду каждый раз:

медленно:

proc te10 { } {
   putlog "te10: [hand2nick $::owner]"
   putlog "te10: [hand2nick $::owner]"
   putlog "te10: [hand2nick $::owner]"
}

быстрее:

proc te10 { } {
   set ownerhand [hand2nick $::owner]
   putlog "te10: $ownerhand"
   putlog "te10: $ownerhand"
   putlog "te10: $ownerhand"
}

Во втором случае, на мой взгляд, скрипт выглядит проще и чище.

--------------------------------------------------------------------------------------
8. Регэкспы

По себе знаю: после овладения синтаксисом регулярных выражений, скриптер зачастую начинает ими злоупотреблять.
Этого нужно избегать. Для простых случаев, когда нужен не парсинг, а только небольшая синтаксическая
проверка, следует пользоваться альтернативными и более быстрыми методами:
[scan], [string is ...], [string match].

% time {regexp -- {(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})} "127.0.0.1" -> a b c d} 10000
98.3358 microseconds per iteration
% time {scan "127.0.0.1" {%3d.%3d.%3d.%3d} a b c d} 10000
9.8354 microseconds per iteration
--------------------------------------------------------------------------------------

Это перевод. Где лежит оригинал - не в курсе. Автор перевода - v0id.



Рейтинг@Mail.ru