Страница 1 из 1

Оптимизация производительности скриптов.

СообщениеДобавлено: 10 июл 2010 09:46
tvrsh
Перевод поста http://forum.egghelp.org/viewtopic.php?t=10180, в котором рассказывается о том, о каких правилах необходимо знать для создания оптимизированных и быстрых скриптов.
Перевод на русский язык: v0id.
Взято с: http://kreon.net.ru/index.php?name=Pages&op=page&pid=4

1. Быстрая замена элементов в списках.
proc K {x y} {set x}
set theList [lreplace [K $theList [set theList {}]] 7 42]


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

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

На моей машине такой способ быстрее почти в 50 раз! Естественно, при манипуляциях большими списками:
TCL: [ Скачать ] [ Скрыть ]
% 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


V0id писал(а):На моей машине прирост производительности не превысил 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
}


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

TCL: [ Скачать ] [ Скрыть ]
% 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 код не так хорошо оптимизирован как скомпилированные в байт-код процедуры:
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 "[ tnr]" $str

Вместо этого сохраните выражение в переменной, а затем используйте её:
set ws "[ tnr]"
...
regexp $ws $str

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

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

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


7. Вызовы процедур.
Если вы используете результат процедуры/команды несколько раз, то лучше воспользоваться промежуточной переменной, а не выполнять команду каждый раз:
медленно:
TCL: [ Скачать ] [ Скрыть ]
proc te10 { } {
   putlog "te10: [hand2nick $::owner]"
   putlog "te10: [hand2nick $::owner]"
   putlog "te10: [hand2nick $::owner]"
}

быстрее:
TCL: [ Скачать ] [ Скрыть ]
proc te10 { } {
   set ownerhand [hand2nick $::owner]
   putlog "te10: $ownerhand"
   putlog "te10: $ownerhand"
   putlog "te10: $ownerhand"
}

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

8. Регэкспы.
V0id писал(а):По себе знаю: после овладения синтаксисом регулярных выражений, скриптер зачастую начинает ими злоупотреблять. Этого нужно избегать. Для простых случаев, когда нужен не парсинг, а только небольшая синтаксическая проверка, следует пользоваться альтернативными и более быстрыми методами: [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