我在很长的UTF-8字符串上使用substr
(~250,000,000个字符)。
事情是我的程序几乎冻结了200,000,000个角色。
有人知道这个问题吗?我有什么选择?
当我使用后缀数组索引文档时,我需要:
至于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";
答案 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";