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

Benchmarking in BaseX: how to set up

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

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

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

由于数据集的某些部分非常庞大,我们也将它们分成不同的、大小相同的文件,称为 treebankparts。它们存储在 treebankparts.lst.

内的 <tb> 标签中
#!/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,然后 returning 总命中和结果(可能受 Perl 脚本中的参数限制)。所以我开始了,但现在的问题是:我如何改进这个脚本,以便从中获得一些基准测试结果。

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

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

(阅读基准标记的描述,我相信这 - 尽管详尽 - post 适合 SO。)

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

因此,您最初的问题是否应该在同一个脚本中测试这两个场景已经不相关了。相反,您只需在没有 perl 脚本的情况下自行为场景 A 和 B 执行两个单独的 XQuery。

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

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

一般来说,如果你想提高脚本的性能,你应该看看查询计划和优化查询,并检查是否使用了合适的索引。此外,我们的新 Selective Indexing 可能对您非常有用。如果不使用索引,您的查询对于 5 亿个单词肯定不会执行良好。

完全披露:我在 BaseX 团队工作,您可能会在 BaseX mailing list 获得更好的帮助,或者可能想参考这个问题,因为我们的首席架构师不像 ML 那样经常关注 SO。

一个可能的改进:尽量减少将控制权从 Perl 转移到数据库的次数——就像尽量减少数据库连接数一样。 (或者至少让自己准备好衡量控制权转移的成本。)我怀疑如果将循环移至 XQuery 而不是 运行 将循环移至 Perl 中,您会得到明显更好的结果。

单次调用数据库管理系统要求它执行 1000 次搜索可能比调用 1000 次 DBMS 每次请求单次搜索要快一些。第一个涉及两个上下文切换:一个从您的脚本或 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 缓存结果,但是Web 服务器不会?...)

最后一个建议:请记住,其他需要进行基准测试的人通常会有与您相同的问题。这意味着您可以将 "Should I do X or Y?" 形式的每个问题重新表述为 "What measurable effect does the difference between X and Y have on the results of a benchmarking test?" 运行 形式的一些测试以尝试衡量该效果,然后 将它们写下来 . (我总是发现,如果我在提出问题之后但在测量差异之前强迫自己做出预测,它会变得更有趣。)