如何在 perl 中有效地解析表达式 '^#[0-9A-F]{2}\$[0-9A-F]{4}/$'
How do I parse an expression '^#[0-9A-F]{2}\$[0-9A-F]{4}/$' effectively in perl
我必须解析 GIA10N 模块返回的数据字符串。响应字符串由 9 个字节的 ASCII 码组成,格式为
^#[0-9A-F]{2}$[0-9A-F]{4}/$
.
描述:
DIGITS 123456789
CODE #FF$DDDD/
# -- Response token
FF -- Function code 00-FF
$ -- delimiter
DDDD -- Data value 0000-FFFF
/ -- Terminal symbol
示例:
#0A$F831 -- means value -1999 from function 0A
通常 sscanf 函数会在 C 中完成解码此字符串的工作。
#include <stdio.h>
int parseData(char *data, int prn) {
char ff[3] = "[=13=]";
unsigned int udddd = 0;
int sdddd = 0;
if (sscanf(data,"#%2s$%X/",ff,&udddd) !=2) {
printf("ERROR parsing %s\n", data);
return 0;
}
sdddd = (int) udddd;
if (sdddd & 0x8000)
sdddd -= 0x10000;
if (prn)
printf("CODE:%s PARSED: %s %d\n", data, ff, sdddd);
return 1;
}
int main(int argc, char **argv)
{
parseData("#0A$F831/", 1);
return 0;
}
运行时间:
$ gcc test-scan.c -o test-scan
$ time ./test-sprintf
CODE: #0A$F831/ PARSED: -1999
real 0m0.003s
user 0m0.000s
sys 0m0.000s
在 perl 中,与格式 %X 结合使用的函数 sscanf 不可用。我必须裁剪和拆分消息并执行十六进制/16 位转换魔术,这要慢得多。
#!/usr/bin/env perl
use warnings;
use strict;
sub parseData($$) {
my ($msg, $print) = @_;
my ($ff,$dddd) = split /$/, substr($msg, 1, length($msg)-2);
$dddd = hex($dddd);
$dddd -= 0x10000 if $dddd & 0x8000;
printf "CODE: %s PARSED: %s %d\n", $msg, $ff, $dddd
if $print;
}
parseData('#0A$F831/', 1);
检查 perl 运行时:
$ time ./test-scan.pl
CODE: #0A$F831/ PARSED: 0A -1999
real 0m0.012s
user 0m0.008s
sys 0m0.000s
我有其他方法可以在不使用其他语言的情况下加快速度吗?
附录
根据 and 的答案和概念以及我的做法:
#!/usr/bin/env perl
use warnings;
use strict;
use Benchmark qw( cmpthese );
my %tests = (
ike => q{
my ($ff, $dddd) = $msg =~ m{^#([0-9A-F]{2})$([0-9A-F]{4})/\z}
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
huck => q{
(my ($ff,$dddd) = split(/$/, substr($msg, 1, length($msg)-2))) == 2
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
pbear => q{
my($ff,$dddd) = unpack('xA2xA4',$msg);
$dddd = hex($dddd);
},
);
$_ = "use strict; use warnings; our $msg; for (1..1000) { $_ }"
for values %tests;
local our $msg = '#0A$F831/';
cmpthese(-3, \%tests);
我得到了这个基准测试结果:
/usr/bin/perl -w "bench-cvx-data.pl"
Rate ike huck pbear
ike 1070/s -- -33% -41%
huck 1596/s 49% -- -12%
pbear 1816/s 70% 14% --
^#[0-9A-F]{2}$[0-9A-F]{4}/$
匹配 9 或 10 个字符。我想你的意思是
^#[0-9A-F]{2}$[0-9A-F]{4}/\z
您的代码简化为以下内容:
my ($ff, $dddd) = $msg =~ m{^#([0-9A-F]{2})$([0-9A-F]{4})/\z}
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
也就是说,这比您的版本慢。不过,它的可读性要高得多。
以下内容和我的一样可读,和你的一样快,但是 non-validating:
my ($ff, $dddd) = unpack('x a2 x a4', $msg);
$dddd = hex($dddd);
就是说,12 毫秒是我加载 perl
所需的时间(尽管在不同的机器上只有 5 毫秒)。
$ time perl -e1
real 0m0.012s
user 0m0.016s
sys 0m0.000s
是否正在尝试优化耗时不到 1 毫秒的内容?
所以我写了一个基准。对我来说,
- 您的版本耗时 1/1232000 s = 0.81 μs
- 我的版本用了 1/636000 s = 1.3 μs
基准:
use warnings;
use strict;
use Benchmark qw( cmpthese );
my %tests = (
ike => q{
my ($ff, $dddd) = $msg =~ m{^#([0-9A-F]{2})$([0-9A-F]{4})/\z}
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
huck => q{
( my ($ff,$dddd) = split /$/, substr($msg, 1, length($msg)-2) ) == 2
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
);
$_ = "use strict; use warnings; our $msg; for (1..1000) { $_ }"
for values %tests;
local our $msg = '#0A$F831/';
cmpthese(-3, \%tests);
代表产出:
Rate ike huck
ike 752/s -- -39%
huck 1232/s 64% --
在 OP 的代码中 sscanf
假定输入数据具有恒定的字段宽度。
对于这样的结构化输入数据,函数unpack
是一个很好的选择
use strict;
use warnings;
use feature 'say';
my $in = '#0A$F831/';
my($hex_func,$hex_val) = unpack('xA2xA4',$in);
my $value = hex $hex_val;
$value = (($value^0xFFFF)+1)*-1 if $value & 0x8000;
say "
Function: $hex_func
Value: $value
";
输出
Function: 0A
Value: -1999
我必须解析 GIA10N 模块返回的数据字符串。响应字符串由 9 个字节的 ASCII 码组成,格式为
^#[0-9A-F]{2}$[0-9A-F]{4}/$
.
描述:
DIGITS 123456789
CODE #FF$DDDD/
# -- Response token
FF -- Function code 00-FF
$ -- delimiter
DDDD -- Data value 0000-FFFF
/ -- Terminal symbol
示例:
#0A$F831 -- means value -1999 from function 0A
通常 sscanf 函数会在 C 中完成解码此字符串的工作。
#include <stdio.h>
int parseData(char *data, int prn) {
char ff[3] = "[=13=]";
unsigned int udddd = 0;
int sdddd = 0;
if (sscanf(data,"#%2s$%X/",ff,&udddd) !=2) {
printf("ERROR parsing %s\n", data);
return 0;
}
sdddd = (int) udddd;
if (sdddd & 0x8000)
sdddd -= 0x10000;
if (prn)
printf("CODE:%s PARSED: %s %d\n", data, ff, sdddd);
return 1;
}
int main(int argc, char **argv)
{
parseData("#0A$F831/", 1);
return 0;
}
运行时间:
$ gcc test-scan.c -o test-scan
$ time ./test-sprintf
CODE: #0A$F831/ PARSED: -1999
real 0m0.003s
user 0m0.000s
sys 0m0.000s
在 perl 中,与格式 %X 结合使用的函数 sscanf 不可用。我必须裁剪和拆分消息并执行十六进制/16 位转换魔术,这要慢得多。
#!/usr/bin/env perl
use warnings;
use strict;
sub parseData($$) {
my ($msg, $print) = @_;
my ($ff,$dddd) = split /$/, substr($msg, 1, length($msg)-2);
$dddd = hex($dddd);
$dddd -= 0x10000 if $dddd & 0x8000;
printf "CODE: %s PARSED: %s %d\n", $msg, $ff, $dddd
if $print;
}
parseData('#0A$F831/', 1);
检查 perl 运行时:
$ time ./test-scan.pl
CODE: #0A$F831/ PARSED: 0A -1999
real 0m0.012s
user 0m0.008s
sys 0m0.000s
我有其他方法可以在不使用其他语言的情况下加快速度吗?
附录
根据
#!/usr/bin/env perl
use warnings;
use strict;
use Benchmark qw( cmpthese );
my %tests = (
ike => q{
my ($ff, $dddd) = $msg =~ m{^#([0-9A-F]{2})$([0-9A-F]{4})/\z}
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
huck => q{
(my ($ff,$dddd) = split(/$/, substr($msg, 1, length($msg)-2))) == 2
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
pbear => q{
my($ff,$dddd) = unpack('xA2xA4',$msg);
$dddd = hex($dddd);
},
);
$_ = "use strict; use warnings; our $msg; for (1..1000) { $_ }"
for values %tests;
local our $msg = '#0A$F831/';
cmpthese(-3, \%tests);
我得到了这个基准测试结果:
/usr/bin/perl -w "bench-cvx-data.pl"
Rate ike huck pbear
ike 1070/s -- -33% -41%
huck 1596/s 49% -- -12%
pbear 1816/s 70% 14% --
^#[0-9A-F]{2}$[0-9A-F]{4}/$
匹配 9 或 10 个字符。我想你的意思是
^#[0-9A-F]{2}$[0-9A-F]{4}/\z
您的代码简化为以下内容:
my ($ff, $dddd) = $msg =~ m{^#([0-9A-F]{2})$([0-9A-F]{4})/\z}
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
也就是说,这比您的版本慢。不过,它的可读性要高得多。
以下内容和我的一样可读,和你的一样快,但是 non-validating:
my ($ff, $dddd) = unpack('x a2 x a4', $msg);
$dddd = hex($dddd);
就是说,12 毫秒是我加载 perl
所需的时间(尽管在不同的机器上只有 5 毫秒)。
$ time perl -e1
real 0m0.012s
user 0m0.016s
sys 0m0.000s
是否正在尝试优化耗时不到 1 毫秒的内容?
所以我写了一个基准。对我来说,
- 您的版本耗时 1/1232000 s = 0.81 μs
- 我的版本用了 1/636000 s = 1.3 μs
基准:
use warnings;
use strict;
use Benchmark qw( cmpthese );
my %tests = (
ike => q{
my ($ff, $dddd) = $msg =~ m{^#([0-9A-F]{2})$([0-9A-F]{4})/\z}
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
huck => q{
( my ($ff,$dddd) = split /$/, substr($msg, 1, length($msg)-2) ) == 2
or die("Error parsing \"$msg\"\n");
$dddd = hex($dddd);
},
);
$_ = "use strict; use warnings; our $msg; for (1..1000) { $_ }"
for values %tests;
local our $msg = '#0A$F831/';
cmpthese(-3, \%tests);
代表产出:
Rate ike huck
ike 752/s -- -39%
huck 1232/s 64% --
在 OP 的代码中 sscanf
假定输入数据具有恒定的字段宽度。
对于这样的结构化输入数据,函数unpack
是一个很好的选择
use strict;
use warnings;
use feature 'say';
my $in = '#0A$F831/';
my($hex_func,$hex_val) = unpack('xA2xA4',$in);
my $value = hex $hex_val;
$value = (($value^0xFFFF)+1)*-1 if $value & 0x8000;
say "
Function: $hex_func
Value: $value
";
输出
Function: 0A
Value: -1999