目前,我是一个研究小组的实习生,该小组使大量文本(语料库)可搜索。不仅可以搜索文字字符串,更重要的是,还可以查找与给定输入类似的语法依赖结构,而无需精通任何编程语言或语料库注释样式。很明显,此工具是为语言学家准备的。
在项目开始时 - 在我参与该项目之前 - 该工具仅限于相当小的语料库(最多 900 万字)。目标是使大量文本也可搜索。我们谈论的是+- 5亿字。已经尝试通过减少搜索空间来提高理论上的速度(见本文),但这尚未经过测试。此尝试的结果是一个新的文件结构。让我们称此结构为 B,与未处理的结构 A 相比。我们希望 B 在使用 BaseX 查询时提供更快的结果。
我的问题是:用Perl脚本测试和比较这两种方法的最佳方法是什么?下面找到我当前的脚本,用于在本地查询 BaseX。它需要两个参数。存储不同文件的目录。这些文件分别存储 XPath。这些 XPath 是我选择进行基准测试的那些。第二个参数是返回结果的限制。设置为零时,不设置任何限制。
由于数据集的某些部分非常庞大,因此我们也将它们划分为不同的,大小相同的文件,称为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 = $xpathfilen";
# 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 $xpathn";
print STDOUT "Limit = $limitn";
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)}	
{string-join($idlist, "-")}	
{string-join($beginlist, "-")}	
{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. "nn";
$querysent->close();
}
(请注意,这是一个精简版本,可能无法按原样工作。此代码段不使用结构 B!
发生的情况是:遍历所有 XPath 文件,遍历XPath 文件中的每一行,遍历所有树库部分,然后执行 sub。然后,子查询 BaseX。这归结为向 BaseX 发送一个新的 XQuery,并返回总点击数和结果(可能受到 Perl 脚本中的参数的限制)。所以我开始了,但现在的问题是:我如何改进这个脚本,以便我可以从中获得一些基准测试结果。
首先,我将首先向此脚本添加探查器。我想这一点是显而易见的。但是,我不确定我应该如何开始比较结构 A 和 B.我会将两个查询(到不同的数据库)放在单独的脚本中,然后在两个脚本上调用分析器,并多次运行两个脚本并获取平均值并进行比较?或者我会几乎同时在同一脚本中运行两个数据库的每个查询吗?
考虑正在发生的缓存非常重要。因此,我不完全确定对如此庞大的数据库进行基准测试的构建是合适的。首先是一个脚本,然后是另一个脚本。两者同时进行。两者之间的交替查询。等等。有很多可能性,但我想知道哪种会提供最好的结果。另外,我会重复这个过程几次。我是重复每个查询,然后继续下一个查询,还是完成所有 XPath 文件,然后再次重复整个过程?
(阅读基准标签的描述,我相信这个 - 尽管很详细 - 帖子适合 SO。
一个可能的改进:尽量减少将控制权从 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 将缓存结果,而 Web 服务器不会?...)
最后一个建议:请记住,其他需要进行基准测试的人通常会有与您相同的问题。 这意味着您可以将"我应该做X还是Y?"形式的每个问题重新表述为"X和Y之间的差异对基准测试结果有什么可测量的影响?" 运行一些测试来尝试测量这种效果,并将它们写下来。 (我总是发现,如果我强迫自己在制定问题之后但在测量差异之前做出预测,它会更有趣。
这里有几件事我们必须分开: 第一个问题是 BaseX 性能不应该与你的 perl 脚本混淆,因为你的 perl 脚本似乎只是构造了一个 XQuery(而不是你在问题和标签中建议的 XPath)。因此,为了进行测试,我建议使用一些已经预先罚款的 XQueries,适合您的真实场景,因为您的 XQuery 构造应该可以忽略不计。如何将查询传递给 BaseX,因此通过 Perl API 或任何其他方式应该无关紧要。即使你的perl性能是相关的,你也应该单独测试性能。
因此,您最初的问题是否应该在同一脚本中测试这两个场景不再相关。相反,您只需单独执行场景 A 和 B 的两个单独的 XQueries,而无需使用 perl 脚本。
你担心缓存是部分正确的,但是Java JIT编译器很可能在这里相关(因为BaseX是用java,JIT编写并使用缓存,而不是BaseX本身。因此,您应该使用客户端/服务器基础结构,并拥有一个长时间运行的服务器,并在运行性能测量之前对其进行预热。
关于性能:BaseX GUI和命令行已经包含了一个测量值(使用命令行,您可以设置-V
来获取解析,编译,评估和打印的运行时间)。此外,使用 -r
参数,您可以多次执行查询,它将为您提供平均执行时间。
提高脚本的性能,则应查看查询计划和优化的查询,并检查是否使用了适当的索引。此外,我们新的选择性索引可能对您非常有用。如果不使用该索引,您的查询对于 5 亿个单词肯定不会很好地执行。
完全披露:我和BaseX团队在一起,你可能会在BaseX邮件列表中得到更好的帮助,或者可能想参考这个问题,因为我们的首席架构师不像ML那样经常观察。