Perl:使用'splice()'与链表的数组插入性能比较

scyqe7ek  于 2022-11-15  发布在  Perl
关注(0)|答案(4)|浏览(167)

我有一个使用Perl数组的脚本,每个数组包含几十万个元素。
我经常需要在数组中间动态添加项目,或者从中删除项目。

我想知道是否应该使用链表而不是Perl数组,因为我经常进行插入和删除

所以我的问题是:

  • splice()是如何实现的?
  • 当用于将项x插入Perl数组中的索引i时,splice()的复杂性是多少
  • 你能推荐一个你使用过的Perl链表模块吗?

谢谢你!

dldeef67

dldeef671#

Perl数组存储为指针数组、起始偏移量、长度和分配的长度。
所以从中间插入或删除需要移动4或8个字节乘以数组中后面的元素数。从两端删除不需要移动任何东西,只需要调整开始的偏移量或长度。在末尾插入通常只需要调整长度,但偶尔需要重新分配整个指针数组。在开头插入,perl将尽最大努力进行安排,以便只需要调整开始的偏移量,但有时需要移动甚至重新分配整个数组。
在实践中,使用perl操作创建和管理链表的开销几乎在每种情况下都要比仅仅使用数组大得多。
为了确定基准,我们需要更多地了解您的具体情况;数组的实际大小、元素的种类和大小(与拼接成本无关,但可能与链表有关)、插入/删除的相对频率等。

hl0ma9xz

hl0ma9xz2#

做了一个快速的拼接基准测试,它似乎表现为O(N)的删除和插入。
脚本:

my $length = shift;
my $toSplice = 100;

my @list = (1 .. $length);

my $t0 = Time::HiRes::time();
for(1 .. $toSplice) {
    my $removeIdx = int(rand() * @list);
    splice @list, $removeIdx, 1;
}

my $t1 = Time::HiRes::time();
for(1 .. $toSplice) {
    my $insertIdx = int(rand() * @list);
    splice @list, $insertIdx, 0, 0;
}

printf("Took %.4fs to remove\n", $t1 - $t0);
printf("Took %.4fs to insert\n", Time::HiRes::time() - $t0);

结果:

$ perl test.pl 100000
Took 0.0026s to remove
Took 0.0092s to insert
$ perl test.pl 1000000
Took 0.0296s to remove
Took 0.0617s to insert
$ perl test.pl 10000000
Took 0.2876s to remove
Took 0.6252s to insert

因此,迭代次数增加10倍,运行时间大约增加10倍。

lsmepo6l

lsmepo6l3#

数组和链表的基准测试存在缺陷。数组方法可以使用以下方法加速:
1.创建一个标量数组来代替多余的哈希引用数组,以匹配链表。
这将执行速度提高了4倍。
1.由于您只是对列表执行一次传递,因此创建一个新列表,而不是尝试拼接旧列表。
这将使速度提高10倍。
当然,这会使内存加倍,但使用链表至少会使内存增加5倍。
下面的基准测试显示了这两个改进。我还简化了链表功能,但是即使对这两个功能都进行了改进,数组方法的速度仍然是原来的两倍。

use strict;
use warnings;

use Benchmark;

my $INSERTION_FREQUENCY = 5;

my $num_of_items = shift or die "Specify size of list\n";

timethese(10, {
    'linked_list'  => sub { linked_list($num_of_items) },
#   'array_splice' => sub { array_splice($num_of_items) },
    'array_map'    => sub { array_map($num_of_items) },
});

sub linked_list {
    my $count = shift;

    my $curr_node = my $list_head = {data => 1};

    # Creating List 
    for my $i (2 .. $num_of_items) {
        $curr_node = $curr_node->{next} = {
            data => $i,
            prev => $curr_node,
        };
    }

    # Inserting Items
    $curr_node = $list_head;
    my $i = 0;
    while ($curr_node) {
        if (++$i % $INSERTION_FREQUENCY == 0) {
            my %new_node = (
                data => "inserted",
                prev => $curr_node->{"prev"},
                next => $curr_node,
            );
            $curr_node->{"prev"}{"next"} = \%new_node if $curr_node->{"prev"};
            $curr_node->{"prev"} = \%new_node;
        }
        $curr_node = $curr_node->{"next"};
    }

    return $list_head;
}

sub array_splice {
    my $num_of_items = shift;

    # Creating Array
    my @array = (1..$num_of_items);

    # Inserting Items
    for my $i (1 .. $num_of_items) {
        if ($i % $INSERTION_FREQUENCY == 0) {
            splice(@array, $i - 1, 0, "inserted");
        }
    }

    return \@array;
}

sub array_map {
    my $num_of_items = shift;

    # Creating Array
    my @array = (1..$num_of_items);

    # Inserting Items
    my $i = 0;
    @array = map {
        ++$i % $INSERTION_FREQUENCY == 0 ? ("inserted", $_) : $_
    } @array;

    return \@array;
}

基准测试

$ perl arrays.pl 100000
Benchmark: timing 10 iterations of array_map, array_splice, linked_list...
 array_map:  1 wallclock secs ( 0.58 usr +  0.01 sys =  0.59 CPU) @ 16.89/s (n=10)
array_splice: 16 wallclock secs (16.21 usr +  0.00 sys = 16.21 CPU) @  0.62/s (n=10)
linked_list:  2 wallclock secs ( 1.43 usr +  0.09 sys =  1.53 CPU) @  6.54/s (n=10)

$ perl arrays.pl 200000
Benchmark: timing 10 iterations of array_map, array_splice, linked_list...
 array_map:  1 wallclock secs ( 1.20 usr +  0.05 sys =  1.25 CPU) @  8.01/s (n=10)
array_splice: 64 wallclock secs (64.10 usr +  0.03 sys = 64.13 CPU) @  0.16/s (n=10)
linked_list:  3 wallclock secs ( 2.92 usr +  0.23 sys =  3.15 CPU) @  3.17/s (n=10)

$ perl arrays.pl 500000
Benchmark: timing 10 iterations of array_map, linked_list...
 array_map:  4 wallclock secs ( 3.12 usr +  0.36 sys =  3.48 CPU) @  2.87/s (n=10)
linked_list:  8 wallclock secs ( 7.52 usr +  0.70 sys =  8.22 CPU) @  1.22/s (n=10)
zlwx9yxi

zlwx9yxi4#

我还做了一个基准测试,并希望与您分享结果。
在我得到的结果中,链表比Perl数组快得多
这是我做的基准测试:
1.创建了一个链接列表或一个包含100万个条目的数组
1.迭代列表/数组并在适当位置插入200 K
1.检查每个场景所需的时间。

链接列表:2秒
Perl数组:1分55秒

我与您分享代码:
运行命令和结果:

> time perl_benchmark.pl list 1000000
1.876u 0.124s 0:02.01 99.0%     0+0k 0+0io 0pf+0w
> time perl_benchmark.pl array 1000000
115.159u 0.104s 1:55.27 99.9%   0+0k 0+0io 0pf+0w

源代码:

my $INSERTION_FREQUENCY = 5;

my $use_list = $ARGV[0] eq "list";
my $num_of_items = $ARGV[1];

my $list_header;
my $list_tail;

my @array;

# Creating List or Array
for (my $i = 0 ; $i < $num_of_items ; $i++) {
    my %new_node;
    $new_node{"data"} = $i;
    if ($use_list) {        
        if (! defined($list_header)) {
            $list_header = $list_tail = \%new_node;
        } else {
            $new_node{"prev"} = $list_tail;
            $list_tail->{"next"} = \%new_node;          
            $list_tail = \%new_node;
        }
    } else {
        push(@array, \%new_node);
    }
}

# Inserting Items
my $curr_node = $list_header;
for (my $i = 1 ; $i < $num_of_items ; $i++) {
    if ($i % $INSERTION_FREQUENCY == 0) {
        my %new_node;
        $new_node{"data"} = "inserted";
        if ($use_list) {
            my $prev_ptr = $curr_node->{"prev"};
            if (defined($prev_ptr)) {
                $prev_ptr->{"next"} = \%new_node;
            }
            $new_node{"prev"} = $prev_ptr;
            $new_node{"next"} = $curr_node;
            $curr_node->{"prev"} = \%new_node

        } else {
            splice(@array, $i - 1, 0, \%new_node);
        }
    }
    if ($use_list) {
        $curr_node = $curr_node->{"next"};
    }
}

相关问题