BaseX 中的基准测试:如何设置

Bra*_*roy 5 perl benchmarking xquery basex

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

在项目开始时 - 在我参与该项目之前 - 该工具仅限于相当小的语料库(最多 900 万字)。目标是使大量文本也可搜索。我们谈论的是 +- 5 亿字。已经进行了尝试,理论上应该通过减少搜索空间来提高速度(请参阅本文),但这尚未经过测试。这种尝试的结果是一个新的文件结构。与未处理的结构 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();
}
Run Code Online (Sandbox Code Playgroud)

(注意,这是一个精简版,它可能无法工作原样。这个片段并没有使用结构B!)

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

首先,我将从向该脚本添加分析器开始。我想那一点是显而易见的。但是,我不确定我应该如何开始比较结构 A 和 B。我是否将两个查询(到不同的数据库)放在单独的脚本中,然后在两者上调用分析器,并多次运行这两个脚本并获得平均值并比较?或者我会几乎同时在同一个脚本中运行两个数据库的每个查询?

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

(阅读 benchmark-tag 的描述,我相信这 - 尽管很详细 - 帖子适合 SO。)

C. *_*een 1

一项可能的改进是:最大限度地减少将控制权从 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
Run Code Online (Sandbox Code Playgroud)

要不就

42
Run Code Online (Sandbox Code Playgroud)

将 BaseX 时间推至尽可能接近零;用户发起查询和显示响应之间的测量时间是每个查询的开销。(有趣的问题:应该使用许多不同的简单表达式来防止缓存结果,还是应该一遍又一遍地使用相同的表达式来鼓励缓存结果?我们如何才能确保 BaseX 会缓存结果,但是网络服务器不会?...)

最后的建议:请记住,其他需要进行基准测试的人通常会遇到与您相同的问题。这意味着您可以重新表述“我应该做 X 还是 Y?”形式的每个问题。形式为“X 和 Y 之间的差异对基准测试结果有什么可测量的影响?” 运行一些测试来尝试衡量该效果,并将其写下来。(我总是发现,如果我在提出问题之后但在测量差异之前强迫自己做出预测,就会变得更有趣。)