• [云计算周刊] 云计算如何推动广电行业多元融合进程?
    随着5G、移动互联网、云计算等新技术的出现,加速了各行各业的数字化转型,传统媒体在新技术的驱动下迎来了新一轮的技术变革。广电媒体行业在采、编、存、管、发各个环节,都迫切需要更专业化、智能化的媒体处理、管理平台,以提高内容生产力。传统媒体“单一渠道采集、封闭式生产、点对面单向传播”的运作模式,向“全媒体汇聚、共平台生产、多渠道分发”的新型传播方式转变。且云平台能够成为标准化、模块化、开放性的业务支撑平台,服务能力灵活组合,可自助调度资源,调整服务策略,业务迁移能够做到平滑无修改。目前,广电行业持续快速发展需要解决以下几个关键需求:存储性能:需要实现海量非结构化文件的存储,并确保存储性能不随文件数增长而性能下降。可扩展性:电视台每天产生的大量节目素材和成片等数据需要存储容量,并要求能够根据业务量可以方便扩展。备份容灾:多媒体内容成为广电行业重要的数字资产,为了应对文件、数据丢失或损坏等可能出现的意外情况,数据需要进行备份容灾。业务应用:传统系统业务流程繁复、单一、适配能力差,重型业务流程无法适应互联网环境下快速、简洁、多样化、高适应性的需求。有孚网络凭借着以北京、上海、深圳为中心,涵盖京津冀、长三角、粤港澳大湾区等重要热点区域的数据中心布局,能够满足融媒体数据管理过程中超大规模的存储需求。针对传统广电传媒行业的转型中的各项IT痛点,分布式结构自带良好的扩展性,支持存储PB级存储容量和千亿级文件数集中管理的有孚专有存储云平台,满足单机故障不影响业务,带伤运行不降速的需求,支持服务与容量双扩展,存储性能随总体容量线性提升,有效解决海量数据的存储和高速计算的问题。有孚网络通过对象存储访问接口将媒体数据安全、快速存储在对象存储中,提供更高的持续读写性能,文件迁移效率以及更高的扩展性。同时,能够在数据处理中完成图片压缩、缩略、裁剪、转格式,及音视频转码、水印、抽帧、拼接等处理。在数据安全方面,有孚专有存储云依托自建数据中心,提供纯物理环境,大大增长了数据的安全保密性。此外,用户无需投入额外的设备或进行特殊的网络配置,直接采用VPN或专线多种组网方式的数据通道建立,通过VPN或专线,可连接云端到用户自有云计算数据中心。不同于传统的文件/块的数据存储,以图片、视频为主的非结构化数据在数据保护方面具有其特性。有孚专有存储云采用分布式存储架构,可跨设备存储、实时数据同步,同时还支持云上两地三中心灾备方案在,主节点系统发生灾难时,容灾系统保障数据安全,保证客户数据万无一失。在保障成本可控的前提下,以CDN、专线、SD-WAN、EIP线路场景化定义网络,提供兼顾专网、公网、云上下的高性能网络,保障从采集、收录,到管理、分发,打破地域限制的高效、安全协作;内容生产商云剪辑服务,与内容服务商视频点播、直播服务场景直接融合,形成视频生产,录制、剪辑、转码,存储管理,分发加速于一体的高效闭环视频服务体系,高效生产快速传播。传统媒体要想更好地发挥自身优势,需要综合利用各方资源,结合云计算及大数据时代的技术创新,走上多元融合的数字化发展之路。有孚网络丰富的云计算数据中心资源、安全的同城/异地灾备方案、完善的分布式环网、资深的客户服务团队,能够为融媒体数据管理提供有效解决方案,有孚网络将持续助力融媒体内容产品升级。
  • [应用开发] 【MDC300F】【CAN】canfd使用method发送大量数据问题
    【功能模块】使用canfd通路配置来支持can【操作步骤&问题现象】1、canfd的通路配置可以兼容can,配置canfd 的channel 8为250k接收整车报文,channel 9 为500k 接收摄像头报文,channel 5为500k 向外转发整车部分报文,channel 11为500k 向外转发摄像头报文2、channel 9 接收摄像头频率为100ms 70帧数据,程序中开启了一个发送线程,对报文进行简单处理之后,使用method向channel11发送3、channel 8 接收整车报文频率为100ms 30帧数据,程序中开启了另外的发送线程,对报文进行简单处理之后,使用method向channel 5发送4、程序运行一段时间之后,异常退出另外:文档中指出“CAN下发数据频率最高100Hz”,意思是1s最多发送100帧数据吗?【日志信息】(可选,上传日志内容或者附件)2019/04/20 02:45:42.823489   99047406 002 ECU1 CAN- INTM log info V 2 [setup console output in DLT back-end. return-code: 0]2019/04/20 02:45:42.826178   99047432 000 ECU1 CAN- CM-- log info V 1 [CM version is: 2.43]2019/04/20 02:45:42.827396   99047445 001 ECU1 CAN- CM-- log info V 1 [RT_DDS_URI is set with RT_DDS_URI=/home/mdc/merlin/canfd_abstract/Debug/etc/dds.xml]2019/04/20 02:45:42.827740   99047448 002 ECU1 CAN- CM-- log info V 1 [SOMEIP_CONFIG_FILE is set with SOMEIP_CONFIG_FILE=/home/mdc/merlin/canfd_abstract/Debug/etc/vsomeip.json]2019/04/20 02:46:38.764639   99606817 003 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.861858   99607789 004 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.889568   99608066 005 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.921894   99608389 006 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.931948   99608490 007 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.941968   99608590 008 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.958648   99608757 009 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]2019/04/20 02:46:38.991940   99609090 010 ECU1 CAN- CM-- log warn V 1 [The same sessionId already exists in sessionId list,and the old one will be replaced by new one]。。。。。。。。segmentation fault然后程序异常退出
  • [技术干货] CANN AICPU算子耗时分析及优化探索
    1.    分析目的在实际开发CANN算子的过程中,常常出现算子功能正常,但性能远低于TensorFlow对标算子的情况。针对这个问题,我以GreaterEqual作为测试算子,该算子计算逻辑较为简单(output = input1 >= input2),旨在尽可能降低计算耗时,使得算子耗时尽可能以数据操作和算子调度作为主体。2.    测试代码与平台介绍本次测试平台为OpenLab提供的Ascend服务器,搭载Ascend910A,CANN Toolkit版本号为5.0.2alpha005。自研测试代码参考cac625f243dfe7b04dbb2a82059cd0e4349f77d1这一commit进行修改,该commit针对广播操作性能进行了优化。自研设置并行阈值:含广播操作计算为8K,不含广播操作计算为32K。GreaterEqual的TensorFlow对标算子为TensorFlow1.15版本算子,canndev对标算子commit为d660e086717b94b8cfb3f35a8e08046ca0461772,该版本算子尝试利用Eigen库的broadcast操作规避canndev源码仓Bcast性能不足的问题,但未启用并行计算进行加速。测试数据我设置了涉及广播操作和不涉及广播操作的两批数据,涉及广播操作的测试数据又分为需广播Tensor的元素个数为1和元素个数不为1两种,测试了int8、int16、int32、int64、uint8、float16、float32、float64共8种TensorFlow对标算子支持的数据类型,每种数据类型分别设置了128B、256B、1K、2K、4K、8K、16K、32K、64K、128K、256K、1M、2M、8M共14个数据规模梯度,详细数据规模与shape对应关系如下:不涉及广播操作数据规模Shapes128B[1,128], [1,128], [1,128]256B[1,256], [1,256], [1,256]1K[1,1024], [1,1024], [1,1024]2K[2,1024], [2,1024], [2,1024]4K[4,1024], [4,1024], [4,1024]8K[8,1024], [8,1024], [8,1024]16K[16,1024], [16,1024], [16,1024]32K[32,1024], [32,1024], [32,1024]64K[64,1024], [64,1024], [64,1024]128K[128,1024], [128,1024], [128,1024]256K[256,1024], [256,1024], [256,1024]1M[1,1024,1024], [1,1024,1024], [1,1024,1024]2M[2,1024,1024], [2,1024,1024], [2,1024,1024]8M[8,1024,1024], [8,1024,1024], [8,1024,1024]表1  不涉及广播操作数据规模与shape对应列表涉及广播操作数据规模Shapes128B[1,128], [128], [1,128]256B[1,256], [256], [1,256]1K[1,1024], [1024], [1,1024]2K[2,1024], [1024], [2,1024]4K[4,1024], [1024], [4,1024]8K[8,1024], [1024], [8,1024]16K[16,1024], [1024], [16,1024]32K[32,1024], [1024], [32,1024]64K[64,1024], [1024], [64,1024]128K[128,1024], [1024], [128,1024]256K[256,1024], [1024], [256,1024]1M[1,1024,1024], [1024], [1,1024,1024]2M[2,1024,1024], [1024], [2,1024,1024]8M[8,1024,1024], [1024], [8,1024,1024]表2  涉及广播操作数据规模与shape对应列表涉及广播操作(需广播Tensor的元素个数为1)数据规模Shapes128B[1,128], [1], [1,128]256B[1,256], [1], [1,256]1K[1,1024], [1], [1,1024]2K[2,1024], [1], [2,1024]4K[4,1024], [1], [4,1024]8K[8,1024], [1], [8,1024]16K[16,1024], [1], [16,1024]32K[32,1024], [1], [32,1024]64K[64,1024], [1], [64,1024]128K[128,1024], [1], [128,1024]256K[256,1024], [1], [256,1024]1M[1,1024,1024], [1], [1,1024,1024]2M[2,1024,1024], [1], [2,1024,1024]8M[8,1024,1024], [1], [8,1024,1024]表3  涉及广播操作数据规模与shape对应列表(需广播Tensor的元素个数为1)3.    单线程性能分析这一部分旨在测试单线程处理数据CANN算子与TensorFlow算子性能差距。为避免广播操作对测试结果产生影响,本次测试数据采用不涉及广播操作的数据批次。图1  单线程耗时比例可以看出,对于数据量低于2K的小型数据规模,CANN算子相比于TensorFlow有一定性能优势,但随着数据量的增加,CANN算子性能出现显著性能劣化,尤其是uint8这一数据类型,劣化程度十分严重,性能劣化高达6.57倍。对于非C++标准的float16这一数据类型,二者均采用Eigen库中的half数据类型进行代替,测试结果性能较为接近。图2  计算1K数据耗时       我还测试了CANN和TF单核计算16K-8M数据量时,计算1K数据所消耗的时间。可以看出,TensorFlow随着数据类型占用空间的增大,耗时也成比例的相应增加。而奇怪的是,CANN的int8、uint8耗时与int16相近,这一特点同样体现在耗时比例int8和uint8的性能劣化程度远高于其他数据类型,猜测有可能是因为int8和uint8是扩展至16位再进行计算。CANN在float32和float64这两个数据类型的表现也十分奇怪,随着数据量的增加,耗时发生了较大波动。具体情况在向量化代码与性能分析部分尝试进行了分析优化。4.    自研算子与主仓已实现算子性能对比Canndev主仓GreaterEqual算子,尝试利用Eigen库的broadcast操作规避canndev源码仓广播性能不足的问题,但未启用并行计算进行加速。自研算子使用canndev仓中的Bcast类进行广播,对是否需要广播的情况进行细化与特殊化,针对不同数据规模设置并行阈值。本部分分别测试了涉及广播操作和不涉及广播操作的两批数据,旨在测试canndev提供的方法和Eigen提供的broadcast操作性能优劣,及自研算子的性能优势。图3  不含广播操作耗时比例图4  含广播操作耗时比例从结果可以看出,当不开启广播操作时,自研算子性能全面优于已有算子,小数据量时由于直接操作指针,并未同已有算子通过Eigen的broadcast方法检查后再进行处理,性能有一定优势,大数据量由于开启多线程,性能远优于已有算子。但是开启广播操作后,由于并行阈值设定在8K,小数据量均同为单线程处理数据,可见目前CANN的Bcast性能劣于Eigen实现的broadcast,数据量大于8K后,由于多线程的并行处理优势,自研算子性能远超已有算子。TensorFlow实现的广播操作相比于Eigen实现的broadcast和CANN实现的Bcast均有较大的性能优势,同为单线程领先Eigen实现的broadcast 8-26倍,领先CANN则更多。5.    并行阈值对比由于参考算子为广播优化后的Less算子,我设置了一个对照组,阈值与Less算子的阈值相同(含广播操作计算为2K,不含广播操作计算为7K),以验证其并行阈值是否合理。为避免广播操作对测试结果产生影响,本次测试数据采用不涉及广播操作的数据批次。测试结果如下:图5  Less算子阈值和自研算子阈值耗时比例阈值可见Less算子的并行阈值设置并不合理,在8K数据规模时出现了一个明显的耗时突增,耗时主体为并行通讯耗时而非计算,自研算子相对平缓,该阈值由二分法循环测试得出,临界点并行加速比接近1。6.    向量化代码与性能分析在进行单线程性能分析时,我注意到一个很奇怪的现象,int8与int16耗时十分接近(如图2),这引起了我的注意,处理器在处理数据时,耗时会与处理的数据为定点数还是浮点数、数据的位宽、处理数据调用的指令等等因素相关,在处理相同数量的int8与int16数据时,理应int16耗时高于int8。观察TensorFlow算子执行时间,int8和uint8耗时也小于int16耗时。现代处理器往往支持SIMD(单指令流多数据流),通过将数据打包在一个向量寄存器中,一个运算指令内执行多个数据的计算,从而实现DLP(Data Level Parallelism),达到加速数据密集型运算的效果。而GreaterEqual算子计算过程不包含分支选择结构,计算逻辑简单重复,适合利用SIMD进行加速。查阅资料发现Ascend910处理器中的AICPU为16个核心的TaiShan核心,通过系统查询,支持AArch64指令集,其中也包含了NEON指令集。我尝试在C++实现代码中嵌入汇编代码来实现手动向量化,性能的确大幅提升。虽然理论上手工向量化能够实现最高程度的向量化,但由于不同处理器提供的SIMD 扩展指令集各不相同,不同应用程序特征也复杂多变,SIMD 向量化代码的可读性较差,可移植程度较低,并难以进行继续优化。考虑到未来算子代码可能需要迁移到x86-64、ARM等不同架构的CPU上,最终选择编译器自动生成针对目标处理器SIMD 扩展的向量程序。自动向量化程序员无需关心底层提供的SIMD 扩展部件结构和指令集等问题,只需要把程序中存在的并行性表达清楚,很大程度上解决了高性能代码可移植性低的问题。查询canndev主仓代码内容,向量化优化相关关键词仅在TFPlugin中出现,检查CmakeLists.txt的编译选项仅进行了O2优化。由于编译AICPU代码的编译器为GCC,通过查阅GCC文档,O2包含的编译选项除包含了O1的优化选项外,还包含了以下选项:-fthread-jumps -fisolate-erroneous-paths-dereference -falign-functions  -falign-jumps -flra-remat -falign-loops  -falign-labels -foptimize-sibling-calls -fcaller-saves -foptimize-strlen -fcrossjumping -fpartial-inlining -fcse-follow-jumps  -fcse-skip-blocks -fpeephole2 -fdelete-null-pointer-checks -freorder-blocks-algorithm=stc -fdevirtualize -fdevirtualize-speculatively -freorder-blocks-and-partition -freorder-functions -fexpensive-optimizations -frerun-cse-after-loop  -fgcse  -fgcse-lm  -fsched-interblock  -fsched-spec -fhoist-adjacent-loads -fschedule-insns  -fschedule-insns2 -finline-small-functions -fstore-merging -findirect-inlining -fstrict-aliasing -fstrict-overflow -fipa-cp -ftree-builtin-call-dce -fipa-bit-cp -ftree-switch-conversion -ftree-tail-merge -fipa-vrp -fcode-hoisting -fipa-sra -ftree-pre -fipa-icf -ftree-vrp -fipa-ra 表4  g++ O2优化包含优化选项可以看到表3中并未包含向量化优化的编译选项,因此我们通过向CmakeLists.txt中添加-ftree-vectorize(包含-ftree-loop-vectorize和-ftree-slp-vectorize)这一编译选项来开启自动向量化,优化结果如下:图6  单线程向量化计算1K数据耗时观察图6结果,可以看到单线程进行向量化优化的代码性能大幅提升。同时我们还可以观察到,相同符号类型的定点数或浮点数的计算耗时随着数据位宽的翻倍而成比例的增加,这也对应着SIMD扩展部件的向量寄存器长度是固定的,NEON的向量寄存器长度为128bit,因此我们设置并行阈值不应该按照元素个数进行设计,而应该按照元素数据总大小来确定。图7  FP16开辟临时变量与否耗时比例尝试将Tensor内的half数据转换为float后存入临时开辟的float数组,性能反而劣化,分析原因为逐元素进行数据类型转换后赋值的开销远大于向量化带来的性能提升。图8  单线程向量化与否耗时比例图9 多线程向量化与否对比耗时比例由图9可知,经过向量化后,所有C++原生数据类型的性能均已优于TensorFlow算子。观察图10,进行向量化优化后,算子性能得到有效提升,但我们可以看到某些数据类型在数据量为128K时性能反而不如未进行优化,这里是因为向量化优化版代码并行阈值是按照数据大小进行设定的,这里可以针对不同数据类型进行更细粒度的并行阈值设定。图10 向量化与否含广播操作(需广播Tensor的元素个数为1)耗时比例我还测试了向量化优化后,单元素做广播操作的特殊情况,可以看到由于没有调用广播操作,而是直接对单个元素指针解引用,编译器能正确对这种情况实现向量化优化,因此性能也得到了显著提高。但遗憾的是,由于需要进行广播操作时,访问Tensor中的元素需要调用Bcast类的GetBroadcastXIndex和GetBroadcastYIndex方法来计算广播操作后的地址偏移量,包含了较为复杂的计算,编译器并不能对其进行向量化优化,而开辟临时空间并赋值的开销远大于向量化带来的性能提升,因此如何优化这个过程还有待研究。      图11 开启-ftree-vectorize前后反汇编代码对比由图11可知,开启-ftree-vectorize编译选项后,编译器不仅进行了自动SIMD优化,还对循环进行了unroll操作,有利于降低循环开销,提供指令级并行,优化指令流水线的调度。 对于float16这一数据类型,通过阅读Eigen库3.3.9版本源码,可以看到当计算设备为CPU时,绝大多数计算(除operator/外)是将其转换为float后再进行计算,最后将计算结果转换为half数据类型。代码片段如下:图12  Eigen库中half数据类型operator>=函数定义       这种实现方式涉及到两次数据类型转换,且由于不是调用ARM原生数据类型,不能SIMD优化,且不利于循环展开,实际计算效率远低于其他原生数据类型。     图13 反汇编代码,左为GCC11.1,右为Clang9.0.0通过查阅ARM架构官方文档,我发现Armv8.2-A中包括了半精度浮点指令,这避免了与单精度浮点之间的转换的需要,因此产生了更高性能的代码。也就说明AICPU完全可以调用数据类型__fp16来实现原生支持半精度浮点数计算。当然,GCC编译器目前对FP16的支持劣于Clang,目前只能优化类似Add这类操作基本和指令集指令相近的算子,对于GreaterEqual算子,GCC<=11.1是转成float再进行比较,而Clang>=9.0.0可以生成对应的半精度浮点数的SIMD指令集代码。但__fp16是 Arm C语言扩展,在x86-64平台上,对于FP16,只支持原生存储,计算都需要将其转换为float,GCC7.3无法编译,Clang可以进行编译。为保证代码的可移植性,并不建议使用这个数据类型。       有没有高可移植性、高性能的实现方案呢?我在翻阅Eigen更新日志的时候,发现在2021/04/19更新的Eigen 3.4-rc1版本中,Eigen::half以ARM原生支持的__fp16实现,并且改进了所有后端的向量化支持和ARM在矩阵计算方面对NEON指令集的调度。图14  Eigen更新日志图15  Eigen3.4.0 Half.h当架构为ARM64时对Eigen::half的定义    图16  Add算子反汇编代码(左为__fp16,中为3.4.0版本Eigen::half,右为3.3.9版本Eigen::half)        通过观察图16反汇编代码,可以看出编译器已成功调用fp16的SIMD指令集指令,Eigen::half生成的代码基本和__fp16无异,相较于未调用SIMD指令集、未启用原生fp16的代码更高效,不仅免去了两次类型转换,还提升了一次循环内的计算数据量(SIMD一次计算8个fp16数据,未启用SIMD指令即便是进行了循环展开,只能在一次循环内计算4个数据,且指令量远大于优化版本)。       由于个人对友商源码熟悉程度PyTorch高于TensorFlow,因此对比对象选定为PyTorch,他们对SIMD进行了部分手动优化,例如在目录aten/src/ATen/cpu/vec下,封装了Vectorized类和一系列常用计算函数,一定程度上避免了实现文件中嵌入SIMD函数导致代码可读性降低,同时通过一系列环境宏定义判断目标CPU架构,启用对应架构的SIMD函数,在自动向量化的基础上进一步优化实际向量化表现。图17  PyTorch aten/src/ATen/cpu/vec/vec256目录下文件7.    向量化的局限性当然,开启向量化是完美的么?当然不是,向量化是有一定的局限性的。目前存在的SIMD扩展部件的向量寄存器长度都是固定的,如果向量寄存器长度过长而循环迭代次数或基本块内同构语句条数较少,则程序不能被向量化。SIMD对数据地址连续与否对执行效率有很大影响,当访存地址不在对齐的边界上时,则需要进行额外的移位和合并操作,才能得到满足要求的向量数据。非对齐访存结构不仅增加了额外的访存操作,而且增加了特殊的操作(例如移位和合并操作等),才能得到满足 SIMD 扩展部件要求的向量数据。由于Tensor的数据逻辑地址是对齐的,对于Element-wise类算子,这个问题并没有产生过大影响。一些程序由于其迭代次数不足,或者基本块内向量并行的语句不够多,不足以为向量寄存器提供足够的并行,需要进行不充分SIMD向量化。通过在算子实现代码中内嵌手写的汇编代码或编译器提供的内函数来添加SIMD指令,理论上手工向量化能够实现最高程度的向量化,但由于不同处理器提供的SIMD扩展指令集各不相同,会导致代码的可移植性大幅下降,并难以进行继续优化。而自动向量化目前对代码的优化还有一定局限性。循环展开会造成一定程度的代码膨胀。ARM的NEON扩展的浮点数计算并没有完全实现符合IEEE 754标准的浮点运算,尤其是非正则化值会被当做0来处理,为保证计算精度,在编译选项不启用-funsafe-math-optimizations选项时,部分不安全浮点计算的NEON代码GCC编译器不会在自动向量化中实现,这也进一步限制了ARM的SIMD性能表现。8.    总结与优化建议总结按照目前canndev源码仓的编译选项,各种数据类型的性能在4K以上数据规模时均和TensorFlow有较大性能差距,且int8和uint8耗时异常,有可能按照16bit进行计算处理。对于Float16的处理canndev和TensorFlow均采用了Eigen库的half,性能差距在所有数据类型中最小,但是差距比例还是高达1.3x。目前canndev源码仓中的GreaterEqual算子未启用多核,且未对无需广播的情况进行特化处理,因此在无需广播的情况下性能远低于自研算子。而涉及非单元素的广播操作时,由于Eigen库的广播性能优于canndev的Bcast,小数据量canndev源码仓中的GreaterEqual算子性能优于自研算子,但随着数据量增大,开启多核后,自研算子性能超过源码仓的算子。自研算子参考源码仓中的Less算子进行设计,两个算子计算逻辑基本相同,但Less算子设计的并行阈值偏低,导致所有数据类型在8K数据规模时出现一个明显的耗时波峰,后移并行阈值后情况改善。目前canndev主仓的编译选项并未启用自动向量化,开启自动向量化后能被正确向量化的代码性能大幅提高,且在不启用-funsafe-math-optimizations编译选项时,计算精度未出现明显变化。从汇编指令的角度探索了算子代码向量化情况,Eigen<3.4版本的half数据类型不是通过ARM原生支持的__fp16进行实现,因此无法进行向量化优化,Eigen 3.4-rc1以及之后的版本底层通过__fp16实现,可以正确调用SIMD指令,性能大幅提升。优化建议优化Less算子并行阈值,使临界数据量并行加速比尽量接近于1。开启编译器自动向量化选项-ftree-vectorize,充分提高CPU在一个时钟周期的计算效率。升级Eigen版本至3.4以及之后的版本,在进行交叉编译时指定对应ARM架构,并且开启fp16支持,如-march=armv8.2+fp16,可实现fp16在ARM平台上的原生支持,由编译器进行SIMD优化和循环展开,有效提升Eigen::half在ARM架构上的性能表现。优化Bcast的实现逻辑,目前版本依赖算子开发人员进行手动判断是否需要广播操作,并提取三种特殊情况进行手动实现(无需Broadcast、X为一个元素、Y为一个元素),算子实现代码充斥大量冗余代码,应把例如判断是否需要广播的操作进行抽象,通过统一接口对元素进行访问。优化Bcast需广播情况的获取元素索引方法的实现方式,目前仓库中的Bcast性能远低于TensorFlow,落后于Eigen库的broadcast,且目前GetBroadcastXIndex方法的实现对编译器优化不友好。9.    结语本文仅为一位CANN算子开发者对AICPU算子耗时的简单分析和优化方案探索,分析和优化思路较为粗糙,不当之处,还请华为专家不吝赐教,也希望能有机会和相关专家探讨交流优化方案。
  • [新手课堂] top命令
    top是Linux下最常见的监控命令,可以对CPU、内存、进程进行监控,数据说明:user:当前挡路用户数load average:load average 中分别有三个值,这三个值分别表示在过去1分钟、5分钟、15分钟的系统负载,即任务队列的平均长度。load average最好不要大于Cpu的核数,否则系统都超负载了。Task:进程总数:running 正在运行的进程数sleeping 休眠的进程数stopped 停止的进程数zombie 僵尸进程数%Cpu(s):这里的cpu为整个cpu平均数据,如果想要看每个核的cpu数据,可以按数字1,查看具体的cpu数据,如下:us:用户空间占用cpu百分比sy:内核空间占用cpu百分比ni:用户进程空间内改变过优先级的进程占用cpu百分比wa:等待输入输出的cpu时间百分比hi:硬中断si:软中断这里注意,很多人在看cpu使用率的时候,以为达到100%,cpu就满负荷了,但其实不是的,实际工作中,经常会出现cpu大于100%的情况,这里cpu主要是根据你系统的核数也决定的,比如我的的操作系统是8核的,那么cpu可以高达800%。
  • [技术干货] 华为云DCS社区版Redis6.0技术大揭秘
    自从Redis进入6.0版本之后,新特性和功能改进每月都有新变化,升级速度简直是开挂上天啦!并且,对于 6.0 版本,Redis 之父 Antirez 在 RC1 版本发布时(2019-12-19)在他的博客上连续用了几个“EST”词语来评价:这个版本提供了诸多令人心动的新特性及功能改进,比如新网络协议 RESP3,新的集群代理,ACL 等,其中关注度最高的应该是“多线程”了。华为云DCS也第一时间启动了对Redis 6.0的支持工作,经过大量前期工作筹备,华为云DCS社区版 Redis 6.0已于2021年8月初发布,正在公测 。同时,与开源Redis6.x相比,DCS 社区版Redis6却是开源版本性能的1.5~3倍。那它是如何做到的呢?下面来给大家展开聊聊。DCS 社区版 Redis6.0 产品性能话不多说,先上图:1、性能对比测试如图,在400客户端连接情况下,2线程时,DCS写性能是开源的1.68倍,读性能是开源的1.54倍,时延分别比开源快39%和35%;4线程时,DCS写性能是开源的2.56倍,读性能是开源的2.22倍;时延分别比开源快61%和55%。2、性能提升剖析看官们可以看到DCS 社区版Redis 6.0版本性能有了大幅提升,那它具体是怎么做到的呢?听小哥慢慢道来。在 Redis 的多线程方案中,I/O 线程任务仅仅是通过 Socket 读取客户端请求命令并解析,却没有真正去执行命令,所有客户端命令最后还需要回到主线程去执行,因此对多核的利用率并不算高,而且每次主线程都必须在分配完任务之后忙轮询等待所有 I/O 线程完成任务之后才能继续执行其他逻辑。Redis之所以如此设计它的多线程网络模型,我认为主要的原因是为了保持兼容性,又能利用多核提升 I/O 性能,应该是一个折中的选择。华为云DCS Redis实现了真正的多线程优化提升,除了多线程网络并发,还优化了多线程事件处理机制,使我们的资源利用率和性能收益提升2~3倍。除此之外,垂直弹性伸缩也能更多层次等等。3、与开源版本深入对比下表是DCS社区版Redis 6.0与开源版本的详细对比:综上:华为云DCS Redis 6.0社区版带来了极致性能、功能全面、可靠性强、性价比高的云服务,并且完全兼容开源Redis,客户端无需修改代码,开通后即可使用,使企业完全无需后顾之忧就能享受到业务响应速度数倍提升的黄金收益。【小喇叭】:看官们,现在DCS Redis 6.0 社区版正在上线公测 ,期待大家踊跃报名,数量有限,先到先得。参考、致谢:• Redis 作者 Antirez 的博客:http://antirez.com• https://mp.weixin.qq.com/s/SkYNjypPY3iW-DH01yYAiw• https://segmentfault.com/a/1190000039223696
  • [技术干货] LINUX云服务器进程D状态问题分析
           问题描述在Linux系统环境,进程在内核模式下等待I/O完成时通常会进入不间断睡眠状态,此时使用ps或top命令输出的进程显示为D状态。       问题分析在正常情况下,不间断状态只持续几分之一秒。但是,异常情况下,进程可能会停留在此状态更长的时间或(在最坏的情况下)无限期地停留。这种状态下,即使使用“kill -9”也无法杀死进程,因为在进程唤醒之前,kill信号无法传递给进程。要确定进程不可中断的原因,就需要找出进程正在等待什么的更多信息。从RHEL5.5 版开始,RHEL包含一个内核线程,用于监视停留在D状态超过指定超时时间的进程。默认情况下,超时时间为120秒,可以使用内核参数 kernel.hung_task_timeout 修改或禁用它。当检测到此类进程时,该内核线程将有关该进程的信息(包括其内核堆栈跟踪)转储到 /var/log/messages。      我们也可以使用sysrq工具将有关进程的信息发送到/var/log/messages。       实验模拟在CentOS 8.2环境模拟一个处于D状态的进程并进行实验分析:使用LVM卷的 suspend 特性,暂停IO,使得上层应用程序处于D状态。   (1)使用losetup创建一个卷作为pv的磁盘。# dd if=/dev/zero of=/tmp/diskfile count=2048 bs=1M# losetup --show -f /tmp/diskfile         (2)使用/dev/loop0创建pv, vg和lv。# pvcreate /dev/loop0# vgcreate vg01 /dev/loop0# lvcreate -n lv01 -L 1G vg01(3)在lv上创建一个文件系统并挂载到/mnt目录。# mkfs.ext4 /dev/vg01/lv01# mount /dev/vg01/lv01 /mnt           (4)写IO同时暂停 lv。# dmsetup suspend /dev/vg01/lv01 && dd if=/dev/urandom of=/mnt/iotest.img count=1024 bs=1M      此时,该进程就会处于D状态,命令不会返回到shell提示符。可以通过另一个 shell 终端来查看这个进程:# ps axl | awk ‘$10 ~ /D/’       (5)分析/var/log/messages日志文件。在当前实验场景,我们通过暂停进程使用的lv,使进程的IO操作发生阻塞,模拟出D进程现象。我们从/var/log/messages也看到了较为详细的相关日志信息。       在实际生产环境中,当出现D进程时,进程无法被杀死,通常需要重新启动系统来解决问题。/var/log/messages日志中也并不一定会有相关日志信息输出。这时我们可以使用sysrq工具手动从内核收集D进程信息,以进行问题分析。# less /var/log/messages我们依旧在当前实验环境使用sysrq工具进行手动从内核收集D进程信息的实验操作:启用sysrq的功能:# echo 1 > /proc/sys/kernel/sysrq转储处于不可中断(阻塞)状态的任务。# echo w > /proc/sysrq-trigger将当前任务列表及其信息转储到您的控制台。# echo t > /proc/sysrq-trigger显示所有活动 CPU 的堆栈回溯。# echo l > /proc/sysrq-trigger这会将任务和线程信息转储到/var/log/messages:           问题解决将任务和线程信息转储到/var/log/messages,恢复lv的IO:# dmsetup resume /dev/vg01/lv01恢复LV的IO后,进程状态也由D变为R:         背景知识    1、LINUX进程的几种状态:(1)TASK_RUNNING:(R)进程当前正在运行,或者正在运行队列中等待调度。只有在该状态的进程才可能在CPU上运行,同一时刻可能有多个进程处于运行状态。(2)TASK_INTERRUPTIBLE:(S)进程处于睡眠状态,处于这个状态的进程因为等待某事件的发生(比如等待socket连接、等待信号量),而被挂起。当这些事件发生时,对应的等待队列中的一个或多个进程将被唤醒。一般情况下,进程列表中的绝大多数进程都处于TASK_INTERRUPTIBLE状态。进程可以被信号中断。接收到信号或被显式的唤醒呼叫唤醒之后,进程将转变为TASK_RUNNING 状态。(3)TASK_UNINTERRUPTIBLE:(D)不可中断的睡眠状态,此进程状态类似于 TASK_INTERRUPTIBLE,只是它不会处理信号。不可中断,指的是进程不响应异步信号,无法用kill命令关闭处于TASK_UNINTERRUPTIBLE状态的进程。(4)TASK_STOPPED:进程已中止执行,它没有运行,并且不能运行。接收到 SIGSTOP 和 SIGTSTP 等信号时,进程将进入这种状态。接收到 SIGCONT 信号之后,进程将再次变得可运行。(5)TASK_TRACED:(T)正被调试程序等其他进程监控时,进程将进入这种状态。(6)EXIT_ZOMBIE:(Z)进程已终止,它正等待其父进程收集关于它的一些统计信息。不可被kill, 即不响应任务信号, 无法用SIGKILL杀死(7)EXIT_DEAD:(X)最终状态(正如其名)。将进程从系统中删除时,它将进入此状态,因为其父进程已经通过 wait4() 或 waitpid() 调用收集了所有统计信息。EXIT_DEAD状态是非常短暂的,几乎不可能通过ps命令捕捉到。(8)TASK_KILLABLE:Linux® kernel 2.6.25 引入了这种进程状态,用于将进程置为睡眠状态,它可以替代有效但可能无法终止的 TASK_UNINTERRUPTIBLE 进程状态,以及易于唤醒但更加安全的 TASK_INTERRUPTIBLE 进程状态。     2、关于SysRq:(1)什么是SysRq键?“SysRq”键也称为“Print Screen”键,按下SysRq键,LINUX内核会响应,除非内核完全崩溃。关于SysRq的参考链接:https://www.kernel.org/doc/html/latest/admin-guide/sysrq.html(2)如何启用SysRq键:在配置LINUX内核时,如果对CONFIG_MAGIC_SYSRQ配置Y选项,完成内核编译后,可以通过/proc/sys/kernel/sysrq来调用SysRq 键调用的函数。以下是 /proc/sys/kernel/sysrq 中支持的值列表:0 完全禁用 sysrq。1 启用 sysrq 的所有功能。>1 允许的 sysrq 函数的位掩码(有关详细的函数说明,请参见下文):2 =   0x2 - enable control of console logging level4 =   0x4 - enable control of keyboard (SAK, unraw)8 =   0x8 - enable debugging dumps of processes etc.16 =  0x10 - enable sync command32 =  0x20 - enable remount read-only64 =  0x40 - enable signalling of processes (term, kill, oom-kill)128 =  0x80 - allow reboot/poweroff256 = 0x100 - allow nicing of all RT tasks您可以通过以下命令在文件中设置值:echo "number" >/proc/sys/kernel/sysrq注意: /proc/sys/kernel/sysrq的值仅影响通过键盘进行的调用;/proc/sysrq-trigger则允许通过命令进行操作(需要具有管理员权限)。/proc/sysrq-trigger支持的命令及对应的功能如下: 命令功能b将立即重新启动系统而不同步或卸载您的磁盘。c如果配置,将执行系统崩溃并进行故障转储。d显示持有的所有锁。e向除 init 之外的所有进程发送 SIGTERM。f将调用 oom 杀手来杀死一个内存占用进程,但如果没有任何东西可以杀死,请不要惊慌。g由 kgdb(内核调试器)使用h将显示帮助(实际上,此处列出的任何其他键都将显示帮助。但h很容易记住:-)i向除 init 之外的所有进程发送 SIGKILL。j强行“解冻” - 文件系统被 FIFREEZE ioctl 冻结。k安全访问密钥 (SAK) 杀死当前虚拟控制台上的所有程序。注意:请参阅下面 SAK 部分中的重要注释。l显示所有活动 CPU 的堆栈回溯。m将当前内存信息转储到您的控制台。n用于使 RT 任务变得很好o将关闭您的系统(如果已配置和支持)。p将当前的寄存器和标志转储到您的控制台。q将转储每个 CPU 的所有武装 hrtimer 列表(但不是常规 timer_list 计时器)以及有关所有时钟事件设备的详细信息。r关闭键盘原始模式并将其设置为 XLATE。s将尝试同步所有已安装的文件系统。t将当前任务列表及其信息转储到您的控制台。u将尝试以只读方式重新挂载所有已挂载的文件系统。v强制恢复帧缓冲控制台。v导致 ETM 缓冲区转储 [ARM 特定]w转储处于不可中断(阻塞)状态的任务。x由 ppc/powerpc 平台上的 xmon 接口使用。在 sparc64 上显示全局 PMU 寄存器。转储 MIPS 上的所有 TLB 条目。y显示全局 CPU 寄存器 [SPARC-64 特定]。z转储 ftrace 缓冲区。0——9设置控制台日志级别,控制将打印到控制台的内核消息。(0,例如,将使得只有像 PANICs 或 OOPSes 这样的紧急消息才能发送到您的控制台。) 
  • [交流分享] 模型训练常用环境变量及日志设置
    ·  训练环境变量设置# 以root用户安装toolkit包. /usr/local/Ascend/ascend-toolkit/set_env.sh# 以root用户安装nnae包. /usr/local/Ascend/nnae/set_env.sh# 以root用户安装tfplugin包. /usr/local/Ascend/tfplugin/set_env.sh# python3.7.5环境变量export PATH=/usr/local/python3.7.5/bin:$PATHexport LD_LIBRARY_PATH=/usr/local/python3.7.5/lib:$LD_LIBRARY_PATH日志和图环境变量说明日志处理机制介绍日志大体分为如下两大类:系统类日志:系统运行产生的日志。主要包括:Control CPU上的系统类日志,包括用户态日志和内核态日志。非Control CPU(例如TSDUMP/LP)上的系统类日志。应用类日志:运行应用程序产生的日志,主要包括:FwkACLlib、ATC中各组件(如ACL、GE)打印的日志。Device侧AICPU 、HCCP打印的日志。·         日志存储路径假设有一个训练ACL应用程序,进程ID为pid(请以实际为准),应用程序运行完成后,可在日志目录下查看日志如图1所示。日志目录默认为“$HOME/ascend/log/plog”,也可以使用环境变量ASCEND_PROCESS_LOG_PATH指定路径(如:export ASCEND_PROCESS_LOG_PATH =/home/xxx)。其中$HOME表示Host侧用户根目录(如root用户,$HOME就是“/root”)。日志存储时如果不存在该目录,会自动创建该目录;如果存在则直接存储·   设置日志级别通过ASCEND_GLOBAL_LOG_LEVEL环境变量设置全局日志级别,只在当前shell下生效,仅对当前窗口设置全局级别及各模块日志级别为该值。如果用户环境变量设置了非法值(或没有设置值),缺省设置为INFO级别。通过执行echo $ASCEND_GLOBAL_LOG_LEVEL命令可以查看设置的日志级别,如果查询为非法值或者空,表示日志级别为缺省级别。设置ASCEND_GLOBAL_LOG_LEVEL环境变量举例:export ASCEND_GLOBAL_LOG_LEVEL=1ASCEND_GLOBAL_LOG_LEVEL取值范围如下:0:对应DEBUG级别。1:对应INFO级别。2:对应WARNING级别。3:对应ERROR级别。4:对应NULL级别,不输出日志。其他值为非法值·    重启日志进程拉起slogd进程。nohup /var/slogd > /dev/null 2>&1 &拉起sklogd进程。nohup /var/sklogd > /dev/null 2>&1 &执行如下命令确认日志进程(slogd、sklogd或log-daemon)是否被拉起。ps -elf | grep log
  • [交流吐槽] RDB的优点
    RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心,或者亚马逊 S3 中。RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
  • [新手课堂] 垃圾收集器 Garbage Collector
    Serial 收集器:一个采用单个线程并基于复制算法工作在新生代的收集器,进行垃圾收集时,必须暂停其它所有的工作线程(Stop The World),是 Client 模式下 JVM 的默认选项,-XX:+UseSerialGCSerial Old 收集器:一个采用单线程基于标记-整理算法并工作在老年代的收集器ParNew 收集器:Serial 收集器的多线程版本(使用多个线程进行垃圾收集),-XX:+UseParNewGCCMS 收集器(Concurrent Mark Sweep):一种以尽量减少停顿时间为目标的收集器,工作在老年代,基于标记-清除算法实现,-XX:+UseConcMarkSweepGCParallel Scavenge 收集器:一个采用多线程基于复制算法并工作在新生代的收集器,也被称作是吞吐量优先的 GC,是早期 jdk1.8 等版本中 Server 模式 JVM 的默认 GC 选择,-XX:+UseParallelGC,使用 Parallel Scavenge(年轻代)+ Serial Old(老年代)的组合进行 GCParallel Old 收集器:一个采用多线程基于标记-整理算法并工作在老年代的收集器,适用于注重于吞吐量及 CPU 资源敏感的场合,-XX:+UseParallelOldGC,使用 Parallel Scavenge(年轻代)+ Parallel Old(老年代)的组合进行 GCG1 收集器:jdk1.7 提供的一个工作在新生代和老年代的收集器,基于标记-整理算法实现,在收集结束后可以避免内存碎片问题,一种兼顾吞吐量和停顿时间的 GC,是 Oracle jdk1.9 以后的默认 GC 选项
  • [技术干货] 单例模式懒汉式,线程安全
    下面的这种方式可以保证线程安全,支持懒加载,优点是第一次调用才初始化,避免内存浪费。缺点是必须加锁synchronized 才能保证单例,但加锁会影响效率。样例代码:public class Singleton{    private static Singleton instance;    private Singleton(){    }    public static synchronized Singleton getInstance(){        if(instance == null){            return new Singleton();        }        return instance;    }}
  • [问题求助] 【麒麟V10】求助:容器方式运行tengine,多次对tengine执行-s reload之后,导致worker进程丢失等问题
    在华为鲲鹏920上面,操作系统是麒麟V10,通过容器方式,运行tengine,多次对tengine执行-s reload之后,会导致tengine的worker进程丢失,并且有个nginx: x的进程一直段错误:9月 11 17:37:30 ecs-42a2b766-326a-4a59-9d63-479c42b6d71c-master-0 audit[2522979]: ANOM_ABEND auid=4294967295 uid=505 gid=505 ses=4294967295 pid=2522979 comm="tengine" exe="/opt/taobao/install/tengine-2.4.4-20201105194751.el7u2/bin/tengine" sig=11 res=1
  • [技术干货] 枚举
    这种实现方式不支持懒加载,线程安全,不过还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。public enum Singleton {      INSTANCE;      public void whateverMethod() {      }  }模拟一个数据库连接类:public enum SingletonEnum {    INSTANCE;    private DBConnection connection = null;    SingletonEnum(){        connection = new DBConnection();    }    public DBConnection getConnection(){        return connection;    }}public class DBConnection{}public class TestConnection {    public static void main(String[] args) {        DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();        DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();        System.out.println(con1 == con2); //输出结果为true。    }}
  • [技术干货] 单例模式双检锁/双重校验锁
    这种方式支持懒加载,线程安全,这种方式采用双锁机制,安全且在多线程情况下能保持高性能。样例代码:public class Singleton {      private volatile static Singleton instance;    private Singleton(){    }    public static Singleton getInstance(){        if(instance==null){            synchronized (Singleton.class){                if(instance == null){                    instance = new Singleton();                }            }        }        return instance;    } }
  • [技术干货] 单例模式懒汉式,线程不安全
    下面的懒汉式是线程不安全的,支持懒加载,因为没有加锁 synchronized,所以严格意义上它并不算单例模式。样例代码:public class Singleton{    private static Singleton instance;    private Singleton(){    }    public static Singleton getInstance(){        if(instance == null){            return new Singleton();        }        return instance;    }}
  • [技术干货] 单例模式懒汉式,线程安全
    下面的这种方式可以保证线程安全,支持懒加载,优点是第一次调用才初始化,避免内存浪费。缺点是必须加锁synchronized 才能保证单例,但加锁会影响效率。样例代码:public class Singleton{    private static Singleton instance;    private Singleton(){    }    public static synchronized Singleton getInstance(){        if(instance == null){            return new Singleton();        }        return instance;    }}