教程:使用PIE模型在C++中开发你的算法

在本教程中,我们将演示如何使用C++编程语言基于PIE模型开发算法。

前提条件:

  • 熟悉PIE编程模型

  • 使用命令 pip3 install graphscope 安装 GraphScope

接下来,我们将引导您开发一个简单的PIE算法,用于计算图中每个顶点的度数。完整API文档可查阅Analytical Engine API文档libgrape-lite API

步骤1:定义上下文类

首先,我们创建一个继承自grape::VertexDataContext的上下文类。该类将存储和管理算法特定的数据和参数。

template <typename FRAG_T>
class MyAppContext : public grape::VertexDataContext<FRAG_T, uint64_t> {
  using oid_t = typename FRAG_T::oid_t;
  using vid_t = typename FRAG_T::vid_t;
  using vertex_t = typename FRAG_T::vertex_t;

 public:
  explicit MyAppContext(const FRAG_T& fragment)
      : grape::VertexDataContext<FRAG_T, uint64_t>(fragment, true),
        result(this->data()) {}

  void Init(grape::ParallelMessageManager& messages, int param1) {
    this->step = 0;
    this->param1 = param1;
    result.SetValue(0);
  }

  int step = 0;
  int param1 = 0;
  typename FRAG_T::template vertex_array_t<uint64_t>& result;
};

如代码所示,MyAppContext类定义了两个名为stepparam1的成员变量,分别用于存储当前超步和算法特定参数。我们还定义了一个uint64_t类型的成员变量result,用于存储片段中每个顶点的度数。Init方法用于初始化计算上下文。在当前示例中,我们将stepparam1变量初始化为零以及算法特定参数。同时我们将每个顶点的结果初始化为零。

步骤2:定义算法类

接下来,我们定义MyApp类,它负责通过使用MyAppContext类来实现算法。

template <typename FRAG_T>
class MyApp : public grape::ParallelAppBase<FRAG_T, MyAppContext<FRAG_T>>,
              public grape::ParallelEngine,
              public grape::Communicator {
 public:
  INSTALL_PARALLEL_WORKER(MyApp<FRAG_T>, MyAppContext<FRAG_T>, FRAG_T)
  static constexpr grape::MessageStrategy message_strategy =
      grape::MessageStrategy::kSyncOnOuterVertex;
  static constexpr grape::LoadStrategy load_strategy =
      grape::LoadStrategy::kBothOutIn;
  using vertex_t = typename fragment_t::vertex_t;

  void PEval(const fragment_t& fragment, context_t& context,
             message_manager_t& messages) {
    messages.InitChannels(thread_num());
    // We put all compute logic in IncEval phase, thus do nothing but force continue.
    messages.ForceContinue();
  }

  void IncEval(const fragment_t& fragment, context_t& context,
               message_manager_t& messages) {
    // superstep
    ++context.step;

    // Process received messages sent by other fragment here.
    {
      messages.ParallelProcess<fragment_t, double>(
          thread_num(), fragment,
          [&context](int tid, vertex_t u, const double& msg) {
            // Implement your logic here
          });
    }

    // Compute the degree for each vertex, set the result in context
    auto inner_vertices = fragment.InnerVertices();
    ForEach(inner_vertices.begin(), inner_vertices.end(),
            [&context, &fragment](int tid, vertex_t u) {
              context.result[u] =
                  static_cast<uint64_t>(fragment.GetOutgoingAdjList(u).Size() +
                                        fragment.GetIncomingAdjList(u).Size());
            });
  }
};

MyApp 类继承自 grape::ParallelAppBase,后者提供了实现并行图算法的基本功能。它还继承了 grape::ParallelEnginegrape::Communicator 类,这些类提供了通信和并行处理能力。MyApp 类定义了两个名为 message_strategyload_strategystatic constexpr 变量,这些变量指定了计算中使用的消息策略和负载策略。更多信息请参阅 libgrape-lite 文档

PEval 方法用于实现计算的部分评估阶段。在当前示例中,我们初始化通信通道但不执行其他操作,而是将计算逻辑放入 IncEval 方法中。

IncEval方法用于实现计算的增量评估阶段。在此方法中,我们递增上下文中的超步计数器,处理其他分片发送的接收消息,并计算图中每个顶点的度数。ForEach方法用于遍历分片的内部顶点,GetOutgoingAdjListGetIncomingAdjList方法用于获取每个顶点的出度和入度邻接表。然后将每个顶点的结果设置在上下文中。

步骤3:打包算法

为了使算法能在GraphScope上运行,我们需要将其打包为.gar文件。该包应包含上述C++文件和一个名为.gs_conf.yaml的配置文件,其内容如下:

app:
- algo: my_app
  type: cpp_pie
  class_name: gs::MyApp
  src: my_app.h
  context_type: vertex_data
  compatible_graph:
    - gs::ArrowProjectedFragment
    - gs::DynamicProjectedFragment
    - vineyard::ArrowFragment

代码库结构如下:

.
├── my_app.h ➝ algorithm logics
└── my_app_context.h ➝ context with auxiliary data for the algorithm
└── .gs_conf.yaml ➝ configuration file 

然后,我们通过以下命令打包算法: zip -jr 'my_app.gar' '*.h' ''.gs_conf.yaml'

步骤4:在GraphScope上运行.gar文件

使用以下Python代码在GraphScope上运行该算法。

import graphscope

from graphscope.framework.app import load_app
from graphscope.dataset import load_p2p_network

sess = graphscope.session()
simple_graph = load_p2p_network(sess)._project_to_simple()

my_app = load_app('<path_to_your_gar_resource>')
result = my_app(simple_graph, 10)  # pass 10 as param1 defined in 'MyAppContext.h'

print(result.to_numpy('r'))

GraphScope C++ SDK GitHub模板

为了帮助您更高效地开发算法,我们提供了C++模板库来辅助您开始算法开发。该库包含在C++中实现PIE算法的示例和最佳实践。