#gStore-weekly | SPARQL 执行过程中高级查询函数的求值(下)

SPARQL 执行过程中高级查询函数的求值(下)

1.1 上期回顾

上篇中,我们介绍了 GeneralEvaluation::getFinalResult 函数中与高级查询函数相关的逻辑,其在查询的 WHERE 语句执行完成后从结果中提取出高级查询函数的各参数;对于每组有效的参数,调用 PathQueryHandler 类中对应的成员函数,得到高级查询函数的执行结果。

本篇中,我们将介绍 PathQueryHandler 类(Query/PathQueryHandler.[h|cpp])的基本功能,及其中重要的成员变量和成员函数。

1.2 PathQueryHandler

1.2.1 基本功能

PathQueryHandler 类是 gStore 中高级查询函数的执行入口,负责将当前装载数据库的全量 RDF 图数据以压缩稀疏行(Compressed Sparse Row, CSR)的形式读入到内存中(目的是在执行高级分析型查询的过程中剔除大量 I/O 带来的开销;目前假设图数据能够全量读入内存,暂未处理图数据占用空间大于内存限制的情形),并在其上通过调用成员函数来执行相应类型的高级查询。

1.2.2 重要的成员变量:csr

PathQueryHandler 类的成员变量 csr 即是上述用于在内存中存储全量图数据的 CSR 结构,其为 CSR 类(Database/CSR.h)指针。一般而言,一个 CSR 结构由 id2vidoffset_listadjacency_list 三部分构成,如下图所示:

CSR

其中 id2vid 表示从下标到结点 ID 的映射(如下标 0 对应的结点 ID 为 1);adjacency_list 连续依次给出 id2vid 中各结点的出邻接列表;offset_list 则给出 adjacency_list 中各结点邻接列表起始的偏移量(例如 offset_list[0] == 0offset_list[1] == 3 ,表示下标 0 对应的结点的邻接列表以 adjacency_list[0] 起始,以 adjacency_list[3 - 1] 为止;offset_list[1] == offset_list[2] == 3 ,表示下标 1 对应的结点没有出邻居)。

CSR 类对象在此基础上添加了 vid2idstd::map 类型),为 id2vid 的逆映射,能对某些操作以空间换时间。此外,由于 RDF 图中有多种谓词(可看作边标签),一些高级查询函数中又对允许出现的谓词有限制,因此上述四个构成部分均为 std::vectorstd::map 类指针,实质上对每个谓词会存储一个其诱导子图(induced subgraph,即将原图中的对应谓词边及其所关联的结点抽取出来得到的子图)对应的 CSR ;以谓词 ID 为下标即可将对应的 CSR 取出。

PathQueryHandler 类中的 CSR 类指针实际上指向含有两个元素的 CSR 数组,分别表示出、入邻接列表。

PathQueryHandler 类中提供了一系列基于 CSR 类访问图的基本操作,如获取总结点或总边数、获取结点邻居列表等,足以满足高级查询(即较复杂的图算法)对图的访问需求。在与高级查询函数对应的PathQueryHandler 类成员函数中均调用了这些基本操作。未来即将介绍的自定义算子函数中,用户也可调用这些基本操作来实现自己的图算法。

1.2.3 与高级查询函数对应的成员函数:以 shortestPath 为例

PathQueryHandler::shortestPath 为最短路径查询函数。其参数包括:

  • int uid :起始结点 u 的 ID ;
  • int vid :目标结点 v 的 ID ;
  • bool directed :表示是否仅返回有向路径(若为 false ,则表示可返回无向或有向路径,无向路径即为将图中所有边视作双向后得到的任意路径);
  • const vector<int> &pred_set :路径中边上允许出现的谓词 ID 集合。

上篇对照可以发现,这些参数与高级查询函数的 SPARQL 扩展语法中的参数是一一对应的。

以下我们以此函数为例,呈现一个典型的高级查询函数的实现方式。

由于考虑不带权最短路径(边权统一视为 1),因此 shortestPath 实现为双向宽度优先搜索(Bidirectional Breadth-First Search, BiBFS)。其中关键的数据结构为:

  • q_u, q_v :分别从结点 u 、v 出发的前向、后向(即与原图中边的方向相反)BFS 的队列;
  • dis_u, dis_v :分别从结点 u 、v 出发分别通过前向、后向 BFS 找到的到某结点的最短路径长度;
  • meet_node :从结点 u 、v 出发的前向、后向 BFS 相遇的结点(若 u 到 v 可达);
  • flag :布尔值,表示 meet_node 的值是否有效(若为假,则尚未找到从 u 到 v 的路径(即尚未找到相遇结点),或 u 到 v 不可达)。

函数的主体为以下 while 循环:

while (flag == 0 && (!q_u.empty() || !q_v.empty()))
{
	queue<int> new_q_u;
	while (!q_u.empty() && flag == 0) { ... }
	q_u = new_q_u;
	
	queue<int> new_q_v;
	while (!q_v.empty() && flag == 0) { ... }
	q_v = new_q_v;
}

可见最外层 while 循环的条件为:尚未找到从 u 到 v 的路径(flag == 0),且 BFS 队列 q_uq_v 中至少有一者不为空;内层还有两个 while 循环,分别表示从 u 出发的一轮前向 BFS 和从 v 出发的一轮后向 BFS 。接下来我们详细分析从 u 出发的一轮前向 BFS 的代码:

while (!q_u.empty() && flag == 0)
{
  int temp_u = q_u.front();

  q_u.pop();
  for (int i = 0; i < num_of_pred; ++i)
  {
    int x = pred_set[i];
    int num = getOutSize(temp_u, x);
    for (int j = 0; j < num; ++j)
    {
      int t = getOutVertID(temp_u, x, j); // get the node

      if (dis_v.find(t) != dis_v.end())
      {
        flag = 1;
        meet_node = t;
        dis_u[t] = dis_u[temp_u] + 1;
        break;
      } // get the meet node

      if (dis_u.find(t) != dis_u.end())
        continue; // has visited before

      new_q_u.push(t);
      dis_u[t] = dis_u[temp_u] + 1;
    }
    if (flag)
      break;
    if (directed)
      continue;

    num = getInSize(temp_u, x);
    for (int j = 0; j < num; ++j)
    {
      int t = getInVertID(temp_u, x, j); // get the node

      if (dis_v.find(t) != dis_v.end())
      {
        flag = 1;
        meet_node = t;
        dis_u[t] = dis_u[temp_u] + 1;
        break;
      } // get the meet node

      if (dis_u.find(t) != dis_u.end())
        continue; // has visited before

      new_q_u.push(t);
      dis_u[t] = dis_u[temp_u] + 1;
    }
    if (flag)
      break;
  }
}

上述代码首先取出队列 q_u 的队首元素,即是准备进行拓展的当前结点。下面的 for 循环对当前结点以每种谓词为标签的边进行拓展(若 directed == true ,则仅拓展出边;注意 for 循环中的 if (directed) continue;。若 directed == false ,则出、入边均拓展)。以拓展出边为例,通过调用 getOutSize 函数获取当前结点以当前谓词为标签的出边数;对每条这样的出边,通过调用 getOutVertID 函数获取对应的出邻居。若获取到的出邻居在 dis_u 中已有值,说明此出邻居已被前向 BFS 访问过,则跳过;若此出邻居在 dis_v 中已有值,说明此出邻居已被后向 BFS 访问过,其为前、后向 BFS 相遇的结点(也就说明从 u 到 v 的最短路径已找到),则相应设置 flagmeet_nodedis_u 值,跳出循环;否则,设置出邻居的 dis_u 值,并将其放入备用队列 new_q_u 中,等待下一轮拓展。

完成主体 for 循环后,shortestPath 函数中还有一段逻辑用于由 meet_node 复原从 u 到 v 的最短路径,此处不再详述。

1.3 小结

本篇介绍了 SPARQL 执行过程中实际进行高级查询函数求值的 PathQueryHandler 类,的基本功能,其中重要的成员变量 csr ,及以 PathQueryHandler::shortestPath 为例的、与高级查询函数一一对应的成员函数,建议在阅读的同时结合源码 Database/CSR.h 、 Query/PathQueryHandler.[h|cpp] ,会更容易理解。接下来我们拟分析 SPARQL 执行过程中自定义算子函数的处理机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值