В нескольких программах (perl) мне нужно ( напрмер, для скорости сравнения )
испольковать ip аддреса в численном виде.
идею перевода заимствовал из mysqlsub to_num {
my @s = split /\./, $_[0];
my $num = $s[0]*256**3 + $s[1]*256**2 + $s[2]*256 + $s[3];
return $num;
}но вот, с обратным переводом - проблемма. Я для него ипользу приведенный ниже алогитм, который, мягко говоря "не оптимальный". Есть-ли более красивый способ?
sub from_num {
my @ipc; # ip in chars
my $curr = $_[0];
push @ipc, int($curr/256**3);
$curr -= $ipc[0]*256**3;
push @ipc, int($curr/256**2);
$curr -= $ipc[1]*256**2;
push @ipc, int($curr/256);
$curr -= $ipc[2]*256;
push @ipc, $curr;
return join('.',@ipc);
}
Странное решение они используют... может быть, просто для того, чтобы избежать неверного сравнивания адресов вида 1.2.3.4 и 001.002.003.004?
Потому что в perl'e расходы на преобразования будут намного больше, чем сравнение строк.
Если мне склероз не изменяет, в perl'e все хранится в виде строк, так что нет нужды переводить из строки в числа.Ну, а если уж очень хочется, то может так красивше?
sub from_num {
my $n = shift;
return int($n/16777216).".".int($n%16777216/65536).".".
int($n%65536/256).".".int($n%256)
}Да, забыл написать, что может еще правильнее будет использовать inet_aton/inet_ntoa
из пакета Socket?
>Странное решение они используют... может быть, просто для того, чтобы избежать неверного
>сравнивания адресов вида 1.2.3.4 и 001.002.003.004?
>Потому что в perl'e расходы на преобразования будут намного больше, чем сравнение
>строк.
>Если мне склероз не изменяет, в perl'e все хранится в виде строк,
>так что нет нужды переводить из строки в числа.Быстрее происходит сарвнение больше/меньше, наприер для определения принадлежности ip к диапозону. И, наприпер, в таком виде удобнее хранить в sqlite базе (маньше места, быстрее выборки, если везде - integer).
>Ну, а если уж очень хочется, то может так красивше?
>sub from_num {
> my $n = shift;
> return int($n/16777216).".".int($n777216/65536).".".
> int($ne536/256).".".int($n%6)
>}
Сасибо. Замеров скорости не проводил, но на взгляд это должно работать быстрее.>Да, забыл написать, что может еще правильнее будет использовать inet_aton/inet_ntoa
>из пакета Socket?
он возвращет "opaque string (if programming in C, struct in_addr)" а, это для мого случая не подходит.
sub from_num {
return pack("C4", @_);
}Только оно (как и твой алгоритм) у тебя не будет работать на другой архитектуре.
>sub from_num {
> return pack("C4", @_);
>}
на архитекту, отличной от x86? можете объяснить подробнее?
>на архитекту, отличной от x86? можете объяснить подробнее?Проблема в том, как выглядит число, скажем 256, в двоичном виде.
Его можно записать как 00000001 00000000 (старший байт в младшем адресе), либо как 00000000 00000001 (младший байт в младшем адресе). В архитектуре DEC (и последователях вроде SPARC) первым идёт старший байт (big endian). В архитектуре IBM (и последователях вроде x86) первым идёт младший байт (little endian).
В результате адрес 192.1.2.3 на x86 выглядит как 0x030201C0, а на SPARC как 0xC0010203. inet_ntoa и inet_aton на любой архитектуре преобразуют строку в "правильное сетевое" число (в действительности big endian).
Самодельные функции должны пользоваться системными вызовами htonl и ntohl, чтобы делать так же.
>sub from_num {
> return pack("C4", @_);
>}
>
>Только оно (как и твой алгоритм) у тебя не будет работать на
>другой архитектуре.
ну, у тебя получится строка из 4 байт
а человеку надо число..
так чтоperl -e 'printf("result:%X\n",unpack("N",pack("C4",split(/\./,$ARGV[0]))))' 254.128.64.32
result:FE804020и вполне себе портабельно :)
А такой вариант?
Полагаю, что операции с побитовыми сдвигами и AND'ами НАМНОГО быстрее работают.sub ipToNum(){
my $sIP = shift;
my $res;
$res=($1<<24)+($2<<16)+($3<<8)+$4 if $sIP =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
$res=($1<<24)+($2<<16)+($3<<8) if $sIP =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
$res=($1<<24)+($2<<16) if $sIP =~ m/^(\d{1,3})\.(\d{1,3})$/;
$res=($1<<24) if $sIP =~ m/^(\d{1,3})$/;
return $res;
}sub ipToStr(){
my $nIP = shift;
my $res = (($nIP>>24) & 255) .".". (($nIP>>16) & 255) .".". (($nIP>>8) & 255) .".". ($nIP & 255);
return $res;
}
Вот результат тестов на скорость:
To numeric
Original: 4.419041
By sashacrane: 8.723555From numeric
Original: 6.641646
By madskull: 3.943649
By sashacrane: 3.610706
А вот если эти строки убрать будет бустрее, но функция будет работать только для полного IP.
$res=($1<<24)+($2<<16)+($3<<8) if $sIP =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
$res=($1<<24)+($2<<16) if $sIP =~ m/^(\d{1,3})\.(\d{1,3})$/;
$res=($1<<24) if $sIP =~ m/^(\d{1,3})$/;
не сильно лучше стало.
To numeric
Original: 3.930417
By sashacrane: 4.403279PS кто-нибуть знает имет-ли спысл учитывать накладные расходы на вызов процедуры?
ip to num на миллион адресов:вариант rWizard - 7.240
вариант твой - 11.223
вариант мой - 5.556sub ipToNum(){
my $sIP = shift;
return unpack("N",pack("C4",split(/\./,$sIP)));
}
еще чуть-чуть можно ускорить(4.820), убрав двойные кавычки и не используя локальную переменную:sub ipToNum(){
return unpack('N',pack('C4',split(/\./,$_[0])));
}
проблемма в том, что
# perl -e'print unpack('N',pack('C4',split(/\./,'127.0.0.1')));print"\n"'
0или я что-то не понял?
что возвращает твоя процедура?
>проблемма в том, что
># perl -e'print unpack('N',pack('C4',split(/\./,'127.0.0.1')));print"\n"'
>0
>
>или я что-то не понял?
>что возвращает твоя процедура?
$ perl -e 'print unpack("N",pack("C4",split(/\./,"127.0.0.1"))) ."\n"'
2130706433на кавычки посмотри...
ты ж в командной строке делаешь, perl -e ' ... '
>ip to num на миллион адресов:
>
>вариант rWizard - 7.240
>вариант твой - 11.223
с убранными строками (только для полного ip) вариант sashacrane - 7.203
в итоге:
To numeric
By rWizard: 4.448895
By qq: 2.827952
By sashacrane: 5.078883From numeric
By rWizard: 5.27202
By madskull: 3.300532
By sashacrane: 2.67985те, самые быстрые
sub ipToNum($){
return unpack('N',pack('C4',split(/\./,$_[0])));
}sub ipToStr(){
my $nIP = shift;
my $res = (($nIP>>24) & 255) .".". (($nIP>>16) & 255) .".". (($nIP>>8) & 255) .".". ($nIP & 255);
return $res;
}Спасибо всем участвавашим.
>те, самые быстрые
>sub ipToNum($){
> return unpack('N',pack('C4',split(/\./,$_[0])));
>}
>
>sub ipToStr(){
> my $nIP = shift;
> my $res = (($nIP>>24) & 255) .".". (($nIP>>16) & 255) .".". (($nIP>>8) & 255) .".". ($nIP & 255);
> return $res;
>}
>
>Спасибо всем участвавашим.ну, самыс быстрым ip_to_num всеже будет использование inet_aton из модуля Socket.
use Socket;
sub ip_to_num {
return unpack('N',inet_aton($_[0]));
}
однако num_to_ip все же быстрее с использованием сдвигов, как сделал sashacrane - т.к. при использовании inet_ntoa надо сначала число перевести в строку из 4 байт, чтобы inet_ntoa это скушал. Если не делать этот pack - то разница исчезает.