sub packwithsubst {
my $string = shift;
my $bits = unpack 'b*', $string;
$bits =~ s/(.{7})./$1/g;
return pack 'Cb*', length($string), $bits;
}
sub unpackwithsubst {
my ($length, $chars) = unpack 'Cb*', shift;
$chars =~ s/(.{7})/${1}0/g;
substr $chars, $length * 8, 8, '';
return pack 'b*', $chars;
}
####
Rate GSM::SMS
GSM::SMS 324/s --
Device::Gsm 342/s 6%
My_code 514/s 59%
####
my (%b2c, %c2b);
$b2c{unpack 'b8', $_} = $_ for map chr, 0..255;
$c2b{$_} = unpack 'b7', $_ for map chr, 0..127;
sub packdevicegsm2 { # Device::Gsm::Pdu:encode_text7, not going via hex
my @char = split //, shift;
my($result, $bits);
map { $bits .= $c2b{$_} } @char;
if (my $len = length($bits) % 8) { $bits .= '0' x (8 - $len) }
$result .= $b2c{ substr $bits, 0, 8, '' } while length $bits;
return chr(scalar @char) . $result;
}
sub unpackdevicegsm2 { # Device::Gsm::Pdu:decode_text7, not going via hex
my $text7 = shift;
my $len = ord substr( $text7, 0, 1, '' );
my $bits = '';
$bits .= unpack 'b8', substr $text7, 0, 1, '' while length $text7;
my $decoded = '';
$decoded .= pack 'b7', substr $bits, 0, 7, '' while length $bits >= 7;
return substr $decoded, 0, $len;
}
####
Rate GSM::SMS
GSM::SMS 322/s --
Device::Gsm 345/s 7%
My_code 519/s 61%
Dev::Gsm_opt 519/s 61%
####
sub packwitharray {
my $string = shift;
return pack 'Cb*', length($string), join('', unpack '(b7)*', $string);
}
sub unpackwitharray {
my ($length, $bits) = unpack 'Cb*', shift;
my @chars = $bits =~ /.{7}/g;
$#chars = $length - 1;
return pack '(b7)*', @chars;
}
####
Rate GSM::SMS
GSM::SMS 321/s --
Device::Gsm 342/s 7%
Dev::Gsm_opt 500/s 56%
Subst/Subst 519/s 62% # This was called My_code above
Subst/Array 527/s 64% # i.e. packwithsubst / unpackwitharray
Array/Subst 777/s 142%
Array/Array 808/s 152%
####
Rate My_code C
My_code 792/s -- -99%
C 77422/s 9670% --
####
#! /usr/bin/perl
use strict;
use warnings;
use Benchmark 'cmpthese';
use 5.010;
use Inline 'C';
my $sample = <<'END_STRING';
To be or not to be: that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune
Or to take arms against a sea of troubles
And by opposing, end them?
To die: to sleep
No more ...
END_STRING
my %packs = (
C => \&c_pack,
subst => \&packwithsubst,
array => \&packwitharray,
gsp => \&packgsmsms,
dgp => \&packdevicegsm,
dgp2 => \&packdevicegsm2,
);
my %unpacks = (
C => \&c_unpack,
subst => \&unpackwithsubst,
array => \&unpackwitharray,
gsp => \&unpackgsmsms,
dgp => \&unpackdevicegsm,
dgp2 => \&unpackdevicegsm2,
);
my @pairs = (
['gsp', 'gsp' ],
['dgp', 'dgp' ],
['dgp2', 'dgp2' ],
['subst', 'subst'],
['subst', 'array'],
['array', 'subst'],
['array', 'array'],
['C', 'C' ],
);
# Check that all implementations work and are equivalent.
for my $impl (keys %packs) {
next if $unpacks{array}->($packs{$impl}->($sample)) eq $sample;
say "$impl packer is broken";
say "Expected: ", packwithsubst("foo bar baz quux");
say "Got: ", $packs{$impl}->("foo bar baz quux");
exit 1;
}
for my $impl (keys %unpacks) {
next if $unpacks{$impl}->($packs{array}->($sample)) eq $sample;
say "$impl unpacker is broken";
say "Expected: foo bar baz quux";
say "Got: ", $unpacks{$impl}->(packwitharray("foo bar baz quux"));
exit 1;
}
# Do the benchmark.
cmpthese(-1, {
map {
my $p = $packs{$$_[0]};
my $u = $unpacks{$$_[1]};
"$$_[0]/$$_[1]" => sub { $u->($p->($sample)) };
} @pairs
});
sub packwithsubst {
my $string = shift;
my $bits = unpack 'b*', $string;
$bits =~ s/(.{7})./$1/g;
return pack 'Cb*', length($string), $bits;
}
sub packwitharray {
my $string = shift;
return pack 'Cb*', length($string), join('', unpack '(b7)*', $string);
}
sub unpackwitharray {
my ($length, $bits) = unpack 'Cb*', shift;
my @chars = $bits =~ /.{7}/g;
$#chars = $length - 1;
return pack '(b7)*', @chars;
}
sub unpackwithsubst {
my ($length, $chars) = unpack 'Cb*', shift;
$chars =~ s/(.{7})/${1}0/g;
substr $chars, $length * 8, 8, '';
return pack 'b*', $chars;
}
sub unpackdevicegsm2 { # Device::Gsm::Pdu:decode_text7, not going via hex
my $text7 = shift;
my $len = ord substr( $text7, 0, 1, '' );
my $bits = '';
while( length $text7 ) {
$bits .= unpack 'b8', substr $text7, 0, 1, '';
}
my $decoded = '';
while( length $bits >= 7 ) {
$decoded .= pack 'b7', substr($bits, 0, 7, '');
}
return substr $decoded, 0, $len;
}
BEGIN {
my (%b2c, %c2b);
$b2c{unpack 'b8', $_} = $_ for map chr, 0..255;
$c2b{$_} = unpack 'b7', $_ for map chr, 0..127;
sub packdevicegsm2 { # Device::Gsm::Pdu:encode_text7, not going via hex
my @char = split //, shift;
my($result, $bits);
map { $bits .= $c2b{$_} } @char;
if( my $len = length($bits) % 8 ) {
$bits .= '0' x ( 8 - $len );
}
while( length $bits ) {
$result .= $b2c{ substr $bits, 0, 8, '' };
}
return chr(scalar @char) . $result;
}
}
sub unpackdevicegsm { # Device::Gsm::Pdu:decode_text7, cosmetic changes only
my $text7 = unpack 'H*', shift;
my $len = hex substr( $text7, 0, 2 );
$text7 = substr $text7, 2;
my $bits = '';
while( $text7 ) {
$bits .= unpack 'b8', pack 'H2', substr $text7, 0, 2;
if( length($text7) > 2 ) {
$text7 = substr($text7, 2);
} else {
$text7 = '';
}
}
my $decoded;
while( length $bits >= 7 ) {
$decoded .= pack 'b7', substr($bits, 0, 7);
$bits = substr $bits, 7;
}
return substr $decoded, 0, $len;
}
BEGIN {
my (%b2h, %h2b);
$b2h{unpack 'b8', $_} = unpack 'H2', $_ for map chr, 0..255;
$h2b{$_} = unpack 'b7', $_ for map chr, 0..127;
sub packdevicegsm { # Device::Gsm::Pdu:encode_text7, cosmetic changes only
my($result, $bits);
my @char = split //, $_[0];
map { $bits .= $h2b{$_} } @char;
if( my $len = length($bits) % 8 ) {
$bits .= '0' x ( 8 - $len );
}
while( length $bits ) {
$result .= $b2h{ substr $bits, 0, 8 };
$bits = substr $bits, 8;
}
return chr(scalar @char) . pack 'H*', $result;
}
}
sub unpackgsmsms { # Based on GSM::SMS::PDU::decode_7bit
my ($ud) = @_;
my ($msg,$bits);
my $cnt = 0;
my $len = ord substr $ud, 0, 1, '';
$msg = "";
my $byte = unpack('b8', substr($ud, 0, 1));
while (($cnt0)) {
$octet = substr($bits,0,8);
$ud .= pack("b8", substr($octet."0" x 7, 0, 8));
$bits = (length($bits) > 8) ? substr($bits, 8) : "";
}
return chr(length $msg) . $ud;
}
__DATA__
__C__
#include
typedef unsigned char uchr;
SV* c_pack(SV* input)
{
SV* rv;
int len, ipos;
uchr *bytes = (uchr*) SvPV(input, len);
int newlen = ceil((len / 8.) * 7) + 1;
uchr *packed = (uchr*) calloc(newlen, 1);
int opos = 1, oset = 0;
packed[0] = len;
for (ipos = 0; ipos < len; ++ipos) {
uchr c1 = bytes[ipos];
uchr c2 = bytes[ipos + 1];
if (c1 & 0x80) croak("Data must be 7-bit");
packed[opos++] = c1 >> oset | c2 << 7 - oset;
if (++oset == 7) { oset = 0; ++ipos; }
}
rv = newSVpv((char*) packed, newlen);
free(packed);
return rv;
}
SV* c_unpack(SV* input)
{
SV* rv;
int len;
uchr *bytes = (uchr*) SvPV(input, len);
int newlen = bytes[0];
uchr *unpacked = (uchr*) calloc(newlen, 1);
int ipos, opos = 0, oset = 0;
uchr carry = 0;
for (ipos = 1; ipos < len; ++ipos) {
uchr ch = bytes[ipos];
unpacked[opos++] = carry | (ch & (0xff >> oset + 1)) << oset;
if (oset == 6) {
unpacked[opos++] = ch >> 1;
carry = oset = 0;
}
else {
carry = ch >> 7 - oset++;
}
}
rv = newSVpv((char*) unpacked, newlen);
free(unpacked);
return rv;
}