在非常长的UTF-8字符串上使用substr的性能问题

时间:2014-07-29 01:23:54

标签: perl unicode utf-8 substring

我在很长的UTF-8字符串上使用substr(~250,000,000个字符)。 事情是我的程序几乎冻结了200,000,000个角色。

有人知道这个问题吗?我有什么选择?

当我使用后缀数组索引文档时,我需要:

  1. 将我的琴弦保持为一体;
  2. 使用索引访问可变长度子字符串。
  3. 至于MWE:

    use strict;
    use warnings;
    use utf8;
    
    my $text = 'あいうえお' x 50000000;
    
    for( my $i = 0 ; $i < length($text) ; $i++ ){
        print "\r$i";
        my $char = substr($text,$i,1);
    }
    print "\n";
    

4 个答案:

答案 0 :(得分:3)

Perl有两种​​字符串存储格式。一个能够存储8位字符,一个能够存储72位字符(实际上限制为32或64)。您的字符串必然使用后一种格式。这种宽字符格式使用每个字符可变数量的字节,如UTF-8。

以第一种格式查找字符串的 i 元素是微不足道的:将偏移量添加到字符串指针。使用第二种格式,找到 i 字符需要从头开始扫描字符串,就像您必须从头开始扫描文件以找到 n 行。有一种机制可以缓解有关字符串的信息,但它并不完美。

如果每个字符使用固定数量的字节,问题就会消失。

use utf8;

use Encode qw( encode );

my $text = 'あいうえお' x 50000000;

my $packed = encode('UCS-4le', $text);
for my $i (0..length($packed)/4) {
    print "\r$i";
    my $char = chr(unpack('V', substr($packed, $i*4, 4)));
}

print "\n";

请注意,字符串将为平假名字符使用33%的内存。或许不是,因为不再有缓存了。

答案 1 :(得分:2)

这是Perls 5.20.0的Bugs下列出的已知问题:

http://perldoc.perl.org/perlunicode.html#Speed
最重要的部分是我引用的第一段:

  

速度

     

使用UTF-8编码的字符串时某些函数比字节编码的字符串慢。当底层数据进行字节编码时,所有需要跳过length()substr()index()等字符或匹配正则表达式的函数都可以更快地工作。

     

在Perl 5.8.0中,速度往往相当惊人;在Perl 5.8.1中引入了缓存方案,这有望使缓慢程度不那么引人注目,至少对某些操作而言如此。通常,使用UTF-8编码字符串的操作仍然较慢。作为一个例子,已知像\p{Nd}这样的Unicode属性(字符类)比它们更简单的对象(如\d慢得多(5-20​​倍)(然后再有数百个Unicode)匹配Nd的字符与匹配d)的10个ASCII字符相比较。

避免它的最简单方法是使用字节字符串而不是unicode-strings。

答案 2 :(得分:1)

我建议您使用正则表达式而不是substr

Benchmarking这两种方法表明正则表达式快了近100倍:

use strict;
use warnings;
use utf8;

my $text = 'あいうえお' x 50_000;

sub mysubstr {
    for( my $i = 0 ; $i < length($text) ; $i++ ){
        my $char = substr($text,$i,1);
    }
}

sub myregex {
    while ($text =~ /(.)/g) {
        my $char = $1;
    }
}

use Benchmark qw(:all) ;

timethese(10, {
    'substr' => \&mysubstr,
    'regex'  => \&myregex,
});

输出:

Benchmark: timing 10 iterations of regex, substr...
     regex:  2 wallclock secs ( 2.18 usr +  0.00 sys =  2.18 CPU) @  4.58/s (n=10)
    substr: 198 wallclock secs (184.66 usr +  0.16 sys = 184.81 CPU) @  0.05/s (n=10)

答案 3 :(得分:0)

在您的特定示例中,您可以在处理它们时从$text字符串的开头删除字符,以避免线性查找:

use utf8;
use Encode qw( encode );
$| = 1;
my $text = 'あいうえお' x 50000000;

while ($text ne '') {
    print ".";
    my $char = substr($text, 0, 1, '');
}
print "\n";