Jetson Nano 性能探索

  1. Jetson Nano 是什么?
  2. 应用
  3. 软件部分
    1. 优化1 运行日志与标准输出
    2. 优化2 设置线程CPU亲和性
      1. taskset指令
      2. sched_setaffinity系统调用
    3. 除法优化
    4. 选择合适的场景,优化适配CUDA
  4. 总结
  5. 参考

Jetson Nano 是什么?

一般地,当我们讲到Jetson Nano时,一般指英伟达公司出品的一款计算平台,详细信息可以在这里看到,https://developer.nvidia.com/embedded/jetson-nano-developer-kit。

GPU 128核 Maxwell架构, CPU Quad-core ARM A57 @ 1.43 GHz 四核,支持的SM(Stream Multipleprocessor)计算能力5.3,与Jetson TX1一致。

应用

当跟手掌一样的,精致的计算平台呈现在你面前的时候,不自主地为硬件能力的迭代感到惊叹,这应该是我们日常工作接触到最小的计算平台了。

在这样的计算平台我们可以做什么?借助于GPU的资源,当然人工智能,深度学习的应用更为受到欢迎。我们今天所在的时代就是这样一个硬软件对AI都相对友好的时代,从软件到硬件总有强大的影子。

我介绍的场景基于Nano一款自动驾驶教育平台,选用的传感器有英特尔的RealSense多种类型,比如D435系列,可以有RGB图像与深度图,还可以有点云数据。以下实例中的sensor模块,均以D435I为准,输出信息包括三轴加速度,角速度,彩色图像(640480@15Hz),jpeg压缩图,640480@15Hz,点云图(640*480@15Hz)。

软件部分

我们使用到了Apollo Cyber RT 框架,作为数据承载和模块之间的数据流依托。更多的信息可以在Cyber RT的GitHub获得。

优化1 运行日志与标准输出

优化2 设置线程CPU亲和性

当我们的应用运行时,也许你通过System Monitor之类的工具遇到过这样的资源或者使用率分布情况:

任务在CPU上的占用时间相对较短,跳跃的过程中意味着增加了额外的负载,所以我们想个办法需要将运行的任务,即线程与CPU做一个简单的绑定关系。

简单地说,CPU 亲和性(affinity) 就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为软 CPU 亲和性(affinity)的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。

如何设置核与线程的绑定?此处有两种办法,1、使用taskset指令,2、使用sched_setaffinity系统调用。

taskset指令

geek-car@geekcar-desktop:~$ taskset -h
Usage: taskset [options] [mask | cpu-list] [pid|cmd [args...]]


Show or change the CPU affinity of a process.

Options:
-a, --all-tasks operate on all the tasks (threads) for a given pid
-p, --pid operate on existing given pid
-c, --cpu-list display and specify cpus in list format
-h, --help display this help
-V, --version display version

The default behavior is to run a new command:
taskset 03 sshd -b 1024
You can retrieve the mask of an existing task:
taskset -p 700
Or set it:
taskset -p 03 700
List format uses a comma-separated list instead of a mask:
taskset -pc 0,3,7-11 700
Ranges in list format can take a stride argument:
e.g. 0-31:2 is equivalent to mask 0x55555555

For more details see taskset(1).

举例:如何设置将任务绑定到多个CPU?

taskset -c 0,1 mainboard -d realsense.dag

上面的指令,执行Cyber RT 提供的mainboard,启动sensor模块,占用CPU 0 和1。效果如下:

sched_setaffinity系统调用

Linux 内核 API 提供了一些方法,让用户可以修改位掩码或查看当前的位掩码:

Cyber RT已经提供了基于配置封装的版本,我们只需要在自己的Component组件对应的启动文件中,这个组件可以是硬件数据解析发布,也可以是软件模块,比如优先级更高的控制、规划、感知等模块。指定调度配置文件即可:

mainboard -d realsense.dag -p sensor_sched

sensor_sched调度配置文件内容:

scheduler_conf {
policy: "classic"
classic_conf {
groups: [
{
name: "sensors"
processor_num: 1
affinity: "1to1"
cpuset: "1"
}
]
}
}

效果:
图;

除法优化

基于ARM架构的Jetson系列产品,一个限制是不支持除法,所以我们需要对除法运算进行优化。比如可行的的优化有:

  1. 将被除数的分子改成1。例如a / b改成a * (1 / b)。这样1 / b可能在编译阶段进一步被优化
  2. 修改除法计算顺序,增加编译器优化的可能性,如a * 180 / PI改成a * (180 / PI)。其中180 / PI在编译阶段会被直接计算出结果

除法优化方法1

关于优化方法1,如果1 / b可以在编译期间被优化,则这个改动有效果,否则将产生反作用。所以该优化方法需要慎重的被使用。而1 / b能否被优化的一个常用的判断方法是它是否是一个可以在编译期间计算的常量表达式。其中GCC的一个非常用的优化选项-freciprocal-math就是提供了这种优化效果。
请参考https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

除法优化方法2

优化方法2是一个可以明显有优化效果的改进。下面我们就从汇编层面分析一下为什么方法2能够带来优化。

float div(int a, float b) {
float f = a * 180 / 3.14f;
return f;
}

上述代码中,只是一个简单的乘法和除法。编译指令为g++ main.cpp -S -O3 指定优化级别为O3。查看到汇编代码main.s的内容为:

_Z3divif:
.LFB30:
.cfi_startproc
imull $180, %edi, %edi
cvtsi2ss %edi, %xmm0
divss .LC0(%rip), %xmm0
ret

可以看到,需要一个mul乘法指令和div除法指令完成计算。

等等!不是ARM没有除法指令吗?实际上在ARM的早期版本中没有除法指令,但是在ARMV7后的版本中很多都加入了除法指令。

那么如果TX2基于的ARM架构有除法操作,在推翻我们的前提的情况下是否还有优化除法运算的必要呢?答案是有的。在计算机常用的运算符中,运算速度可以概括为:
移位>加法=减法>乘法>除法。

继续回到代码部分。我们将c++代码进行如下的修改:

float div(int a, float b) {
float f = a * (180 / 3.14f);
return f;
}

汇编代码为:

_Z3divif:
.LFB30:
.cfi_startproc
cvtsi2ss %edi, %xmm0
mulss .LC0(%rip), %xmm0
ret

可以看到只有一个乘法运算了。括号中的除法运算在编译期间计算出来了结果。

选择合适的场景,优化适配CUDA

Cuda在并行计算上的优势,对于比如矩阵一类的数据类型,非常适用,而图像的原始数据,就是一种矩阵,所以非常合适。再来重复下我们的场景,我们需要将传感器的图做分别的处理,最后将处理结果发布出来,供其他模块使用。

关于图像的处理其实有主要有两个部分,1、传感器生成图像;2、应用模块将发布的结果处理的过程。
所以我们想如果在生成图像的过程针对CUDA做支持,将这部分的计算挪一部分在GPU上,可以留给更多的效能在软件应用模块。

此外,下游软件模块的处理,比如感知的车道线识别,模型识别,视觉定位等都需要视觉图像的地方,也可以考虑CUDA资源的适配,但因为资源要在主机和GPU之间拷贝,所以如果将CPU上的处理逻辑转移到GPU上之后,发现提升不够明显,这样的迁移其实就是没有必要的。

1、编译RealSense SDK,让其在内部成图、生成深度图,点云计算的过程中,支持CUDA。
可以使用JetsonHacksNano提供的一个类库,安装便捷。


$ git clone https://github.com/JetsonHacksNano/installLibrealsense

# 默认使用CUDA编译,即WITH_CUDA=ON
$ ./installLibrealsense.sh

支持CUDA之后的效果对比:

2、在深度使用图像处理的地方,转换到GPU处理。

OPENCV后续的版本中已经支持GPUMat,支持在CUDA上的处理格式。

总结

对于性能的探索,怎么都不为过,所以还有很多探索的地方。大家互相学习,共同提高。

参考

如果你有不同看法?
script>