EdgeX 性能分析

写在前面

EdgeX 是 Linux 软件基金会旗下的一个开源的,应用在边缘计算设备上的,针对 IoT 设备的软件系统。它使用 Go 语言实现,基于微服务框架。

这还是个很早期的开源软件系统,截止写稿当日,其最新版本是 V2.0.0。早期版本的很多服务还是基于 Java 实现的,最近才全部改为 Go 语言实现。这给了我们一点希望:是不是可以应用在资源受限的 IoT 网关上。

性能评估方案

我们使用 Raspbery Pi 3B+ 来进行评估,它使用 BCM2837B0,1.4GHz 64-bit 四核,1GB DDR2 内存。其实这个性能不低,但暂时手上只有这个用来评估的硬件,权作参考。

我们设计的方案是,在 Raspbery Pi 上运行 EdgeX 的基础服务,然后再运行一个官方的 Demo Device Service,创建 10 个设备,其中 5 个设备每秒通过 AutoEvent 上报一次数据,另外 5 个设备每 3 秒通过 AutoEvent 上报一次数据。在这种情况下,我们使用 Go pprof 工具,来检查 CPU 以及内存使用情况。

快速了解 pprof 工具,可参阅 Go 官方博客上的一篇文章 Profiling Go Programs

测试结论

结论一:EdgeX 核心服务中,core-data 的 CPU 和内存占用相对较多,而其他的核心服务,如 core-metadata, core-command 则资源消耗非常少。

这也非常好理解,core-metadata 只有在启动的时候以及搜索到新设备的时候,才会运行相关业务,在业务持续运行中,根本就没有需要运行的任务。由于我们测试过程中,没有对设备进行命令控制,而是由设备主动上报事件,故 core-command 也几乎没有资源消耗。

结论二:我们的 Demo Device Service 资源消耗相对较多。

特别地,如果使用官方的 edgex-sdk-go 中的 simple device,资源占用非常多。而如果去掉 device-simple 中的 Image 属性上报,则资源占用少很多。

这里的原因是,每次上报 Image 属性时,需要解码一个 jpeg 图片,然后再编码,再把二进制数据发送给 core-data 服务。这个过程会消耗非常多的 CPU 以及内存资源。

由于我们的评估,暂不涉及多媒体数据,故去掉 Image 属性的上报,只上报文本以及数值型数据,这样更符合普通的 IoT 设备场景。

最终结论:EdgeX 运行在 1GHz, 512MB 内存的 ARM 设备上,机会还是比较大的。但一个不确定因素是,我们当前的测试,没有开启 EdgeX 的安全机制。开启安全机制后,对资源的消耗肯定会更多。

详细数据

我们拿 core-data 以及 device-simple 两个服务,来分析 CPU 以及内存占用。先来看 core-data 服务。

$ go tool pprof core-data cpu.prof
File: core-data
Build ID: 73afc378ab00c91fecf93fa688e1f178276ffeab
Type: cpu
Time: Aug 5, 2021 at 10:52pm (CST)
Duration: 3.86mins, Total samples = 4.04s ( 1.75%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1300ms, 32.18% of 4040ms total
Dropped 230 nodes (cum <= 20.20ms)
Showing top 10 nodes out of 198
      flat  flat%   sum%        cum   cum%
     340ms  8.42%  8.42%      360ms  8.91%  syscall.Syscall
     150ms  3.71% 12.13%      150ms  3.71%  runtime.epollwait
     130ms  3.22% 15.35%      130ms  3.22%  runtime._LostSIGPROFDuringAtomic64
     120ms  2.97% 18.32%      210ms  5.20%  encoding/json.checkValid
     110ms  2.72% 21.04%      110ms  2.72%  runtime.memmove
     100ms  2.48% 23.51%      500ms 12.38%  runtime.findrunnable
     100ms  2.48% 25.99%      400ms  9.90%  runtime.mallocgc
      90ms  2.23% 28.22%      180ms  4.46%  regexp.(*Regexp).doOnePass
      80ms  1.98% 30.20%       80ms  1.98%  runtime.futex
      80ms  1.98% 32.18%       80ms  1.98%  runtime.memclrNoHeapPointers
(pprof) svg
Generating report in profile002.svg

Duration: 3.86mins, Total samples = 4.04s ( 1.75%) 这个信息基本可以看到,对 CPU 资源的占用不高。系统总共运行了 3.86 分钟,在采样周期内,core-data 只被运行了 4.04 秒,占系统 CPU 单核资源的 1.75%。

通过生成的 CPU 运行情况 svg 图片,可以看到,CPU 主要消耗处理设备上报的数据上。其中,一是 AddEvent 来通过 Redis 读取设备数据。二 是 unmarshalPayload 对 JSON 进行解析。这些分析结论,符合我们设计的测试场景。

core-data cpu

接下来,我们来看内存资源消耗情况。

$ go tool pprof core-data mem.prof
File: core-data
Build ID: 73afc378ab00c91fecf93fa688e1f178276ffeab
Type: inuse_space
Time: Aug 5, 2021 at 10:55pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) sample_index=alloc_space
(pprof) top
Showing nodes accounting for 8354.43kB, 76.54% of 10914.46kB total
Showing top 10 nodes out of 43
      flat  flat%   sum%        cum   cum%
 2049.50kB 18.78% 18.78%  2049.50kB 18.78%  github.com/go-redis/redis/v7/internal/proto.(*Reader).readStringReply
 1184.27kB 10.85% 29.63%  1184.27kB 10.85%  runtime/pprof.StartCPUProfile
 1024.40kB  9.39% 39.01%  4097.92kB 37.55%  github.com/edgexfoundry/go-mod-messaging/v2/internal/pkg/redis.(*goRedisWrapper).Receive
 1024.01kB  9.38% 48.40%  2048.09kB 18.76%  encoding/json.(*decodeState).object
  512.10kB  4.69% 53.09%   512.10kB  4.69%  encoding/json.Marshal
  512.04kB  4.69% 57.78%  2048.09kB 18.76%  github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/requests.(*AddEventRequest).Unmarshal
  512.04kB  4.69% 62.47%   512.04kB  4.69%  github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/requests.AddEventRequest.Validate
  512.03kB  4.69% 67.16%   512.03kB  4.69%  github.com/edgexfoundry/go-mod-core-contracts/v2/dtos.ToReadingModel
  512.02kB  4.69% 71.85%   512.02kB  4.69%  strings.(*Builder).grow (inline)
  512.01kB  4.69% 76.54%  3072.15kB 28.15%  github.com/edgexfoundry/edgex-go/internal/pkg/infrastructure/redis.addEvent
(pprof)

我们通过 sample_index=alloc_space 来查看内存累计分配情况。默认情况下,pprof 显示的是内存占用情况。两者的区别是,如果在程序运行过程中,分配完马上释放,则不计入内存使用 (inuse_space),但会计入 alloc_space。作为内存资源,我们更关注峰值情况。

通过上述数据,可以看到 Showing nodes accounting for 8354.43kB, 76.54% of 10914.46kB total,在 3.86 分钟内,累计分配了 8MB 的堆内存。看起来,内存资源占用并不多。

按照我们设计的测试方案,每均每秒将近 7 次事件(5 + 5/3)上报,3.86 分钟内,总共上报了 3.86 * 60 * 7 = 1621.2 个事件。这样算下来,每个事件上报大概会分配内存 8354 / 1621 = 5KB。粗略换算,如果每秒有 100 个事件上报,大概占用 500KB 的内存。从这个数据来看,内存资源消耗还是很少的。

通过生成堆内存消耗情况的 svg 图片,可以清晰地看到:

  1. SubscribeEvents 累计使用 5MB 的资源。这里面主要包括通过 Redis 客户端读取数据,对数据进行 JSON 解析,以及数据类型转换。
  2. redisClientSubscribe 累计使用了近 4MB 的资源。这里主要是 Redis 客户端订阅数据及处理数据过程中的内存资源消耗。
  3. main 是系统服务初始化,累计使用了近 1MB 的资源。

总体来看,符合我们的分析场景。

core-data alloc space

接着,我们看 device-simple 设备服务的 CPU 占用情况:

$ go tool pprof device-simple cpu.prof
File: device-simple
Build ID: 25762f65b4ab8c689fad4df7f11869635b5f094b
Type: cpu
Time: Aug 5, 2021 at 10:52pm (CST)
Duration: 3.86mins, Total samples = 3.04s ( 1.31%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1220ms, 40.13% of 3040ms total
Dropped 169 nodes (cum <= 15.20ms)
Showing top 10 nodes out of 237
      flat  flat%   sum%        cum   cum%
     210ms  6.91%  6.91%      210ms  6.91%  runtime._LostSIGPROFDuringAtomic64
     160ms  5.26% 12.17%      160ms  5.26%  runtime.epollwait
     150ms  4.93% 17.11%      800ms 26.32%  runtime.findrunnable
     150ms  4.93% 22.04%      150ms  4.93%  syscall.Syscall
     110ms  3.62% 25.66%      110ms  3.62%  runtime._ExternalCode
     100ms  3.29% 28.95%      110ms  3.62%  kernelcas
     100ms  3.29% 32.24%      110ms  3.62%  runtime.heapBitsSetType
      90ms  2.96% 35.20%       90ms  2.96%  runtime.futex
      80ms  2.63% 37.83%      300ms  9.87%  runtime.netpoll
      70ms  2.30% 40.13%       70ms  2.30%  runtime.memmove
(pprof)

从数据 Duration: 3.86mins, Total samples = 3.04s ( 1.31%) 来看,比 core-data 少一些。总体 CPU 占用不多。当然,这里的测试,我们去掉了 Image 属性的上报,并没有进行官方示例程序里的图片编码解码。

内存占用情况如下:

$ go tool pprof device-simple mem.prof
File: device-simple
Build ID: 25762f65b4ab8c689fad4df7f11869635b5f094b
Type: inuse_space
Time: Aug 5, 2021 at 10:55pm (CST)
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) sample_index=alloc_space
(pprof) top
Showing nodes accounting for 18056.90kB, 100% of 18056.90kB total
Showing top 10 nodes out of 43
      flat  flat%   sum%        cum   cum%
14469.41kB 80.13% 80.13% 15493.54kB 85.80%  github.com/edgexfoundry/device-sdk-go/v2/internal/transformer.CommandValuesToEventDTO
 1024.02kB  5.67% 85.80%  1538.64kB  8.52%  regexp.makeOnePass.func1
  514.63kB  2.85% 88.65%   514.63kB  2.85%  regexp.mergeRuneSets.func2 (inline)
  512.38kB  2.84% 91.49%  1024.72kB  5.67%  encoding/json.Marshal
  512.34kB  2.84% 94.33%   512.34kB  2.84%  encoding/json.encodeByteSlice
  512.10kB  2.84% 97.16%   512.10kB  2.84%  fmt.Sprintf
  512.02kB  2.84%   100%  1024.12kB  5.67%  github.com/edgexfoundry/device-sdk-go/v2/internal/cache.(*profileCache).ResourceOperation
         0     0%   100%   512.34kB  2.84%  encoding/json.(*encodeState).marshal
         0     0%   100%   512.34kB  2.84%  encoding/json.(*encodeState).reflectValue
         0     0%   100%   512.34kB  2.84%  encoding/json.structEncoder.encode
(pprof)

Showing nodes accounting for 18056.90kB, 100% of 18056.90kB total 从这个信息来看,总共消耗 18MB 的堆内存。比 core-data 要多出近 10MB。

按照我们设计的测试方案,每均每秒将近 7 次事件(5 + 5/3)上报,3.86 分钟内,总共上报了 3.86 * 60 * 7 = 1621.2 个事件。这样算下来,每个事件上报大概会分配内存 18056 / 1621 = 11KB。粗略换算,如果每秒有 100 个事件上报,大概占用 1MB 的内存。从这个数据来看,内存资源消耗还是很少的。

device-simple alloc space

从导出的 svg 图片来看,通过 autoevent 读取设备设备属性时,累计分析了近 15MB 的内存,其中大部分都是在 CommandValuesToEventDTO 函数上分配的。且分配出来的内存,有将近 14MB 通过 Redis 写入数据库。

进一步计算,每个事件写入 redis 数据库,大概需要消耗 15000 / 1621 = 9KB 的内存。

(完)


Post by Joey Huang under edge on 2021-08-06(Friday) 23:20. Tags: edgex, raspberry pi, pprof,


Powered by Pelican and Zurb Foundation. Theme by Kenton Hamaluik.