【问题标题】:How do I parse an expression '^#[0-9A-F]{2}\$[0-9A-F]{4}/$' effectively in perl如何在 perl 中有效地解析表达式 '^#[0-9A-F]{2}\$[0-9A-F]{4}/$'
【发布时间】:2020-12-25 10:09:14
【问题描述】:

我必须解析 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]  = "\0";

        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 不可用。我必须剪裁和拆分消息并执行 hex/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

我还有其他方法可以在不使用其他语言的情况下加快速度吗?

附录

根据ikepolar bear的答案和概念以及我的做法:


    #!/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%    --

【问题讨论】:

  • 使用my ($ff, $dddd) = unpack 'xA2xA4x', $msg; 比基于我的系统的substr 快几个百分点,fwiw。
  • 另请注意,您的 C 版本具有未定义的行为 - 在 sscanf() 中,您将指向 short 的指针传递给需要指向 unsigned int 的指针的格式。如果您的编译器没有对此发出警告,请打开警告选项(-Wall -Wextra 是 gcc 和 clang 的良好开端)。
  • @Shawn 感谢您的建议,所以似乎实现有错误。我发现实现中的 C 代码 sn-p 正是这种方式。我已经更正了代码。

标签: c perl parsing scanf


【解决方案1】:
^#[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);

也就是说,这比你的版本。不过,它的可读性要高得多。

以下内容和我的一样可读,和你的一样快,但没有验证:

my ($ff, $dddd) = unpack('x a2 x a4', $msg);
$dddd = hex($dddd);

也就是说,为我简单地加载 perl 需要 12 毫秒(尽管在不同的机器上只需 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%   --

【讨论】:

  • 感谢它工作正常并且在相同的运行时优雅,但是我如何补偿 sscanf %X 问题。
  • 你指的是什么问题?
  • 感谢你写的时间计算实验,很有启发。我必须处理两个问题。 1 用于传感器场的迁移(运行多个 RS485 总线)。由于 VALIDATION 功能和易于编写的网络通信功能,我想使用 perl 环境。旧的实现是在工业 PC 上编写的(运行 XP/cygwin 的 2000er)。 2. 我想将该技术/逻辑迁移到时钟速率更慢的更小的嵌入式 linux 设备上,如果我可以离开 C 编程领域,我不知道。
【解决方案2】:

在 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

perlpacktut

【讨论】:

    猜你喜欢
    • 2018-04-22
    • 2013-08-24
    • 2021-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-08
    • 2018-10-29
    相关资源
    最近更新 更多