Коллеги,Подскажите, плз, что-то сам не соображу.
Итак, есть некий перл-скрипт, запускающийся из шелл-врапера, задача которого анализировать принадлежность трафика заданным подсетям и генерировать .sql файлы, для последующего импорта в БД. Скрипт крутится на линукс-боксе P4 3GHz (HT включен) и гиг оперативки, ядро 2.6.22-smp.
Когда скрипт не работает, загрузки нет совсем:
top - 14:55:23 up 24 days, 16:07, 2 users, load average: 0.29, 0.77, 0.85
Tasks: 68 total, 1 running, 67 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 0.3%us, 0.0%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 906184k total, 875832k used, 30352k free, 98976k buffers
Swap: 1048568k total, 52k used, 1048516k free, 616412k cachedPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 15 0 2036 656 564 S 0 0.1 0:00.83 init
Когда работает один инстанс скрипта, загрузка системы выглядит след. образом:
top - 14:54:05 up 24 days, 16:05, 2 users, load average: 1.01, 1.00, 0.92
Tasks: 71 total, 2 running, 69 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.6%us, 0.0%sy, 0.0%ni, 98.1%id, 0.0%wa, 0.6%hi, 0.6%si, 0.0%st
Cpu1 : 0.0%us, 0.0%sy,100.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 906184k total, 880312k used, 25872k free, 98940k buffers
Swap: 1048568k total, 52k used, 1048516k free, 617392k cachedPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9763 root 26 1 11244 6148 2756 R 100 0.7 0:08.64 summary_ipcad.p
1 root 15 0 2036 656 564 S 0 0.1 0:00.83 initКак видно, система (проц) загружен на 50%. Причем загружено только одно ядро.
Логично предположить, что запустив второй инстанс скрипта, система загрузиться полностью и станет работать более эффективно (соседнее ядро проца не будет простаивать). Почти так и происходит:
top - 14:58:39 up 24 days, 16:10, 2 users, load average: 0.94, 0.69, 0.78
Tasks: 73 total, 4 running, 69 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.6%us, 0.6%sy, 98.2%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.6%si, 0.0%st
Cpu1 : 0.0%us, 0.0%sy,100.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 906184k total, 884116k used, 22068k free, 97360k buffers
Swap: 1048568k total, 52k used, 1048516k free, 623988k cachedPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10054 root 26 1 10980 5932 2756 R 100 0.7 0:07.69 summary_ipcad.p
10081 root 26 1 14288 6324 2816 R 100 0.7 0:06.29 summary_ipcad.pНо эффективность мало того, что не вырастает, она уменьшается в несколько раз! Вместо 60 секунд, которые в среднем работал один инстанс, обрабатывая один файл, два инстанса работают дольше 300 секунд!
Объясните, плз, в чем ошибка в моей логике? Или может где-то что-то надо подкрутить?
Заранее спасибо!
вероятнее в вашей задаче основное время затрачивается на операции I/O (чтения/записи на диск).
>вероятнее в вашей задаче основное время затрачивается на операции I/O (чтения/записи на
>диск).как видно из приведенного top'а - i/o wait минимален.
Так как текст скрипта вы не предоставили, то остается только гадать. И самое логичное предположить, что оба экземеляра обращаются к одним и тем же файлам, как следствие только мешают друг другу.
>Так как текст скрипта вы не предоставили, то остается только гадать. И
>самое логичное предположить, что оба экземеляра обращаются к одним и тем
>же файлам, как следствие только мешают друг другу.текст я привести не могу, сорри.
к сожалению, Ваше предположение не верно, т.к. каждый инстанс работает со своим входным файлом. размеры файлов - обычно несколько мегабайт. скрипт его читает, сортирует и дальше отбирает из него только информацию о нужных подсетях.
как я уже написал выше - iowait практически нулевой. так что дело не в этом.
>[оверквотинг удален]
>>самое логичное предположить, что оба экземеляра обращаются к одним и тем
>>же файлам, как следствие только мешают друг другу.
>
>текст я привести не могу, сорри.
>к сожалению, Ваше предположение не верно, т.к. каждый инстанс работает со своим
>входным файлом. размеры файлов - обычно несколько мегабайт. скрипт его читает,
>сортирует и дальше отбирает из него только информацию о нужных подсетях.
>
>как я уже написал выше - iowait практически нулевой. так что дело
>не в этом.60 сек на несколько мегобайт ради того чтобы вытащить подсети? это само по себе как-то долго.
>>как я уже написал выше - iowait практически нулевой. так что дело
>>не в этом.
>
>60 сек на несколько мегобайт ради того чтобы вытащить подсети? это само
>по себе как-то долго.это очень сильно зависит от кол-ва подсетей.
>>>как я уже написал выше - iowait практически нулевой. так что дело
>>>не в этом.
>>
>>60 сек на несколько мегобайт ради того чтобы вытащить подсети? это само
>>по себе как-то долго.
>
>это очень сильно зависит от кол-ва подсетей.Тогда вызывайте дэвида блейна =)
>>60 сек на несколько мегобайт ради того чтобы вытащить подсети? это само по себе как-то долго.
>это очень сильно зависит от кол-ва подсетей.Ну о чем и речь! А не должно зависеть! То есть должно зависеть только от размера файла.
Это я к тому что всё таки где то инстансы лочат друг друга. Если не на входном файле - то на выходном ... без сырков трудно судить - ищи сам.
Кстати про i\o wait чего то у тебя не так, ты как его измерял?
Есть предположение что автор не видит разницы в iowait процессора, выдаваемое top, нагрузке на io, которую можно получить из iostat, и ситуацией взаимных локов, которые вообще не относятся к io.
Также серьезной проблемой является сам процессор, P4 с HT конечно притворяется двухядерником, но де-факто таковым не является и если что-то умудряется загрузить одно псевдо-ядро полностью, то от запуска еще одного экземпляра становится только хуже, причем иногда значительно.
>Есть предположение что автор не видит разницы в iowait процессора, выдаваемое top,
>нагрузке на io, которую можно получить из iostat, и ситуацией взаимных
>локов, которые вообще не относятся к io.Как раз в этом автор разницу видит.
>Также серьезной проблемой является сам процессор, P4 с HT конечно притворяется двухядерником,
>но де-факто таковым не является и если что-то умудряется загрузить одно
>псевдо-ядро полностью, то от запуска еще одного экземпляра становится только хуже,
>причем иногда значительно.А вот эта причина выглядит наиболее правдоподобной. Спасибо!
>>это очень сильно зависит от кол-ва подсетей.
>
>Ну о чем и речь! А не должно зависеть! То есть должно
>зависеть только от размера файла.да, это в идеале. но пока не очень понимаю, как к этому придти.
сорри, guest - это я
Ну так покажите скрипт, поможем :)
Общий принцип работы в подобных ситуациях:
1. Формируем хеш из того, с чем надо проводить сравнение
2. Считываем данные построчно из каждой строки выкусываем регексами нужные куски
3. Проверяем куски на принадлежность хешу, для счетчиков опять таки используем хеши
4. Выводим результат
Как легко заметить скорость будет зависеть только от объема файла с данными и зачастую будет лишь незначительно превышать время нужное на io
>Ну так покажите скрипт, поможем :)ну, давайте попробуем, спасибо :) приведу основной кусок. исходные:
трафик делится на две категории: внешний и локальный
@nonlocalhosts - ip-адреса хостов, трафик на которые считается внешним
@local_nets_dec - ip-адреса локальных подсетей в десятичном представлении, префиксы, маски локальных подсетей в десятичном представлении (соответствующие префиксам)
@client_nets_dec - тоже самое, но только клиентские подсети. плюс - id клиента
@pre_mul_256_... - массивы для быстрого перевода ip-адресов из октетного вида в десятичныйwhile ( <IN> ) {
next if (/^$/);
( $ip1, $ip2, $bytes1, $bytes2 ) = split;$H1proxy = grep $ip1 eq $_, @nonlocalhosts;
$H2proxy = grep $ip2 eq $_, @nonlocalhosts;# какой из хостов принадлежит нашим сетям?
$H1loc = 0; $H2loc = 0;
@ips = split(/\./, $ip1);
#-- convert ips to dec
$ip1_dec = $pre_mul_256_3[$ips[0]] + $pre_mul_256_2[$ips[1]] + $pre_mul_256_1[$ips[2]] + $ips[3];
@ips = split(/\./, $ip2);
#-- convert ip to dec
$ip2_dec = $pre_mul_256_3[$ips[0]] + $pre_mul_256_2[$ips[1]] + $pre_mul_256_1[$ips[2]] + $ips[3];foreach (@local_nets_dec) {
($net_dec, $prefix, $mask_dec) = @{$_};
#-- apply mask
my $ip1_net = $ip1_dec & $mask_dec;
my $ip2_net = $ip2_dec & $mask_dec;
$H1loc = 1 if ($ip1_net == $net_dec && !$H1proxy && !$H1loc);
$H2loc = 1 if ($ip2_net == $net_dec && !$H2proxy && !$H2loc);
#-- exit from the loop if we made all checks
last if ($H1loc && $H2loc);
}#-- вот это самый долгий кусок
foreach (@client_nets_dec) {
($net_dec, $prefix, $mask_dec, $cid) = @{$_};
$ip1_net = $ip1_dec & $mask_dec;
$ip2_net = $ip2_dec & $mask_dec;
#-- хост1 - клиент?
$C1 = ($ip1_net == $net_dec);
#-- хост2 - клиент?
$C2 = ($ip2_net == $net_dec);
unless( $C1 or $C2 ) { next; } # оба хоста не принадлежат %client_netsif( $C1 and $C2 ) { # оба хоста принадлежат %client_nets -> лок трафик
if( ($H1loc and $H2loc) || ($H1loc and $H2proxy) || ($H1proxy and $H2loc) ) { # и входят в лок сеть
$bytes_in_loc{$cid} += $bytes1;
$bytes_out_loc{$cid} += $bytes2;
next;
}
}if( !$C1 and $C2 ) { #$ip1 не принадлежат сети $net, $ip2 принадлежат сети $net
if( (!$H1loc and $H2loc) || (!$H1loc and $H2proxy) ) { # $ip1 внешний хост -> внеш трафик
$bytes_in{$cid} += $bytes2; # переворачиваем
$bytes_out{$cid} += $bytes1;
next;
}
if( ($H1loc and $H2loc) || ($H1loc and $H2proxy) || ($H1proxy and $H2loc) ) { # $ip1 наш хост -> лок трафик
$bytes_in_loc{$cid} += $bytes2; # переворачиваем
$bytes_out_loc{$cid} += $bytes1;
next;
}
}if( $C1 and !$C2 ) { #$ip1 принадлежат сети $net, $ip2 не принадлежат сети $net
if( ($H1loc and !$H2loc) || ($H1proxy and !$H2loc) ) { # $ip2 внешний хост -> внеш трафик
$bytes_in{$cid} += $bytes1;
$bytes_out{$cid} += $bytes2;
next;
}
if( ($H1loc and $H2loc) || ($H1loc and $H2proxy) || ($H1proxy and $H2loc) ) { # $ip2 наш хост -> лок трафик
$bytes_in_loc{$cid} += $bytes1;
$bytes_out_loc{$cid} += $bytes2;
next;
}
}
}
}
Ну как и предполагалось - использование массивов вместо хешей, как следствие полный проход по всем значениям вместо индексов. Однако оптимизация не совсем тривиальна из-за того, что маска различается, нужно учитывать внешние условия. Сколько у вас елементов в local_nets_dec, в client_nets_dec и сколько возможных масок(понятно, что всего 32, но сколько реально используется у вас и какие именно)?
Ну а пока тривиальная оптимизация:
$H1proxy = grep $ip1 eq $_, @nonlocalhosts;
До основного цикла создаем хеш
my %nonlocalhosts;
map {$nonlocalhosts{$_}=1} @nonlocalhosts;
или так
my %nonlocalhosts = map {$_,1} @nonlocalhosts;
После чего проверка будет осуществятся следующим образом:
$H1proxy = $nonlocalhosts{$ip1}
>из-за того, что маска различается, нужно учитывать внешние условия. Сколько у
>вас елементов в local_nets_dec, в client_nets_dec и сколько возможных масок(понятно, что
>всего 32, но сколько реально используется у вас и какие именно)?DB<8> p$#local_nets_dec
12
DB<9> p$#client_nets_dec
1047Префиксы в настоящий момент такие:
32
31
30
29
28
27
26
25
24
23
20
19
18
17
16>До основного цикла создаем хеш
>my %nonlocalhosts;
>map {$nonlocalhosts{$_}=1} @nonlocalhosts;
>После чего проверка будет осуществятся следующим образом:
>$H1proxy = $nonlocalhosts{$ip1}сделал. скорость не выросла совсем: было 29 сек, стало 30 сек.
>сделал. скорость не выросла совсем: было 29 сек, стало 30 сек.Ну так ведь это был не самый важный участок :).
Итого имеем 15 масок. а значит на каждый ip нам нужно сделать всего 15 операций выделения сетевой части. Дальше будет цикл на эти пятнадцать сетей полученных из ip с поиском по хешу сформированному из client_nets_dec. У вас же было 1047 операций по выделению сети и сравнению. Замечу что при увеличении количества сетей время будет расти логарифмически, у вас же оно растет линейно.
Сейчас нет времени переделать ваш скрипт, но идею думаю вы поняли, попробуйте сами сделать.
>Итого имеем 15 масок. а значит на каждый ip нам нужно сделать
>всего 15 операций выделения сетевой части. Дальше будет цикл на эти
>пятнадцать сетей полученных из ip с поиском по хешу сформированному из
>client_nets_dec. У вас же было 1047 операций по выделению сети и
>сравнению.Что-то я все-таки не очень понял. 15 масок - это только уникальные, причем только среди префиксов. Всего же - 1048 сетей.
Чтобы определить, какому (каким) клиенту (клиентам - т.к. 1 подсеть может принадлежать сразу нескольким клиентам) принадлежат ip1 и ip2, мне надо сравнивать эти ip с подсетью каждого клиента, т.е. всего - 1048 сравнений (ну, 2 по 1048).
Для начала преобразуем @client_nets_dec в хеш, разумеется до основного цикла
foreach (@client_nets_dec) {
($net_dec, $prefix, $mask_dec, $cid) = @{$_};
$client_nets_mask{$mask_dec}{$net_dec}=$cid;
}
Дальше в самом цикле меняем ваш foreach (@client_nets_dec) на следущий
foreach my $mask_dec (keys %client_nets_mask){
$ip1_net = $ip1_dec & $mask_dec;
$C1 = $client_nets_mask{$mask_dec}{$ip1_net};
...
В $С1 или пустота в случае если ip не принадлежит ни одной подсети данной размерности либо соответствующий cid. Аналогично для второго ip. В результате мы получим что количество проходов зависит не от размера @client_nets_dec, а от количества разных масок в нем, а у вас их всего 15.
Для local_nets_dec подобное смысла не имеет.
Если у вас несколько клиентов могут иметь одну и ту же сеть, что несколько странно, то используем arrayref для значения:
$client_nets_mask{$mask_dec}{$net_dec}=$cid;
заменяем на
push @{client_nets_mask{$mask_dec}{$net_dec}},$cid;
и соответственно в $C1 будем иметь нужный нам arrayref, по которому надо будет пройтись в foreach или map
>Если у вас несколько клиентов могут иметь одну и ту же сеть,
>что несколько странно, то используем arrayref для значения:Не подсеть одну, а маску. В общем, теперь все понятно :)
Сделал, короче. Поразительные результаты. Даже сложно определить во сколько раз выросла скорость :)
Было - 64 секунды, стало - 1 (одна!)В общем, спасибо большое!