教程:使用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类定义了两个名为step和param1的成员变量,分别用于存储当前超步和算法特定参数。我们还定义了一个uint64_t类型的成员变量result,用于存储片段中每个顶点的度数。Init方法用于初始化计算上下文。在当前示例中,我们将step和param1变量初始化为零以及算法特定参数。同时我们将每个顶点的结果初始化为零。
步骤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::ParallelEngine 和 grape::Communicator 类,这些类提供了通信和并行处理能力。MyApp 类定义了两个名为 message_strategy 和 load_strategy 的 static constexpr 变量,这些变量指定了计算中使用的消息策略和负载策略。更多信息请参阅 libgrape-lite 文档。
PEval 方法用于实现计算的部分评估阶段。在当前示例中,我们初始化通信通道但不执行其他操作,而是将计算逻辑放入 IncEval 方法中。
IncEval方法用于实现计算的增量评估阶段。在此方法中,我们递增上下文中的超步计数器,处理其他分片发送的接收消息,并计算图中每个顶点的度数。ForEach方法用于遍历分片的内部顶点,GetOutgoingAdjList和GetIncomingAdjList方法用于获取每个顶点的出度和入度邻接表。然后将每个顶点的结果设置在上下文中。
步骤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算法的示例和最佳实践。