在BaseX中进行基准测试:如何设置

时间:2016-02-21 12:46:11

标签: perl xquery benchmarking basex

目前,我是一个研究小组的实习生,可以搜索大量文本(语料库)。不仅可以搜索文字字符串,更重要的是,还可以查找与给定输入类似的语法依赖结构,而无需精通任何编程语言或语料库注释样式。很明显,这个工具是针对语言学家的。

在项目开始时 - 在我参与项目之前 - 该工具仅限于相当小的语料库(最多900万字)。目标是使大量文本也可搜索。我们正在讨论+ - 5亿字。已经尝试过,理论上应该通过减少搜索空间来提高速度(参见this paper),但尚未经过测试。此尝试的结果是一个新的文件结构。与非处理结构A相比,我们将此结构称为B.我们希望B在使用BaseX查询时提供更快的结果。

我的问题是:用Perl脚本测试和比较这两种方法的最佳方法是什么?您可以在下面找到我当前在本地查询BaseX的脚本。它需要两个参数。存储不同文件的目录。这些文件分别存储XPath。那些XPath是我选择用于基准测试的那些。第二个参数是要返回的结果的限制。设置为零时,不设置限制。

由于数据集的某些部分非常庞大,我们将它们分成不同的,大小相同的文件,称为treebankparts。它们存储在<tb>内的treebankparts.lst标记中。

#!/usr/bin/perl

use warnings;

$| = 1;    # flush every print

# Directory where XPaths are stored
my $directory = shift(@ARGV);

# Set limit. If set to zero all results will be returned
my $limit = shift(@ARGV);

# Create session, connect to BaseX
my $session = Session->new([INFORMATION WITHHELD]);

# List all files in directory
@xpathfiles = <$directory/*.txt>;

# Read lines of treebank parts into variable
open( my $tfh, "treebankparts.lst" ) or die "cannot open file treebankparts.lst";
chomp( my @tlines = <$tfh> );
close $tfh;

# Loop through all XPaths in $directory
foreach my $xpathfile (@xpathfiles) {
    open( my $xfh, $xpathfile ) or die "cannot open file $xpathfile";
    chomp( my @xlines = <$xfh> );
    close $xfh;

    print STDOUT "File = $xpathfile\n";

    # Loop through lines from XPath file (= XPath query)
    foreach my $xline (@xlines) {
        # Loop through the lines of treebank file
        foreach my $tline (@tlines) {
            my ($treebank) = $tline =~ /<tb>(.+)<\/tb>/;
            QuerySonar( $xline, $treebank );
        }
    }
}
$session->close();

sub QuerySonar {
    my ( $xpath, $db ) = @_;

    print STDOUT "Querying $db for $xpath\n";
    print STDOUT "Limit = $limit\n";
    my $x_limit;
    my $x_resultsofxp = 'declare variable $results := db:open("' . $db . '")/treebank/alpino_ds'
      . $xpath . ';';
    my $x_open       = '<results>';
    my $x_totalcount = '<total>{count($results)}</total>';
    my $x_loopinit   = '{for $node at $limitresults in $results';

    # Spaces are important!
    if ( $limit > 0 ) {
        $x_limit = ' where $limitresults <= ' . $limit . ' ';
    }
    # Comment needed to prevent `Incomplete FLWOR expression`
    else { $x_limit = '(: No limit set :)'; }

    my $x_sentenceinfo = 'let $sentid := ($node/ancestor::alpino_ds/@id)
        let $sentence := ($node/ancestor::alpino_ds/sentence)
        let $begin := ($node//@begin)
        let $idlist := ($node//@id)
        let $beginlist := (distinct-values($begin))';

    # Separate sentence info by tab
    my $x_loopexit = 'return <match>{data($sentid)}&#09;
        {string-join($idlist, "-")}&#09;
        {string-join($beginlist, "-")}&#09;
        {data($sentence)}</match>}';
    my $x_close = '</results>';

    # Concatenate all XQuery parts
    my $x_concatquery =
        $x_resultsofxp
      . $x_open
      . $x_totalcount
      . $x_loopinit
      . $x_limit
      . $x_sentenceinfo
      . $x_loopexit
      . $x_close;

    my $querysent = $session->query($x_concatquery);

    my $basexoutput = $querysent->execute();
    print $basexoutput. "\n\n";

    $querysent->close();
}

(请注意,这是一个精简版本,它可能无法正常工作。此代码段使用结构B!)

发生的事情是:遍历所有XPath文件,遍历XPath文件中的每一行,遍历所有treebankparts然后执行sub。然后子查询BaseX。这归结为向BaseX发送新的XQuery,并返回总命中和结果(可能受Perl脚本中的参数限制)。所以我明白了,但现在的问题是:如何改进这个脚本,以便我可以从中得到一些基准测试结果。

首先,我开始在此脚本中添加探查器。我猜这一点很明显。但是,我不确定应该如何开始将结构A与B进行比较。我会将两个查询(到不同的数据库)放在不同的脚本中,然后在两者上调用一个探查器,并多次运行这两个脚本并得到一个平均值并比较?或者我会在同一个脚本中同时运行两个数据库的每个查询吗?

考虑正在发生的缓存非常重要。因此,我不完全确定这个巨大的数据库基准测试的构建是否合适。第一个脚本,然后是另一个。两者同时进行。两者之间的交替查询。等等。有这么多的可能性,但我想知道哪个会提供最好的结果。另外,我会重复这个过程几次。我会重复每个查询然后继续下一个查询,还是完成所有XPath文件,然后再重复整个过程?

(阅读基准标签的描述我相信这个 - 虽然精心制作 - 帖子适合SO。)

2 个答案:

答案 0 :(得分:1)

我们必须在这里分离几件事:第一个问题是BaseX性能不应该与你的perl脚本混淆,因为你的perl脚本似乎只是构造一个XQuery(而不是你在问题和标签中建议的XPath) )。因此,对于测试,我建议使用一些适用于您的实际场景的已经预先定义的XQueries,因为您的XQuery构造应该可以忽略不计。如何将查询传递给BaseX,因此通过Perl API或通过任何其他方式不应该相关。即使您的perl性能相关,也应该单独测试性能。

因此,您是否应该将测试两个场景放在同一个脚本中的原始问题不再相关。相反,您只需在没有perl脚本的情况下自行为场景A和B执行两个单独的XQueries。

担心缓存是正确的,但是Java JIT编译器很可能与此相关(因为BaseX是用java编写的,JIT和使用缓存,而不是BaseX本身。因此,您应该使用Client /服务器基础架构并具有长时间运行的服务器,并在运行性能测量之前进行预热。

关于性能:BaseX GUI和命令行已经包含一个测量(使用命令行可以设置-V以获得解析,编译,评估和打印的运行时间)。此外,使用-r参数,您可以多次执行查询,它将为您提供平均执行时间。

通常,如果要提高脚本的性能,应该查看查询计划和优化的查询,并检查是否使用了适当的索引。此外,我们的新Selective Indexing可能对您非常有用。如果未使用索引,则查询肯定不会在5亿字的情况下表现良好。

完全披露:我与BaseX团队合作,您可能会在BaseX mailing list获得更好的帮助,或者可能想要参考这些问题,因为我们的首席架构师并没有像ML那样定期观看。

答案 1 :(得分:0)

一种可能的改进:最小化将控制权从Perl传输到数据库的次数 - 就像您最小化数据库连接数一样。 (或者至少让自己测量转移控制的成本。)我怀疑如果你将循环转移到XQuery而不是在Perl中运行循环,你会得到明显更好的结果。

对数据库管理系统的一次调用要求它执行1000次搜索可能比每次请求单次搜索的DBMS的1000次调用快一些。第一个涉及两个上下文切换:一个从您的脚本或bash到dbms,一个返回;第二次涉及2000.上次我仔细测量这样的事情,每个上下文切换花费500毫秒;它安装得很快。 (也就是说,这是很久以前,有一个不同的数据库。但是,令人惊讶的是[并且发人深省]学习而不是我试图比较的两个查询公式之间的差异与运行测试循环之间的差异相形见绌。脚本或dbms内部。)

第二个建议:根据您的说法,数据库的大小和结果集似乎可能确保运行之间的缓存不会对结果产生很大影响。但这似乎是一个可测试的断言,值得测试。因此,设置A和B脚本,然后进行试运行:for runcount in 1 2 3 4 5; do perl A.pl; perl B.pl; done生成的结果是否与for runcount in 1 2 3 4 5; do perl A.pl; done; for runcount in 1 2 3 4 5; do perl B.pl; done相当?如果它们具有可比性,那么您有理由相信如果单独或交替运行A和B并不重要。如果它们不具有可比性,那么你知道它确实重要,这将是非常有价值的信息。在其他条件相同的情况下,我希望在继续执行下一个查询之前,在多次运行一个查询时会产生较低的缓存次数,如果只运行一次查询,则缓存未命中会产生更高的时间。可能值得一试。

本着同样的精神,我建议您使用Perl脚本中的循环和XQuery查询中的循环来运行测试。

第三个建议:实际上,语料库查询界面的查询将涉及几个阶段,每个阶段都有可测量的时间:从用户的浏览器(假设它是Web界面)传输查询到服务器,将请求转换为适合传输到后端dbms(此处为BaseX)的形式,上下文切换到BaseX,在BaseX内处理,上下文切换回,由Web服务器处理,传输给用户。至少对每个步骤所涉及的时间进行粗略估计是有用的,或者至少是所有东西 - 但是BaseX所花费的时间。

因此,如果是我运行测试,我认为我还准备了一组空的XQuery测试,这些测试是按照

进行的。
2 + 3

或只是

42

将BaseX时间尽可能接近零;用户启动查询和显示响应之间的测量时间是每个查询的开销。 (有趣的问题:是否应该使用许多不同的平凡表达式来防止缓存结果,或者应该反复使用相同的表达式,以鼓励缓存结果?我们如何尝试确保BaseX将缓存结果,但是网络服务器赢了?...)

最后的建议:请记住,其他需要进行基准测试的人通常会遇到与您相同的问题。这意味着你可以重新表达形式的每一个问题&#34;我应该做X还是Y?&#34;形式&#34; X和Y之间的差异对基准测试的结果有什么可衡量的影响?&#34;运行一些测试以尝试测量该效果,然后编写它们。 (我总是发现,如果我在制定问题之后但在测量差异之前强迫自己做出预测,那会更有趣。)