类 TaskFlow 框架性能年终 PK

开场白

临近元旦,又到了各个平台发年度报告的时间。刚好,离 ograph 第一次正式提交也过了差不多一年了,是骡子是马,咱也趁着这个热闹,拉出来溜溜。

还是先介绍一下 ograph 吧,这是一个基于DAG图的任务流程执行框架。你可能听说过 taskflow,ograph 就是类似的框架。基本的使用方法如下:

func TestHello(t *testing.T) {
    pipeline := ograph.NewPipeline()

    zhangSan := ograph.NewElement("ZhangSan").UseFn(func() error {
        fmt.Println("hello")
        return nil
    })
    liSi := ograph.NewElement("LiSi").UseFn(func() error {
        fmt.Println("hi")
        return nil
    })

    pipeline.Register(zhangSan).
        Register(liSi, ograph.Rely(zhangSan))

    if err := pipeline.Run(context.TODO(), nil); err != nil {
        t.Error(err)
    }
}

ograph 会按依赖关系,先执行 ZhangSan 输出 hello, 再执行 LiSi,输出 hi

除了 UseFn,按实际使用场景或者风格偏好,还可以使用 UseNodeUseFactory,这里就不详细展开了。

PK 嘉宾

作为老牌大哥,taskflow 的后来者当然不止 ograph。虽然风格和侧重场景有所不同,但是核心功能是一样的。

下面揭晓本次 PK 嘉宾:

首先是 CGraph🫡🫡🫡

ChunelFeng/CGraph: 【A common used C++ DAG framework】 一个通用的、无三方依赖的、跨平台的、收录于awesome-cpp的、基于流图的并行计算框架。欢迎star & fork & 交流

想必你也发现了,ograph 名字和 cgraph 很相似。是的,ograph 确实在风格上 部分借鉴了 CGraph。当然在底层实现上还是完全不同的,毕竟一个是 C++ 写的,一个是 Go 写的。

和一个多人开发,已经经过多年打磨的 C++ 项目比拼性能,还是挺有挑战性的,但也正因是因为这份挑战驱使我一次次优化性能。且看 ograph 是否能后来居上!

下一位来到战场的是:go-taskflow 🤟🤟🤟

noneback/go-taskflow: A pure go General-purpose Task-parallel Programming Framework with integrated visualizer and profiler

想必聪明的你又发现了,go-taskflow 和 taskflow 名字很相似,风格上两者确实也很类似。

这个框架是我最近发现的,也是单人开发了差不多一年的 go 框架,从这点来说和 ograph 还挺像的。既然这么巧碰上了,那就不打不相识,一起来 PK 下吧。

至于为什么没有 taskflow 本尊呢?因为 CGraph 已经有和 taskflow 的性能测试对比了。从测试结果看,CGraph 性能已经领先 taskflow 了。有兴趣的小伙伴可以参考下,CGraph 作者的博客和B站视频:

炸裂!CGraph性能全面超越taskflow之后,作者却说他更想… - 一面之猿网

www.bilibili.com/video/BV1gwWAekEA...

PK 开始

这次 PK 使用的测试用例沿用 CGraph 作者的设计,是对框架核心调度性能的测试。

废话不多说,PK 这就开始。

Round One

第一回合,考验各个框架的并发处理能力。

具体场景如下,有 32 个节点,这些节点之间没有任何依赖关系。因此在执行时候,这些节点是并发执行的。

每个框架各自执行 50 万次。

No.1 OGraph

总耗时 2.17s


BenchmarkConcurrent_32-8 500000 4331  ns/op 1592  B/op  16  allocs/op

PASS

ok github.com/symphony09/ograph/test  2.170s

No.2 CGraph

总耗时 4.13s


**** Running test-performance-01 ****

[2024-12-28 15:33:44.174] [test_performance_01]: time counter is : [4134.87] ms

No.3 go-taskflow

总耗时 9.71s


BenchmarkC32-8 500000  19425  ns/op 7084  B/op 208  allocs/op

PASS

ok github.com/noneback/go-taskflow  9.716s

Round Two

第二回合,考验各个框架对串行节点的执行优化。

具体场景如下,有 32 个节点,前后依次依赖(N1->N2->……->N32),因此在执行时候,节点是一个接一个执行。

每个框架各自执行 100 万次。

No.1 OGraph

总耗时 0.29s


BenchmarkSerial_32-8  1000000  285.9  ns/op  120  B/op 4  allocs/op

PASS

ok github.com/symphony09/ograph/test  0.290s

No.2 CGraph

总耗时 0.57s


**** Running test-performance-02 ****

[2024-12-28 15:33:44.749] [test_performance_02]: time counter is : [572.61] ms

No.3 go-taskflow

总耗时 67.3s ?

go-taskflow 跑 100 万次时,出现卡死的情况。只能取跑 10 万次的耗时再乘 10。


BenchmarkS32-8 100000  67309  ns/op 6907  B/op 255  allocs/op

PASS

ok github.com/noneback/go-taskflow  6.735s

Round Three

第三回合,考验各个框架对经典 DAG 图的执行性能。

具体场景如下:

各个框架各执行 100 万次

No.1 OGraph

总耗时 2.778s


BenchmarkComplex_6-8  1000000 2773  ns/op 1048  B/op  21  allocs/op

PASS

ok github.com/symphony09/ograph/test  2.778s

No.2 CGraph

总耗时 4.027s


**** Running test-performance-03 ****

[2024-12-28 15:33:48.778] [test_performance_03]: time counter is : [4027.31] ms

No.3 go-taskflow

总耗时 8.223s


BenchmarkC6-8 1000000 8219  ns/op 1264  B/op  45  allocs/op

PASS

ok github.com/noneback/go-taskflow  8.223s

Round Four

第四回合,考验各个框架对极多依赖的优化。

具体场景如下:8x8 全连接

各个框架各执行 10 万次

No.1 OGraph

总耗时 0.86s


BenchmarkConnect_8x8-8 100000 8557  ns/op 2554  B/op  16  allocs/op

PASS

ok github.com/symphony09/ograph/test  0.860s

No.2 CGraph

总耗时 1.357s


**** Running test-performance-04 ****

[2024-12-28 15:33:50.137] [test_performance_04]: time counter is : [1357.03] ms

No.3 go-taskflow

总耗时 7.35s ?

又出问题了,故技重施


BenchmarkC8x8

[panic] copool: node N24 ref counter is zero, cannot deref: goroutine 248743

结算

我宣布,ograph 《遥遥领先》😎😎😎。如果你也觉得 OK 的话,请给 ograph 一个 Star!

symphony09/ograph: 【Go DAG Schedule Framework】A simple way to build a pipeline with Go.

附录

测试硬件

【CPU】AMD 5600G - 6 核心 12 线程

【内存】DDR4 32 GB

测试软件

【系统】Linux 6.11 - Fedora Workstation 41

【OGraph】v0.6.1 - Go 1.23.4

【CGraph】v2.6.2 - GCC14.2.1

【go-taskflow】v0.1.6 (master分支最新提交)

测试代码

【OGraph】

ograph/test/benchmark_test.go at main · symphony09/ograph

【CGraph】

CGraph/test/Performance at main · ChunelFeng/CGraph

【go-taskflow】

func BenchmarkC32(b *testing.B) {
    tf := gotaskflow.NewTaskFlow("G")
    for i := 0; i < 32; i++ {
        tf.NewTask(fmt.Sprintf("N%d", i), func() {})
    }

    executor := gotaskflow.NewExecutor(32)

    for i := 0; i < b.N; i++ {
        executor.Run(tf).Wait()
    }
}

func BenchmarkS32(b *testing.B) {
    tf := gotaskflow.NewTaskFlow("G")
    prev := tf.NewTask("N0", func() {})
    for i := 1; i < 32; i++ {
        next := tf.NewTask(fmt.Sprintf("N%d", i), func() {})
        prev.Precede(next)
        prev = next
    }

    executor := gotaskflow.NewExecutor(1)

    for i := 0; i < b.N; i++ {
        executor.Run(tf).Wait()
    }
}

func BenchmarkC6(b *testing.B) {
    tf := gotaskflow.NewTaskFlow("G")
    n0 := tf.NewTask("N0", func() {})
    n1 := tf.NewTask("N1", func() {})
    n2 := tf.NewTask("N2", func() {})
    n3 := tf.NewTask("N3", func() {})
    n4 := tf.NewTask("N4", func() {})
    n5 := tf.NewTask("N5", func() {})

    n0.Precede(n1, n2)
    n1.Precede(n3)
    n2.Precede(n4)
    n5.Succeed(n3, n4)

    executor := gotaskflow.NewExecutor(1)

    for i := 0; i < b.N; i++ {
        executor.Run(tf).Wait()
    }
}

func BenchmarkC8x8(b *testing.B) {
    tf := gotaskflow.NewTaskFlow("G")

    layersCount := 8
    layerNodesCount := 8

    var curLayer, upperLayer []*gotaskflow.Task

    for i := 0; i < layersCount; i++ {
        for j := 0; j < layerNodesCount; j++ {
            task := tf.NewTask(fmt.Sprintf("N%d", i*layersCount+j), func() {})

            for i := range upperLayer {
                upperLayer[i].Precede(task)
            }

            curLayer = append(curLayer, task)
        }

        upperLayer = curLayer
        curLayer = []*gotaskflow.Task{}
    }

    executor := gotaskflow.NewExecutor(8)

    for i := 0; i < b.N; i++ {
        executor.Run(tf).Wait()
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!