一文看懂开源图化框架中的循环设计逻辑!

简介: 相信大家在日常工作中,已经精通各种循环逻辑的实现。就拿我来说吧,多年的工作经验,已经让我可以熟练的使用 C++,Python,英语等多种语言,循环多次输出“hello word”。不过大家有没有想过一个这样的问题:如何在一个有向无环图(Directed Acyclic Graph,简称dag)中实现循环呢?

大家好,我是不会写代码的纯序员——Chunel Feng。


相信大家在日常工作中,已经精通各种循环逻辑的实现。就拿我来说吧,多年的工作经验,已经让我可以熟练的使用 C++,Python,英语等多种语言,循环多次输出“hello word”。不过大家有没有想过一个这样的问题:如何在一个有向无环图(Directed Acyclic Graph,简称dag)中实现循环呢?


今天,我们就来介绍一下如何实现这个小目标 —— 而且是想怎么循环,就怎么循环哦~~~


功能介绍


为了防止有比我还小白的童鞋,我先几句话介绍一下什么是dagdag是一种图结构,由多个图节点组成,每个节点可以指向 0 个或 n 个其他的节点,且在图内部不会形成环形指向的结构。


如果觉得定义太枯燥,那看下面的图,哪个是dag、哪个不是dag应该就一目了然了。顺便说一句,tree结构也可以理解成是一种特殊的dag,就好比list可以理解成是一种特殊的tree一样。


38.png


在上一篇文章中,我们介绍了图(以下内容,dag和“图”表示同一概念)中节点是按什么顺序执行的。至于图中循环,举个例子,我们假设每个节点的功能,就是输出字母。这个时候,我们需要整个流图输出一串字母:abcbcd,需要如何实现?


最常规的方法:实现一些算子,功能是输出 a,bcbc,d 即可。 假设我要输出不同的 n 次 bc,还要实现 n 个不同的算子么?有人说,可以实现一个算子,输出 bc 即可,然后循环次数可以当做参数传递进来啊。但是这样的算子,功能不是最细粒度,可重用性并不高——比如下次又需要循环输出 ab 了呢?


还有一种方法:反正可以创建节点的,我们只要实现 4 个算子分别输出 a/b/c/d,然后向图里按照顺序注册 6 个次就好了。 那问题又来了,需要 bc 反复循环 100 次怎么办?又有方法:外部写个 for 循环,注册 100 次进去啊。emmm,好像也行。那如果是需要 bc 反复输出 100 亿次呢?for 循环注册 100 亿次?


注册节点实际上是在程序内部 new 节点的过程,是需要开辟内存和分配资源的。如果你的电脑性能这么给力的话,我不建议你用来写程序。应该直接去挖比特币,用比特币的钱再去买更多的电脑,再来挖更多的比特币——当然了,这又是另一种循环了,不在我们今天的讨论范围之内,哈哈。


39.png


言归正传,上图中,背景为蓝色的区域,表示需要循环执行的区域。可以看出,在dag中循环主要有四种形式:单点循环,多点串行循环,多点并行循环和多点混合循环。

【单点循环】就不多解释了,见名知意。


刚才抛出的问题,实际上是多点(b、c 两个节点)【串行循环】,循环的时候,bc 需要依次顺序执行。


多点【并行循环】,指的是输出的结果 bc 需要循环多次输出,但是可以无序的情况。

至于多点【混合循环】,可以理解成循环的区域又由好几部分组成,其中的部分区域需要串行,有的部分可以并行。如图第四部分所示,循环处执行的顺序是:b->cd(或 dc)->e。


B 节点明明仅依赖 A 节点,如何让程序执行完 C 节点之后,“掉头”回来继续执行 B 呢?如何标记这些信息呢?我们接下来就结合CGraph开源项目的实现逻辑,为大家做一些通用的解释。


需要申明的是,看懂以下内容并不需要你了解 CGraph 工程本身,但需要你已经了解了dag图的执行原理。还不懂执行原理的童鞋,可以先看一下我的上一篇文章:如何实现一个图化框架?代码已开源!


名词解释


我们先梳理几个名词,说到具体流程的时候,会用得到。


40.png


element(元素)


element 是所有被执行结构的基类,可以派生出node,group两种类型。无实际意义,且不可被执行。


node(节点)


node 是最小粒度的算子。node本身无法执行,但所有有具体功能的功能节点,都继承自node


functionNode(功能节点)


functionNode 是最小粒度的可执行算子,继承自node类,相当于是node的功能实现类。与node不同的是,functionNode有具体功能,且可以被执行。至于具体功能是什么,可以是输出字母 a,也可以是去挖比特币。总之,需要自己去实现。


group(组)


group 是多个functionNode的组合。自身不可以执行,但可以派生出clusterregion等组合逻辑。


cluster(簇)


cluster 继承自group,由多个functionNode线性组合而成。执行cluster的时候,内部的node依次顺序执行。简而言之就是可以依次完成多个功能。


region(区域)


region 继承自group,也是由多个functionNode组合而成。与cluster的区别是,region中的加入的node需要指定相互依赖关系。如果不指定依赖的话,就相当于是并发执行了,因为没有任何需要依赖信息。


pipeline(流水线)


pipeline是以上信息运行的地方。所有的functionNodeclusterregion信息,都需要注册到pipeline中,并且设定相互依赖关系。注册了以上三种信息的pipeline,实际上就对应了一个dag图,执行pipeline的过程,就是图执行的过程。


41.png


实现思路


一句话:分治!


看了上面介绍的朋友,应该已经能想到,一个图实际上是由多个不同的子模块组成。这些子模块都可以拆解成functionNodeclusterregion这三种形式,而这三种形式都统一派生自element类。在向图中注册element的时候,也可以注册这个element的循环执行次数。而不同的element,又有自己专属的执行方式,比如:functionNode就是简单执行自己的功能,而cluster就是依次执行其中的functionNode。针对cluster的循环执行,就是依次执行其中的functionNode,并且循环 n 次即可。


当然,还有更扫的。functionNodeclusterregion这三个类,实际都继承自element,那相当于是cluster中,不仅可以加入functionNode,也可以加入region甚至是另一个cluster了。而加入的信息中,又可以有自己的循环执行逻辑。region也是同理,这样就可以实现cluster中加入循环 n 次的regionregion中再加入循环 m 次的内部的cluster的无限套娃逻辑了。


当然了,针对循环问题,我相信 bfs 或者 dfs 应该也是可以解决的。但是解决的过程,会相对更加吃力一些,而且流程的颗粒度没有使用分治来的清晰。有兴趣的朋友,可以自己尝试一下。


42.png


给大家带上几句 CGraph 中具体实现的代码吧。更多的例子,可以去 github 去搜索同名工程,里面还有实现了套娃逻辑的例子哦,哈哈。


#include "MyGNode/MyNode1.h"#include "MyGNode/MyNode2.h"void tutorial_cluster () {    CSTATUS status = STATUS_OK;    GPipelinePtr pipeline = new GPipeline();    GElementPtr a, b_cluster, c, d = nullptr;    b_cluster = pipeline->createGGroup<GCluster>({         pipeline->createGNode<MyNode1>(GNodeInfo("nodeB1", 1)),    // 创建名为nodeB1的node信息,并将其放入b_cluster中         pipeline->createGNode<MyNode1>(GNodeInfo("nodeB2", 3)),    // 创建名为nodeB2且自循环3次的node信息,并将其放入b_cluster中         pipeline->createGNode<MyNode2>(GNodeInfo("nodeB3", 1))    });    // 创建cluster信息,包含了三个node信息    /* 请对所有返回值进行判定 */    status = pipeline->registerGElement<MyNode1>(&a, {}, "nodeA", 1);        // 将名为nodeA的node信息,注册入pipeline中    status = pipeline->registerGElement<GCluster>(&b_cluster, {a}, "clusterB", 2);    // 将名为clusterB,依赖a执行且自循环2次的cluster信息,注册入pipeline中    status = pipeline->registerGElement<MyNode1>(&c, {a}, "nodeC", 1);    status = pipeline->registerGElement<MyNode2>(&d, {b_cluster, c}, "nodeD", 2);    status = pipeline->process();    CGRAPH_ECHO("pipeline process status is : [%d]", status);    delete pipeline;}


在 CGraph 中,以上几句代码,就实现了图片中描述的循环逻辑。其中,MyNode1MyNode2是继承于node并且实现了特定功能的子类。AB1C等信息,都是一个functionNode。而B1B2B3作为一个cluster,被循环执行 2 次。每个循环的过程中,B2又自己单独循环执行 3 次,D自己循环执行 2 次。


假设ACD的功能是打印字母 A、C、D,而B1B2B3的功能是打印数字 1、2、3。那最终打印出来的结果,就是 A[1C222312223]DD,其中长串数字和 C 的输出顺序(中括号内部)每次可能不同,因为是并行执行。


写在最后


大数据和人工智能当道的时代,图化框架已然成了当下的主流。对这部分知识的了解,是很有必要加入日程的。大家熟悉的 tensorflow,flink 等库,在数据传递和计算的时候,底层都维护了一张“看不见的图”。


CGraph 项目是一个 C++的开源的并行图计算框架,最近也是刚刚起步,还有很多功能没有实现,比如跨模块参数的传递、底层并发优化、当前信息快照和异常恢复处理等等等。也希望有兴趣的朋友可以联系我们,加入我们,一起实现一点小的功能,或者测出几个 bug 啥的。生而为程序员,一起为开(bai)源(piao)社区做一点自己的贡献,提升自己的同时,也可以做到 build together, power another —— 共建,赋能。


引用链接


[1] 一面之猿网: http://www.chunel.cn/

[2] CGraph 图计算框架: https://github.com/ChunelFeng/CGraph


目录
相关文章
|
18天前
|
C语言 C++ 开发者
深入探索C++:特性、代码实践及流程图解析
深入探索C++:特性、代码实践及流程图解析
|
7天前
|
SQL 设计模式 Java
【软件工程底层逻辑系列】建模的底层逻辑
在本文中,给出建模的底层逻辑:用图形逻辑地表达现实业务的抽象,通过一些大家通识的技术案例讲述建模的过程。
74854 1
|
9月前
|
机器学习/深度学习 人工智能 自然语言处理
这 10 本书,带你了解 ChatGPT 的底层逻辑
作为一门应用型学科,机器学习植根于数学理论,落地于代码实现。这就意味着,掌握公式推导和代码编写,方能更加深入地理解机器学习算法的内在逻辑和运行机制。 本书在对全部机器学习算法进行分类梳理的基础上,分别对监督学习单模型、监督学习集成模型、无监督学习模型、概率模型四个大类共 26 个经典算法进行了细致的公式推导和代码实现,旨在帮助机器学习的学习者和研究者完整地掌握算法细节、实现方法以及内在逻辑。
168 0
|
10月前
|
C#
一个 C#例子,代码简化的过程
一个 C#例子,代码简化的过程
51 0
|
10月前
|
人工智能 数据可视化 前端开发
如何用smardaten无代码平台进行复杂逻辑编排?
如何用smardaten无代码平台进行复杂逻辑编排?
|
存储 自然语言处理 算法
GaiaX开源解读 | 表达式作为逻辑动态化的基础,我们是如何设计的
GaiaX跨端模板引擎,是在阿里优酷、淘票票、大麦内广泛使用的Native动态化方案,其核心优势是性能、稳定和易用。本系列文章《GaiaX开源解读》,带大家看看过去三年GaiaX的发展过程。
257 0
|
存储 自然语言处理 算法
作为逻辑动态化的基础,GaiaX 表达式是如何设计的? | GaiaX 开源解读
GaiaX 跨端模板引擎,是在阿里文娱内广泛使用的 Native 动态化方案,其核心优势是性能、稳定和易用。本系列文章《GaiaX 开源解读》,带大家看看过去三年 GaiaX 的发展过程。 GaiaX 开源地址:https://github.com/alibaba/GaiaX
331 0
作为逻辑动态化的基础,GaiaX 表达式是如何设计的? | GaiaX 开源解读
|
前端开发
前端项目实战45-加一层判断 否则影响项目执行
前端项目实战45-加一层判断 否则影响项目执行
48 0
|
Android开发 UED iOS开发
一个淘宝的bug,让我弄懂了它的底层逻辑和顶层设计
一个淘宝的bug,让我弄懂了它的底层逻辑和顶层设计
一个淘宝的bug,让我弄懂了它的底层逻辑和顶层设计
|
存储 前端开发 对象存储
前端基石:函数的底层执行机制
本文主要讲函数的底层执行机制
99 0