Оптимизация скриптов.
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.
|