【问题标题】:Interpreting "timethese" results for search algorithms解释搜索算法的“timethese”结果
【发布时间】:2016-07-08 18:26:45
【问题描述】:

我对以下结果有点困惑,我希望你们中的一些人能够阐明为什么线性搜索似乎比 Perl 中的二进制和插值更快。

Benchmark: timing 1000000 iterations of Binary, Interpolation, Linear...
    Binary: 17 wallclock secs (16.33 usr +  0.00 sys = 16.33 CPU) @ 61236.99/s (n=1000000)
Interpolation:  4 wallclock secs ( 3.65 usr +  0.00 sys =  3.65 CPU) @ 273972.60/s (n=1000000)
    Linear:  2 wallclock secs ( 1.52 usr +  0.00 sys =  1.52 CPU) @ 657894.74/s (n=1000000)

每个功能如下。我正在尝试编写一堆众所周知的算法,并在使用 Perl 掌握算法中进行后续操作。

sub LinearSearch {
    # Search linearly for a value
    my $val = $_[0];
    my $arrptr = $_[1];

    for (my $i=0; $i<ARR_LENGTH; ++$i) {
        if ($arrptr->[$i] == $val) {
            return $i;
        }
    }

    return -1;
}


sub BinarySearch {
    my $val = $_[0];
    my $arrptr = $_[1];

    my $low = 0;
    my $high = ARR_LENGTH;  # to be modified

    while ($low <= $high) {
        my $middle = int(($low + $high) / 2);
        my $midValue = $arrptr->[$middle];

        if ($midValue < $val) {
            $low = $middle + 1;
        } elsif ($midValue > $val) {
            $high = $middle - 1;
        } else {
            return $middle;
        }
    }

    return -1;
}


sub InterpolationSearch {
    my $val = $_[0];
    my $arrptr = $_[1];

    my $low = 0;
    my $high = ARR_LENGTH;  # to be modified

    while ($val >= $arrptr->[$low] && $val <= $arrptr->[$high]) {
        # solve for the middle value again
        my $middle = int($low + ($high - $low)*(($val - @{$arrptr}[$low]) 
            / (@{$arrptr}[$high] - @{$arrptr}[$low] + 1)));

        my $middleVal = $arrptr->[$middle];

        if ($middleVal < $val) {
            $low = $middle + 1;
        } elsif ($middleVal > $val) {
            $high = $middle - 1;
        } else {
            return $middle;
        }
    }
    return -1;      # Not found
}

另外,ARR_LENGTH 定义为

use constant ARR_LENGTH => 10_000;

一开始。奇怪的是,二进制搜索需要这么长时间,然后插值更少,但仍然是线性搜索的两倍。


基准代码(就是我在网上找到的):

my @array = OrderedArray();
my $random_val = $array[int(rand(ARR_LENGTH))];
timethese(1_000_000, {
    Interpolation => 'InterpolationSearch($random_val, \@array)',
    Binary        => 'BinarySearch($random_val, \@array)',
    Linear        => 'LinearSearch($random_val, \@array)' }
);

OrderedArray() 只是一个快速(可能不必要)的功能

sub OrderedArray {
    # Create a random ordered array
    my @arr;

    for (my $i=1; $i<=ARR_LENGTH; ++$i) {
        push @arr, $i;
    }

    return @arr;
}

【问题讨论】:

  • 我从不查看没有生成它们的代码的基准。太容易出错了。请出示您的代码。
  • 提示:ARR_LENGTH 应该是 @$arrptr
  • 你的数组排序了吗?您是否计算了算法循环内发生的迭代次数?您是否在二分搜索之前对每次迭代都调用排序?
  • 您还需要显示基准测试代码。基准测试的一个常见错误是在每次迭代后更改数据而不恢复数据。
  • @JayKumarR 真的吗?我已经在我的机器上多次运行了这段代码,每一个都与上面的相似......

标签: perl search


【解决方案1】:

当您将字符串而不是子引用传递给 timethes 时,您的 @array 和 $random_val 变量不在 Benchmark 评估它的范围内。所以它实际上并没有使用您指定的数据运行。

尝试运行它:

use Benchmark 'timethese';

use strict;
use warnings 'all';

use constant ARR_LENGTH => 10000;

my @array = OrderedArray();
my $random_val = $array[int(rand(ARR_LENGTH))];
timethese(
    -5,
    {
        'Interpolation' => sub { InterpolationSearch($random_val, \@array) },
        'Binary' => sub { BinarySearch($random_val, \@array) },
        'Linear' => sub { LinearSearch($random_val, \@array) },
    }
);

启用警告会显示 InterpolationSearch 中的错误。启用警告并将 $random_val 设置为 ARR_LENGTH +1 会显示 BinarySearch 中的错误。在您担心基准测试之前,您可能会考虑编写一些测试用例并验证您的代码。

您可能更喜欢 cmpthese 而不是 timethese;我不觉得这些输出有用。

【讨论】:

  • 我完全注释掉了所有内容并得到了以下结果:Benchmark: timing 1000000 iterations of Binary, Interpolation, Linear... Binary: 3 wallclock secs ( 2.47 usr + 0.00 sys = 2.47 CPU) @ 404858.30/s (n=1000000) Interpolation: 4 wallclock secs ( 3.78 usr + 0.01 sys = 3.79 CPU) @ 263852.24/s (n=1000000) Linear: 2 wallclock secs ( 1.44 usr + 0.00 sys = 1.44 CPU) @ 694444.44/s (n=1000000) 我不知道它还在运行什么(我没有任何无序数组),但不管它做了什么,它都有效
  • 不知道您所说的“完全注释掉所有内容”是什么意思,但您的数字仍然非常快。添加代码以正确运行基准测试。
  • 谢谢,解决了。我之前可能有点匆忙,但你仍然完整地回答了这个问题。
【解决方案2】:

为什么您的时间不符合您的预期的问题已经得到解答,但我认为您可能希望看到您的三个搜索算法的更多 Perlish 实现,以及它们的时间

请注意,Benchmark 提供的所有函数都可以将负数作为其第一个参数,该参数表示运行每个基准测试的秒数。这通常是规定执行次数的更好方法,而不是猜测您是否需要 100,000 或 100 万才能获得一个体面的样本

另请注意,我已将 ARR_LENGTH 设置为 100 万

如您所料,线性搜索最慢,每秒 15 次搜索,其次是二进制搜索,每秒 117,018 次,插值搜索每秒 481,320 次

希望对你有帮助

use strict;
use warnings 'all';

use Benchmark 'timethese';

use constant ARR_LENGTH => 1_000_000;

STDOUT->autoflush;

my @array = ( 0 .. ARR_LENGTH-1 );

timethese(-10, {

    interpolation_search => sub {
        my $random_val = int rand @array;
        my $i = interpolation_search($random_val, \@array);
        die "Wrong result" unless $array[$i] == $random_val;
    },

    binary_search => sub {
        my $random_val = int rand @array;
        my $i = binary_search($random_val, \@array);
        die "Wrong result" unless $array[$i] == $random_val;
    },

    linear_search => sub {
        my $random_val = int rand @array;
        my $i = linear_search($random_val, \@array);
        die "Wrong result" unless $array[$i] == $random_val;
    },

} );


sub linear_search {

    my ($target, $array) = @_;

    for my $i ( 0 .. $#$array ) {

        return $i if $array->[$i] == $target;

        last if $array->[$i] > $target;
    }

    return;
}


sub binary_search {

    my ($target, $array) = @_;

    my $low = 0;
    my $high = $#$array;

    my ($mid, $mid_val);

    while ( $low <= $high ) {

        $mid = int(($low + $high) / 2);

        $mid_val = $array->[$mid];

        return $mid if $mid_val == $target;

        if ( $mid_val < $target ) {
            $low = $mid + 1;
        }
        else {
            $high = $mid - 1;
        }
    }

    return;
}


sub interpolation_search {

    my ($target, $array) = @_;

    my $low  = 0;
    my $high = $#$array;

    while () {

        my ($low_val, $high_val) = @{$array}[$low, $high];

        if ( $low_val == $high_val) {
            last unless $low_val == $target;
            return $low;
        }
        last if $target < $low_val or $target > $high_val;

        my $delta_i = $high     - $low;
        my $delta_v = $high_val - $low_val;

        my $mid = $low + ($target - $low_val) * $delta_i / $delta_v;
        my $mid_val = $array->[$mid];

        return $mid if $mid_val == $target;

        if ( $mid_val < $target ) {
            $low = $mid + 1;
        }
        else {
            $high = $mid - 1;
        }
    }

    return;
}

输出

Benchmark: running binary_search, interpolation_search, linear_search for at least 10 CPU seconds...
binary_search: 10 wallclock secs (10.53 usr +  0.00 sys = 10.53 CPU) @ 117018.33/s (n=1232320)
interpolation_search: 10 wallclock secs (10.39 usr +  0.00 sys = 10.39 CPU) @ 481320.50/s (n=5000920)
linear_search: 10 wallclock secs (10.05 usr +  0.00 sys = 10.05 CPU) @ 15.03/s (n=151)

【讨论】:

  • eww 为return; 而不是return undef;(或-1,如原来的那样)
  • @Borodin 值得注意的是,这是插值搜索的完美数据。在某些数据上,它可能比二分搜索慢得多
  • 谢谢,这个很有帮助!
  • @ysth:你比较奇怪的 "eww" 是否表示不赞成?裸 return 是返回失败状态的正确方法,因为它在列表和标量上下文中的计算结果均为 false。列表上下文中的return undef 返回单元素列表( undef ),即true,而return -1 的计算结果为true 在任一情况下
  • 是的,不赞成。像这样的函数,本质上要求一个标量,它应该始终返回一个标量。
【解决方案3】:
use constant ARR_LENGTH=>10000;
for $i (0..ARR_LENGTH) {
   $arr[$i]=int(rand(15000));
}
@arr = sort {$a <=> $b}(@arr);

for $i (0..ARR_LENGTH) {
   $x=int(rand(15000));
   #LinearSearch(\@arr,$x); -- uncomment this or next
   #BinarySearch(\@arr,$x);
}

我在取消注释的情况下测试了代码。结果一致:

Jay-$ vim some.pl ( uncommenting Binary Search)
Jay-$ time perl some.pl

real    0m0.096s
user    0m0.087s
sys     0m0.007s
Jay-$ time perl some.pl

real    0m0.091s
user    0m0.083s
sys     0m0.007s
Jay-$ time perl some.pl

real    0m0.100s
user    0m0.091s
sys     0m0.007s

Jay-$ vim some.pl ( uncommenting Linear Search)
Jay-$ time perl some.pl

real    0m23.163s
user    0m23.121s
sys     0m0.032s

Jay-$ time perl some.pl

real    0m22.482s
user    0m22.448s
sys     0m0.025s

结论:当 n = 10000 时,二分查找要快 200 倍。很好地匹配 o(log n)。 如果您按照指定的方式运行了 100 万次,从技术上讲,线性搜索需要 50 亿次迭代,这在您指定的 2 秒内是不可能的。还有你传递的随机数,是不是分布均匀?

【讨论】:

  • sort @array_of_integers 并没有像你认为的那样做
  • "很好的匹配 o(log n)" 这无法确定。它甚至可能 更慢 仍然是 O(log n)
  • 原始问题显示“timethese”输出。暴民试图帮助你;我认为他的措辞是为了好玩,而不是傲慢。鲍罗丁是正确的;您不能通过比较不同的算法说它与 o(log n) 匹配,您必须以这种方式显示具有不同 n 缩放比例的 same 算法。
  • perl -E '@a=(6..15);say for sort @a'。二进制搜索和插值搜索取决于被排序的输入,因此您的结果很快但错误。深吸一口气。这只是互联网点。
  • 我已要求主持人干预您的有毒 cmets。在我看来,你对自己指责他人的消极情绪要内疚得多。请缓和你的话。你的答案很差,值得被否决
猜你喜欢
  • 1970-01-01
  • 2014-10-28
  • 2017-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多