专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

高德Go生态的服务稳定性建设性能优化的实战总结

  阿里妹导读
  目前go语言不仅在阿里集团内部,在整个互联网行业内也越来越流行,本文把高德过去go服务开发中的性能调优经验进行总结和沉淀,希望能为正在使用go语言的同学在性能优化方面带来一些参考价值。
  前言
  go语言凭借着优秀的性能,简洁的编码风格,极易使用的协程等优点,逐渐在各大互联网公司中流行起来。而高德业务使用go语言已经有3年时间了,随着高德业务的发展,go语言生态也日趋完善,今后会有越来越多新的go服务出现。在任何时候,保障服务的稳定性都是首要的,go服务也不例外,而性能优化作为保障服务稳定性,降本增效的重要手段之一,在高德go服务日益普及的当下显得愈发重要。此时此刻,我们将过去go服务开发中的性能调优经验进行总结和沉淀,为您呈上这篇精心准备的go性能调优指南。
  通过本文您将收获以下内容:从理论的角度,和你一起捋清性能优化的思路,制定最合适的优化方案。推荐几款go语言性能分析利器,与你一起在性能优化的路上披荆斩棘。总结归纳了众多go语言中常用的性能优化小技巧,总有一个你能用上。基于高德go服务百万级QPS实践,分享几个性能优化实战案例,让性能优化不再是纸上谈兵。
  一、性能调优理论篇
  1。1衡量指标
  优化的第一步是先衡量一个应用性能的好坏,性能良好的应用自然不必费心优化,性能较差的应用,则需要从多个方面来考察,找到木桶里的短板,才能对症下药。那么如何衡量一个应用的性能好坏呢?最主要的还是通过观察应用对核心资源的占用情况以及应用的稳定性指标来衡量。所谓核心资源,就是相对稀缺的,并且可能会导致应用无法正常运行的资源,常见的核心资源如下:cpu:对于偏计算型的应用,cpu往往是影响性能好坏的关键,如果代码中存在无限循环,或是频繁的线程上下文切换,亦或是糟糕的垃圾回收策略,都将导致cpu被大量占用,使得应用程序无法获取到足够的cpu资源,从而响应缓慢,性能变差。
  内存:内存的读写速度非常快,往往不是性能的瓶颈,但是内存相对来说容量有限切价格昂贵,如果应用大量分配内存而不及时回收,就会造成内存溢出或泄漏,应用无法分配新的内存,便无法正常运行,这将导致很严重的事故。
  带宽:对于偏网络IO型的应用,例如网关服务,带宽的大小也决定了应用的性能好坏,如果带宽太小,当系统遇到大量并发请求时,带宽不够用,网络延迟就会变高,这个虽然对服务端可能无感知,但是对客户端则是影响甚大。
  磁盘:相对内存来说,磁盘价格低廉,容量很大,但是读写速度较慢,如果应用频繁的进行磁盘IO,那性能可想而知也不会太好。
  以上这些都是系统资源层面用于衡量性能的指标,除此之外还有应用本身的稳定性指标:异常率:也叫错误率,一般分两种,执行超时和应用panic。panic会导致应用不可用,虽然服务通常都会配置相应的重启机制,确保偶然的应用挂掉后能重启再次提供服务,但是经常性的panic,会导致应用频繁的重启,减少了应用正常提供服务的时间,整体性能也就变差了。异常率是非常重要的指标,服务的稳定和可用是一切的前提,如果服务都不可用了,还谈何性能优化。
  响应时间(RT):包括平均响应时间,百分位(toppercentile)响应时间。响应时间是指应用从收到请求到返回结果后的耗时,反应的是应用处理请求的快慢。通常平均响应时间无法反应服务的整体响应情况,响应慢的请求会被响应快的请求平均掉,而响应慢的请求往往会给用户带来糟糕的体验,即所谓的长尾请求,所以我们需要百分位响应时间,例如tp99响应时间,即99的请求都会在这个时间内返回。
  吞吐量:主要指应用在一定时间内处理请求事务的数量,反应的是应用的负载能力。我们当然希望在应用稳定的情况下,能承接的流量越大越好,主要指标包括QPS(每秒处理请求数)和QPM(每分钟处理请求数)。
  1。2制定优化方案
  明确了性能指标以后,我们就可以评估一个应用的性能好坏,同时也能发现其中的短板并对其进行优化。但是做性能优化,有几个点需要提前注意:
  第一,不要反向优化。比如我们的应用整体占用内存资源较少,但是rt偏高,那我们就针对rt做优化,优化完后,rt下降了30,但是cpu使用率上升了50,导致一台机器负载能力下降30,这便是反向优化。性能优化要从整体考虑,尽量在优化一个方面时,不影响其他方面,或是其他方面略微下降。
  第二,不要过度优化。如果应用性能已经很好了,优化的空间很小,比如rt的tp99在2ms内,继续尝试优化可能投入产出比就很低了,不如将这些精力放在其他需要优化的地方上。
  由此可见,在优化之前,明确想要优化的指标,并制定合理的优化方案是很重要的。
  常见的优化方案有以下几种:优化代码
  有经验的程序员在编写代码时,会时刻注意减少代码中不必要的性能消耗,比如使用strconv而不是fmt。Sprint进行数字到字符串的转化,在初始化map或slice时指定合理的容量以减少内存分配等。良好的编程习惯不仅能使应用性能良好,同时也能减少故障发生的几率。总结下来,常用的代码优化方向有以下几种:1)提高复用性,将通用的代码抽象出来,减少重复开发。2)池化,对象可以池化,减少内存分配;协程可以池化,避免无限制创建协程打满内存。3)并行化,在合理创建协程数量的前提下,把互不依赖的部分并行处理,减少整体的耗时。4)异步化,把不需要关心实时结果的请求,用异步的方式处理,不用一直等待结果返回。5)算法优化,使用时间复杂度更低的算法。使用设计模式
  设计模式是对代码组织形式的抽象和总结,代码的结构对应用的性能有着重要的影响,结构清晰,层次分明的代码不仅可读性好,扩展性高,还能避免许多潜在的性能问题,帮助开发人员快速找到性能瓶颈,进行专项优化,为服务的稳定性提供保障。常见的对性能有所提升的设计模式例如单例模式,我们可以在应用启动时将需要的外部依赖服务用单例模式先初始化,避免创建太多重复的连接。空间换时间或时间换空间
  在优化的前期,可能一个小的优化就能达到很好的效果。但是优化的尽头,往往要面临抉择,鱼和熊掌不可兼得。性能优秀的应用往往是多项资源的综合利用最优。为了达到综合平衡,在某些场景下,就需要做出一些调整和牺牲,常用的方法就是空间换时间或时间换空间。比如在响应时间优先的场景下,把需要耗费大量计算时间或是网络io时间的中间结果缓存起来,以提升后续相似请求的响应速度,便是空间换时间的一种体现。使用更好的三方库
  在我们的应用中往往会用到很多开源的第三方库,目前在github上的go开源项目就有173万。有很多go官方库的性能表现并不佳,比如go官方的日志库性能就一般,下面是zap发布的基准测试信息(记录一条消息和10个字段的性能表现)。
  Package
  Time
  Timetozap
  ObjectsAllocated
  zap
  862nsop
  0
  5allocsop
  zap(sugared)
  1250nsop
  45
  11allocsop
  zerolog
  4021nsop
  366
  76allocsop
  gokit
  4542nsop
  427
  105allocsop
  apexlog
  26785nsop
  3007
  115allocsop
  logrus
  29501nsop
  3322
  125allocsop
  log15
  29906nsop
  3369
  122allocsop
  从上面可以看出zap的性能比同类结构化日志包更好,也比标准库更快,那我们就可以选择更好的三方库。
  二、性能调优工具篇
  当我们找到应用的性能短板,并针对短板制定相应优化方案,最后按照方案对代码进行优化之后,我们怎么知道优化是有效的呢?直接将代码上线,观察性能指标的变化,风险太大了。此时我们需要有好用的性能分析工具,帮助我们检验优化的效果,下面将为大家介绍几款go语言中性能分析的利器。
  2。1benchmark
  Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,benchmark可以帮助我们评估代码的性能表现,主要方式是通过在一定时间(默认1秒)内重复运行测试代码,然后输出执行次数和内存分配结果。下面我们用一个简单的例子来验证一下,strconv是否真的比fmt。Sprint快。首先我们来编写一段基准测试的代码,如下:packagemainimport(fmtstrconvtesting)funcBenchmarkStrconv(btesting。B){forn:0;nb。N;n{strconv。Itoa(n)}}funcBenchmarkFmtSprint(btesting。B){forn:0;nb。N;n{fmt。Sprint(n)}}
  我们可以用命令行gotestbench。来运行基准测试,输出结果如下:goos:darwingoarch:amd64pkg:maincpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkStrconv124198801427。41nsopBenchmarkFmtSprint121373817281。19nsopokmain7。039s
  可以看到strconv每次执行只用了27。41纳秒,而fmt。Sprint则是81。19纳秒,strconv的性能是fmt。Sprint的三倍,那为什么strconv要更快呢?会不会是这次运行时间太短呢?为了公平起见,我们决定让他们再比赛一轮,这次我们延长比赛时间,看看结果如何。
  通过gotestbench。benchtime5s命令,我们可以把测试时间延长到5秒,结果如下:goos:darwingoarch:amd64pkg:maincpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkStrconv1221153320731。60nsopBenchmarkFmtSprint126948128789。58nsopPASSokmain18。891s
  结果有些变化,strconv每次执行的时间上涨了4ns,但变化不大,差距仍有2。9倍。但是我们仍然不死心,我们决定让他们一次跑三轮,每轮5秒,三局两胜。
  通过gotestbench。benchtime5scount3命令,我们可以把测试进行3轮,结果如下:goos:darwingoarch:amd64pkg:maincpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkStrconv1221789455431。76nsopBenchmarkStrconv1221714013231。45nsopBenchmarkStrconv1221913682831。79nsopBenchmarkFmtSprint127068358089。53nsopBenchmarkFmtSprint126388175882。51nsopBenchmarkFmtSprint126498432982。04nsopPASSokmain54。296s
  结果变化也不大,看来strconv是真的比fmt。Sprint快很多。那快是快,会不会内存分配上情况就相反呢?
  通过gotestbench。benchmem这个命令我们可以看到两个方法的内存分配情况,结果如下:goos:darwingoarch:amd64pkg:maincpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkStrconv124370092227。46nsop7Bop0allocsopBenchmarkFmtSprint1214341280。88nsop16Bop2allocsopPASSokmain7。031s
  可以看到strconv在内存分配上是0次,每次运行使用的内存是7字节,只是fmt。Sprint的43。8,简直是全方面的优于fmt。Sprint啊。那究竟是为什么strconv比fmt。Sprint好这么多呢?
  通过查看strconv的代码,我们发现,对于小于100的数字,strconv是直接通过digits和smallsString这两个常量进行转换的,而大于等于100的数字,则是通过不断除以100取余,然后再找到余数对应的字符串,把这些余数的结果拼起来进行转换的。constdigits0123456789abcdefghijklmnopqrstuvwxyzconstsmallsString00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899smallreturnsthestringforaniwith0inSmalls。funcsmall(iint)string{ifi10{returndigits〔i:i1〕}returnsmallsString〔i2:i22〕}funcformatBits(dst〔〕byte,uuint64,baseint,neg,appendbool)(d〔〕byte,sstring){。。。forj:4;j0;j{is:us1002us100i2a〔i1〕smallsString〔is1〕a〔i0〕smallsString〔is0〕}。。。}
  而fmt。Sprint则是通过反射来实现这一目的的,fmt。Sprint得先判断入参的类型,在知道参数是int型后,再调用fmt。fmtInteger方法把int转换成string,这多出来的步骤肯定没有直接把int转成string来的高效。fmtIntegerformatssignedandunsignedintegers。func(ffmt)fmtInteger(uuint64,baseint,isSignedbool,verbrune,digitsstring){。。。switchbase{case10:foru10{inext:u10buf〔i〕byte(0unext10)unext}。。。}
  benchmark还有很多实用的函数,比如ResetTimer可以重置启动时耗费的准备时间,StopTimer和StartTimer则可以暂停和启动计时,让测试结果更集中在核心逻辑上。
  2。2pprof
  2。2。1使用介绍
  pprof是go语言官方提供的profile工具,支持可视化查看性能报告,功能十分强大。pprof基于定时器(10ms次)对运行的go程序进行采样,搜集程序运行时的堆栈信息,包括CPU时间、内存分配等,最终生成性能报告。
  pprof有两个标准库,使用的场景不同:runtimepprof通过在代码中显式的增加触发和结束埋点来收集指定代码块运行时数据生成性能报告。
  nethttppprof是对runtimepprof的二次封装,基于web服务运行,通过访问链接触发,采集服务运行时的数据生成性能报告。
  runtimepprof的使用方法如下:packagemainimport(osruntimepproftime)funcmain(){w,:os。OpenFile(testcpu,os。ORDWRos。OCREATEos。OAPPEND,0644)pprof。StartCPUProfile(w)time。Sleep(time。Second)pprof。StopCPUProfile()}
  我们也可以使用另外一种方法,nethttppprof:packagemainimport(nethttpnethttppprof)funcmain(){err:http。ListenAndServe(:6060,nil)iferr!nil{panic(err)}}
  将程序run起来后,我们通过访问http:127。0。0。1:6060debugpprof就可以看到如下页面:
  点击profile就可以下载cpuprofile文件。那我们如何查看我们的性能报告呢?
  pprof支持两种查看模式,终端和web界面,注意:想要查看可视化界面需要提前安装graphviz。
  这里我们以web界面为例,在终端内我们输入如下命令:gotoolpprofhttp:6060testcpu
  就会在浏览器里打开一个页面,内容如下:
  从界面左上方VIEW栏下,我们可以看到,pprof支持FlameGraph,dotGraph和Top等多种视图,下面我们将一一介绍如何阅读这些视图。2。2。1火焰图FlameGraph如何阅读
  首先,推荐直接阅读火焰图,在查函数耗时场景,这个比较直观;
  最简单的:横条越长,资源消耗、占用越多;
  注意:每一个function的横条虽然很长,但可能是他的下层子调用耗时产生的,所以一定要关注下一层子调用各自的耗时分布;
  每个横条支持点击下钻能力,可以更详细的分析子层的耗时占比。
  2。2。2dotGraph图如何阅读
  英文原文在这里:https:github。comgooglepprofblobmasterdocREADME。md
  节点颜色:红色表示耗时多的节点;绿色表示耗时少的节点;灰色表示耗时几乎可以忽略不计(接近零);
  节点字体大小:字体越大,表示占上层函数调用比例越大;(其实上层函数自身也有耗时,没包含在此)字体越小,表示占上层函数调用比例越小;
  线条(边)粗细:线条越粗,表示消耗了更多的资源;反之,则越少;
  线条(边)颜色:颜色越红,表示性能消耗占比越高;颜色越绿,表示性能消耗占比越低;灰色,表示性能消耗几乎可以忽略不计;
  虚线:表示中间有一些节点被移除或者忽略了;(一般是因为耗时较少所以忽略了)
  实线:表示节点之间直接调用
  内联边标记:被调用函数已经被内联到调用函数中(对于一些代码行比较少的函数,编译器倾向于将它们在编译期展开从而消除函数调用,这种行为就是内联。)
  2。2。3TOP表如何阅读flat:当前函数,运行耗时(不包含内部调用其他函数的耗时)flat:当前函数,占用的CPU运行耗时总比例(不包含外部调用函数)sum:当前行的flat与上面所有行的flat总和。cum:当前函数加上它内部的调用的运行总耗时(包含内部调用其他函数的耗时)cum:同上的CPU运行耗时总比例
  2。3trace
  pprof已经有了对内存和CPU的分析能力,那trace工具有什么不同呢?虽然pprof的CPU分析器,可以告诉你什么函数占用了最多的CPU时间,但它并不能帮助你定位到是什么阻止了goroutine运行,或者在可用的OS线程上如何调度goroutines。这正是trace真正起作用的地方。
  我们需要更多关于Go应用中各个goroutine的执行情况的更为详细的信息,可以从P(goroutine调度器概念中的processor)和G(goroutine调度器概念中的goroutine)的视角完整的看到每个P和每个G在Tracer开启期间的全部所作所为,对Tracer输出数据中的每个P和G的行为分析并结合详细的event数据来辅助问题诊断的。
  Tracer可以帮助我们记录的详细事件包含有:与goroutine调度有关的事件信息:goroutine的创建、启动和结束;goroutine在同步原语(包括mutex、channel收发操作)上的阻塞与解锁。与网络有关的事件:goroutine在网络IO上的阻塞和解锁;与系统调用有关的事件:goroutine进入系统调用与从系统调用返回;与垃圾回收器有关的事件:GC的开始停止,并发标记、清扫的开始停止。
  Tracer主要也是用于辅助诊断这三个场景下的具体问题的:并行执行程度不足的问题:比如没有充分利用多核资源等;因GC导致的延迟较大的问题;Goroutine执行情况分析,尝试发现goroutine因各种阻塞(锁竞争、系统调用、调度、辅助GC)而导致的有效运行时间较短或延迟的问题。
  2。3。1trace性能报告
  打开trace性能报告,首页信息包含了多维度数据,如下图:
  Viewtrace:以图形页面的形式渲染和展示tracer的数据,这也是我们最为关注最常用的功能Goroutineanalysis:以表的形式记录执行同一个函数的多个goroutine的各项trace数据Networkblockingprofile:用pprofprofile形式的调用关系图展示网络IO阻塞的情况Synchronizationblockingprofile:用pprofprofile形式的调用关系图展示同步阻塞耗时情况Syscallblockingprofile:用pprofprofile形式的调用关系图展示系统调用阻塞耗时情况Schedulerlatencyprofile:用pprofprofile形式的调用关系图展示调度器延迟情况Userdefinedtasks和Userdefinedregions:用户自定义trace的task和regionMinimummutatorutilization:分析GC对应用延迟和吞吐影响情况的曲线图
  通常我们最为关注的是Viewtrace和Goroutineanalysis,下面将详细说说这两项的用法。2。3。2viewtrace
  如果Tracer跟踪时间较长,trace会将Viewtrace按时间段进行划分,避免触碰到traceviewer的限制:
  Viewtrace使用快捷键来缩放时间线标尺:w键用于放大(从秒向纳秒缩放),s键用于缩小标尺(从纳秒向秒缩放)。我们同样可以通过快捷键在时间线上左右移动:s键用于左移,d键用于右移。(游戏快捷键WASD)
  采样状态
  这个区内展示了三个指标:Goroutines、Heap和Threads,某个时间点上的这三个指标的数据是这个时间点上的状态快照采样:Goroutines:某一时间点上应用中启动的goroutine的数量,当我们点击某个时间点上的goroutines采样状态区域时(我们可以用快捷键m来准确标记出那个时间点),事件详情区会显示当前的goroutines指标采样状态:
  Heap指标则显示了某个时间点上Go应用heap分配情况(包括已经分配的Allocated和下一次GC的目标值NextGC):
  Threads指标显示了某个时间点上Go应用启动的线程数量情况,事件详情区将显示处于InSyscall(整阻塞在系统调用上)和Running两个状态的线程数量情况:
  P视角区
  这里将Viewtrace视图中最大的一块区域称为P视角区。这是因为在这个区域,我们能看到Go应用中每个P(Goroutine调度概念中的P)上发生的所有事件,包括:EventProcStart、EventProcStop、EventGoStart、EventGoStop、EventGoPreempt、Goroutine辅助GC的各种事件以及Goroutine的GC阻塞(STW)、系统调用阻塞、网络阻塞以及同步原语阻塞(mutex)等事件。除了每个P上发生的事件,我们还可以看到以单独行显示的GC过程中的所有事件。
  事件详情区
  点选某个事件后,关于该事件的详细信息便会在这个区域显示出来,事件详情区可以看到关于该事件的详细信息:
  Title:事件的可读名称;Start:事件的开始时间,相对于时间线上的起始时间;WallDuration:这个事件的持续时间,这里表示的是G1在P4上此次持续执行的时间;StartStackTrace:当P4开始执行G1时G1的调用栈;EndStackTrace:当P4结束执行G1时G1的调用栈;从上面EndStackTrace栈顶的函数为runtime。asyncPreempt来看,该GoroutineG1是被强行抢占了,这样P4才结束了其运行;Incomingflow:触发P4执行G1的事件;Outgoingflow:触发G1结束在P4上执行的事件;Precedingevents:与G1这个goroutine相关的之前的所有的事件;Follwingevents:与G1这个goroutine相关的之后的所有的事件Allconnected:与G1这个goroutine相关的所有事件。
  2。3。3Goroutineanalysis
  Goroutineanalysis提供了从G视角看Go应用执行的图景。与Viewtrace不同,这次页面中最广阔的区域提供的G视角视图,而不再是P视角视图。在这个视图中,每个G都会对应一个单独的条带(和P视角视图一样,每个条带都有两行),通过这一条带可以按时间线看到这个G的全部执行情况。通常仅需在goroutineanalysis的表格页面找出执行最快和最慢的两个goroutine,在Go视角视图中沿着时间线对它们进行对比,以试图找出执行慢的goroutine究竟出了什么问题。
  2。4后记
  虽然pprof和trace有着非常强大的profile能力,但在使用过程中,仍存在以下痛点:获取性能报告麻烦:一般大家做压测,为了更接近真实环境性能态,都使用生产环境pre环境进行。而出于安全考虑,生产环境内网一般和PC办公内网是隔离不通的,需要单独配置通路才可以获得生产环境内网的profile文件下载到PC办公电脑中,这也有一些额外的成本;
  查看profile分析报告麻烦:之前大家在本地查看profile分析报告,一般gotoolpprofhttp:8083profile命令在本地PC开启一个webservice查看,并且需要至少安装graphviz等库。
  查看trace分析同样麻烦:查看gotrace的profile信息来分析routine锁和生命周期时,也需要类似的方式在本地PC执行命令gotooltracemytrace。profile。
  分享麻烦:如果我想把自己压测的性能结果内容,分享个另一位同学,那只能把1中获取的性能报告profile文件通过钉钉发给被分享人。然而有时候本地profile文件比较多,一不小心就发错了,还不如截图,但是截图又没有了交互放大、缩小、下钻等能力。处处不给力!
  留存复盘麻烦:系统的性能分析就像一份病历,每每看到阶段性的压测报告,总结或者对照时,不禁要询问,做过了哪些优化和改造,病因病灶是什么,有没有共性,值不值得总结归纳,现在是不是又面临相似的性能问题?
  那么能不能开发一个平台工具,解决以上的这些痛点呢?目前在阿里集团内部,高德的研发同学已经通过对go官方库的定制开发,实现了go语言性能平台,解决了以上这些痛点,并在内部进行了开源。该平台已面向阿里集团,累计实现性能场景快照数万条的获取和分析,解决了很多的线上服务性能调试和优化问题,这里暂时不展开,后续有机会可以单独分享。
  三、性能调优技巧篇
  除了前面提到的尽量用strconv而不是fmt。Sprint进行数字到字符串的转化以外,我们还将介绍一些在实际开发中经常会用到的技巧,供各位参考。
  3。1字符串拼接
  拼接字符串为了书写方便快捷,最常用的两个方法是运算符和fmt。Sprintf()
  运算符只能简单地完成字符串之间的拼接,fmt。Sprintf()其底层实现使用了反射,性能上会有所损耗。
  从性能出发,兼顾易用可读,如果待拼接的变量不涉及类型转换且数量较少(5),拼接字符串推荐使用运算符,反之使用fmt。Sprintf()。推荐:用进行字符串拼接funcBenchmarkPlus(btesting。B){fori:0;ib。N;i{s:abs}}不推荐:用fmt。Sprintf进行字符串拼接funcBenchmarkFmt(btesting。B){fori:0;ib。N;i{s:fmt。Sprintf(ss,a,b)s}}goos:darwingoarch:amd64pkg:maincpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkPlus1210000000000。2658nsop0Bop0allocsopBenchmarkFmt121655994970。83nsop2Bop1allocsopPASSokmain5。908s
  3。2提前指定容器容量
  在初始化slice时,尽量指定容量,这是因为当添加元素时,如果容量的不足,slice会重新申请一个更大容量的容器,然后把原来的元素复制到新的容器中。推荐:初始化时指定容量funcBenchmarkGenerateWithCap(btesting。B){nums:make(〔〕int,0,10000)forn:0;nb。N;n{fori:0;i10000;i{numsappend(nums,i)}}}不推荐:初始化时不指定容量funcBenchmarkGenerate(btesting。B){nums:make(〔〕int,0)forn:0;nb。N;n{fori:0;i10000;i{numsappend(nums,i)}}}goos:darwingoarch:amd64pkg:maincpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkGenerateWithCap1223508336485nsop476667Bop0allocsopBenchmarkGenerate122262068747nsop426141Bop0allocsopPASSokmain16。628s
  3。3遍历〔〕struct{}使用下标而不是range
  常用的遍历方式有两种,一种是for循环下标遍历,一种是for循环range遍历,这两种遍历在性能上是否有差异呢?让我们来一探究竟。
  针对〔〕int,我们来看看两种遍历有和差别吧funcgetIntSlice()〔〕int{nums:make(〔〕int,1024,1024)fori:0;i1024;i{nums〔i〕i}returnnums}用下标遍历〔〕intfuncBenchmarkIndexIntSlice(btesting。B){nums:getIntSlice()b。ResetTimer()fori:0;ib。N;i{vartmpintfork:0;klen(nums);k{tmpnums〔k〕}tmp}}用range遍历〔〕int元素funcBenchmarkRangeIntSlice(btesting。B){nums:getIntSlice()b。ResetTimer()fori:0;ib。N;i{vartmpintfor,num:rangenums{tmpnum}tmp}}goos:darwingoarch:amd64pkg:demotestcpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkIndexIntSlice123923230270。2nsop0Bop0allocsopBenchmarkRangeIntSlice124518495287。8nsop0Bop0allocsopPASSokdemotest3。303s
  可以看到,在遍历〔〕int时,两种方式并无差别。
  我们再看看遍历〔〕struct{}的情况typeItemstruct{idintval〔1024〕byte}推荐:用下标遍历〔〕struct{}funcBenchmarkIndexStructSlice(btesting。B){varitems〔1024〕Itemfori:0;ib。N;i{vartmpintforj:0;jlen(items);j{tmpitems〔j〕。id}tmp}}推荐:用range的下标遍历〔〕struct{}funcBenchmarkRangeIndexStructSlice(btesting。B){varitems〔1024〕Itemfori:0;ib。N;i{vartmpintfork:rangeitems{tmpitems〔k〕。id}tmp}}不推荐:用range遍历〔〕struct{}的元素funcBenchmarkRangeStructSlice(btesting。B){varitems〔1024〕Itemfori:0;ib。N;i{vartmpintfor,item:rangeitems{tmpitem。id}tmp}}goos:darwingoarch:amd64pkg:demotestcpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkIndexStructSlice124413182266。7nsop0Bop0allocsopBenchmarkRangeIndexStructSlice124545476269。4nsop0Bop0allocsopBenchmarkRangeStructSlice123330035444nsop0Bop0allocsopPASSokdemotest5。282s
  可以看到,用for循环下标的方式性能都差不多,但是用range遍历数组里的元素时,性能则相差很多,前面两种方法是第三种方法的130多倍。主要原因是通过fork,v:range获取到的元素v实际上是原始值的一个拷贝。所以在面对复杂的struct进行遍历的时候,推荐使用下标。但是当遍历对象是复杂结构体的指针(〔〕struct{})时,用下标还是用range迭代元素的性能就差不多了。
  3。4利用unsafe包避开内存copy
  unsafe包提供了任何类型的指针和unsafe。Pointer的相互转换及uintptr类型和unsafe。Pointer可以相互转换,如下图
  unsafe包指针转换关系
  依据上述转换关系,其实除了string和〔〕byte的转换,也可以用于slice、map等的求长度及一些结构体的偏移量获取等,但是这种黑科技在一些情况下会带来一些匪夷所思的诡异问题,官方也不建议用,所以还是慎用,除非你确实很理解各种机制了,这里给出项目中实际用到的常规string和〔〕byte之间的转换,如下:funcStr2bytes(sstring)〔〕byte{x:(〔2〕uintptr)(unsafe。Pointer(s))h:〔3〕uintptr{x〔0〕,x〔1〕,x〔1〕}return(〔〕byte)(unsafe。Pointer(h))}funcBytes2str(b〔〕byte)string{return(string)(unsafe。Pointer(b))}
  我们通过benchmark来验证一下是否性能更优:推荐:用unsafe。Pointer实现string到bytesfuncBenchmarkStr2bytes(btesting。B){s:testStringvarbs〔〕byteforn:0;nb。N;n{bsStr2bytes(s)}bs}不推荐:用类型转换实现string到bytesfuncBenchmarkStr2bytes2(btesting。B){s:testStringvarbs〔〕byteforn:0;nb。N;n{bs〔〕byte(s)}bs}推荐:用unsafe。Pointer实现bytes到stringfuncBenchmarkBytes2str(btesting。B){bs:Str2bytes(testString)varsstringb。ResetTimer()forn:0;nb。N;n{sBytes2str(bs)}s}不推荐:用类型转换实现bytes到stringfuncBenchmarkBytes2str2(btesting。B){bs:Str2bytes(testString)varsstringb。ResetTimer()forn:0;nb。N;n{sstring(bs)}s}goos:darwingoarch:amd64pkg:demotestcpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkStr2bytes1210000000000。2938nsop0Bop0allocsopBenchmarkStr2bytes2123819313928。39nsop16Bop1allocsopBenchmarkBytes2str1210000000000。2552nsop0Bop0allocsopBenchmarkBytes2str2126083614019。60nsop16Bop1allocsopPASSokdemotest3。301s
  可以看到使用unsafe。Pointer比强制类型转换性能是要高不少的,从内存分配上也可以看到完全没有新的内存被分配。
  3。5协程池
  go语言最大的特色就是很容易的创建协程,同时go语言的协程调度策略也让go程序可以最大化的利用cpu资源,减少线程切换。但是无限度的创建goroutine,仍然会带来问题。我们知道,一个go协程占用内存大小在2KB左右,无限度的创建协程除了会占用大量的内存空间,同时协程的切换也有不少开销,一次协程切换大概需要100ns,虽然相较于线程毫秒级的切换要优秀很多,但依然存在开销,而且这些协程最后还是需要GC来回收,过多的创建协程,对GC也是很大的压力。所以我们在使用协程时,可以通过协程池来限制goroutine数量,避免无限制的增长。
  限制协程的方式有很多,比如可以用channel来限制:varwgsync。WaitGroupch:make(chanstruct{},3)fori:0;i10;i{chstruct{}{}wg。Add(1)gofunc(iint){deferwg。Done()log。Println(i)time。Sleep(time。Second)ch}(i)}wg。Wait()
  这里通过限制channel长度为3,可以实现最多只有3个协程被创建的效果。
  当然也可以使用errgoup。使用方法如下:funcTestErrGroupRun(ttesting。T){errgroup:WithTimeout(nil,10time。Second)errgroup。SetMaxProcs(4)forindex:0;index10;index{errgroup。Run(nil,index,test,func(contextgin。Context,iinterface{})(interface{},error){t。Logf(〔s〕input:v,time:s,test,i,time。Now()。Format(2006010215:04:05))time。Sleep(2time。Second)returni,nil})}errgroup。Wait()}
  输出结果如下:RUNTestErrGroupRunerrgrouptest。go:23:〔test〕input:0,time:2022120417:31:29errgrouptest。go:23:〔test〕input:3,time:2022120417:31:29errgrouptest。go:23:〔test〕input:1,time:2022120417:31:29errgrouptest。go:23:〔test〕input:2,time:2022120417:31:29errgrouptest。go:23:〔test〕input:4,time:2022120417:31:31errgrouptest。go:23:〔test〕input:5,time:2022120417:31:31errgrouptest。go:23:〔test〕input:6,time:2022120417:31:31errgrouptest。go:23:〔test〕input:7,time:2022120417:31:31errgrouptest。go:23:〔test〕input:8,time:2022120417:31:33errgrouptest。go:23:〔test〕input:9,time:2022120417:31:33PASS:TestErrGroupRun(6。00s)PASS
  errgroup可以通过SetMaxProcs设定协程池的大小,从上面的结果可以看到,最多就4个协程在运行。
  3。6sync。Pool对象复用
  我们在代码中经常会用到json进行序列化和反序列化,举一个投放活动的例子,一个投放活动会有许多字段会转换为字节数组。typeActTaskstruct{Idint64ddb:id主键idStatuscommon。Statusddb:status状态0初始1生效2失效3过期BizProdcommon。BizProdddb:bizprod业务类型Namestringddb:name活动名Adcodestringddb:adcode城市RealTimeRuleByte〔〕byteddb:realtimerule实时规则json。。。}typeRealTimeRuleStructstruct{Filter〔〕struct{PropertyIdint64json:propertyidPropertyCodestringjson:propertycodeOperatorstringjson:operatorValue〔〕stringjson:value}json:filterExtData〔1024〕bytejson:extdata}func(atActTask)RealTimeRule()form。RealTimeRule{iferr:json。Unmarshal(at。RealTimeRuleByte,at。RealTimeRuleStruct);err!nil{returnnil}returnat。RealTimeRuleStruct}
  以这里的实时投放规则为例,我们会将过滤规则反序列化为字节数组。每次json。Unmarshal都会申请一个临时的结构体对象,而这些对象都是分配在堆上的,会给GC造成很大压力,严重影响程序的性能。
  对于需要频繁创建并回收的对象,我们可以使用对象池来提升性能。sync。Pool可以将暂时不用的对象缓存起来,待下次需要的时候直接使用,不用再次经过内存分配,复用对象的内存,减轻GC的压力,提升系统的性能。
  sync。Pool的使用方法很简单,只需要实现New函数即可。对象池中没有对象时,将会调用New函数创建。varrealTimeRulePoolsync。Pool{New:func()interface{}{returnnew(RealTimeRuleStruct)},}
  然后调用Pool的Get()和Put()方法来获取和放回池子中。rule:realTimeRulePool。Get()。(RealTimeRuleStruct)json。Unmarshal(buf,rule)realTimeRulePool。Put(rule)Get()用于从对象池中获取对象,因为返回值是interface{},因此需要类型转换。Put()则是在对象使用完毕后,放回到对象池。
  接下来我们进行性能测试,看看性能如何:varrealTimeRule〔〕byte({filter:〔{propertyid:2,propertycode:searchpoiidindustry,operator:in,value:〔yimei〕},{propertyid:4,propertycode:requestpageid,operator:in,value:〔all〕}〕,whitelist:〔{propertyid:1,propertycode:whitelistforadiu,operator:in,value:〔j838ef77bf227chcl89888f3fb0946,lb89bea9af558589i55559764bc83e〕}〕,ipcusertag:〔{propertyid:1,propertycode:ipccrowdtag,operator:in,value:〔test20227041152mixipctag〕}〕,relationid:0,iscopy:true})推荐:复用一个对象,不用每次都生成新的funcBenchmarkUnmarshalWithPool(btesting。B){forn:0;nb。N;n{task:realTimeRulePool。Get()。(RealTimeRuleStruct)json。Unmarshal(realTimeRule,task)realTimeRulePool。Put(task)}}不推荐:每次都会生成一个新的临时对象funcBenchmarkUnmarshal(btesting。B){forn:0;nb。N;n{task:RealTimeRuleStruct{}json。Unmarshal(realTimeRule,task)}}goos:darwingoarch:amd64pkg:demotestcpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkUnmarshalWithPool123627546319。4nsop312Bop7allocsopBenchmarkUnmarshal122342208490。8nsop1464Bop8allocsopPASSokdemotest3。525s
  可以看到,两种方法在时间消耗上差不太多,但是在内存分配上差距明显,使用sync。Pool后内存占用仅为不使用的15。
  3。7避免系统调用
  系统调用是一个很耗时的操作,在各种语言中都是,go也不例外,在go的GPM模型中,异步系统调用G会和MP分离,同步系统调用GM会和P分离,不管何种形式除了状态切换及内核态中执行操作耗时外,调度器本身的调度也耗时。所以在可以避免系统调用的地方尽量去避免。推荐:不使用系统调用funcBenchmarkNoSytemcall(btesting。B){b。RunParallel(func(pbtesting。PB){forpb。Next(){ifconfigs。PUBLICKEY!nil{}}})}不推荐:使用系统调用funcBenchmarkSytemcall(btesting。B){b。RunParallel(func(pbtesting。PB){forpb。Next(){ifos。Getenv(PUBLICKEY)!{}}})}goos:darwingoarch:amd64pkg:demotestcpu:Intel(R)Core(TM)i79750HCPU2。60GHzBenchmarkNoSytemcall1210000000000。1495nsop0Bop0allocsopBenchmarkSytemcall123722498831。10nsop0Bop0allocsopPASSokdemotest1。877s
  四、性能调优实战篇
  案例1:go协程创建数据库连接不释放导致内存暴涨
  应用背景
  感谢路现提供的案例。
  遇到的问题及表象特征
  线上机器偶尔出现内存使用率超过百分之九十报警。
  分析思路及排查方向
  在报警触发时,通过直接拉取线上应用的profile文件,查看内存分配情况,我们看到内存分配主要产生在本地缓存的组件上。
  但是分析代码并没有发现存在内存泄露的情况,看着像是资源一直没有被释放,进一步分析goroutine的profile文件。
  发现存在大量的goroutine未释放,表现在本地缓存击穿后回源数据库,对数据库的查询访问一直不释放。
  调优手段与效果
  最终通过排查,发现使用的数据库组件存在bug,在极端情况下会出现死锁的情况,导致数据库访问请求无法返回也无法释放。最终bug修复后升级数据库组件版本解决了问题。
  案例2:优惠索引内存分配大,gc耗时高
  应用背景
  感谢梅东提供的案例。
  遇到的问题及表象特征
  接口tp99高,偶尔会有一些特别耗时的请求,导致用户的优惠信息展示不出来。
  分析思路及排查方向
  通过直接在平台上抓包观察,我们发现使用的分配索引这个方法占用的堆内存特别高,通过top可以看到是排在第一位的。
  我们分析代码,可以看到,获取城市索引的地方,每次都是重新申请了内存的,通过改动为返回指针,就不需要每次都单独申请内存了,核心代码改动:
  调优手段与效果
  修改后,上线观察,可以看到使用中的内存以及gc耗时都有了明显降低
  案例3:流量上涨导致cpu异常飙升
  应用背景
  感谢君度提供的案例。
  遇到的问题及表象特征
  能量站v2接口和taskhomepage接口流量较大时,会造成ab实验策略匹配时cpu飙升
  分析思路及排查方向
  调优手段与效果
  主要优化点如下:
  1、优化toEntity方法,简化为单独的ID()方法
  2、优化数组、map初始化结构
  3、优化adCode转换为string过程
  4、关闭过多的matchlog打印
  优化后profile:
  优化上线前后CPU的对比
  案例4:内存对象未释放导致内存泄漏
  应用背景
  感谢淳深提供的案例,提供案例的服务,日常流量峰值在百万qps左右,是高德内部十分重要的服务。此前该服务是由java实现的,后来用go语言进行重构,在重构完成切全量后,有许多性能优化的优秀案例,这里选取内存泄漏相关的一个案例分享给大家,希望对大家在自己服务进行内存泄漏问题排查时能提供参考和帮助。
  遇到的问题及表象特征
  go语言版本全量切流后,每天会对服务各项指标进行详细review,发现每日内存上涨约0。4,如下图
  在go版本服务切全量前,从第一张图可以看到整个内存使用是平稳的,无上涨趋势,但是切go版本后,从第二张图可以看到,整个内存曲线呈上升趋势,遂认定内存泄漏,开始排查内存泄漏的罪魁祸首。
  分析思路及排查方向
  我们先到线上机器抓取当前时间的heap文件,间隔一天后再次抓取heap文件,通过pprofdiff对比,我们发现time。NewTicker的内存占用增长了几十MB(由于未保留当时的heap文件,此处没有截图),通过调用栈信息,我们找到了问题的源头,来自中间件vipserverclient的SrvHost方法,通过深扒vipserverclient代码,我们发现,每个vipserver域名都会有一个对应的协程,这个协程每隔三秒钟就会新建一个ticker对象,且用过的ticker对象没有stop,也就不会释放相应的内存资源。
  而这个time。NewTicker会创建一个timer对象,这个对象会占用72字节内存。
  在服务运行一天的情况下,进过计算,该对象累计会在内存中占用约35。6MB,和上述内存每日增长0。4正好能对上,我们就能断定这个内存泄漏来自这里。
  调优手段与效果
  知道是timer对象重复创建的问题后,只需要修改这部分的代码就好了,最新的vipserverclient修改了此处的逻辑,如下
  修改完后,运行一段时间,内存运行状态平稳,已无内存泄漏问题。
  结语
  目前go语言不仅在阿里集团内部,在整个互联网行业内也越来越流行,希望本文能为正在使用go语言的同学在性能优化方面带来一些参考价值。在阿里集团内部,高德也是最早规模化使用go语言的团队之一,目前高德线上运行的go服务已经达到近百个,整体qps已突破百万量级。在使用go语言的同时,高德也为集团内go语言生态建设做出了许多贡献,包括开发支持阿里集团常见的中间件(比如配置中心Diamond、分布式RPC服务框架HSF、服务发现Vipserver、消息队列MetaQ、流量控制Sentinel、日志追踪Eagleeye等)go语言版本,并被阿里中间件团队官方收录。但是go语言生态建设仍然有很长的道路要走,希望能有更多对go感兴趣的同学能够加入我们,一起参与阿里的go生态建设,乃至为互联网业界的go生态发展添砖加瓦。
  作者:阳迪、联想、君清
  来源:微信公众号:阿里开发者
  出处:https:mp。weixin。qq。comsUHaCLhiIyLYVrbanEUONA

人老后各种病都来了?说实话3种营养跟得上,有助于增强体质我的一位患者老张,今年已经65岁了。因为平时喜欢整点酒,来点猪头肉,血压一直控制的不太稳定。自己看到开的药,瓶瓶罐罐的,也非常头痛,但就是戒不掉自己的这点儿爱好。这天老张来复查,我老年营养不良与体重低下需要注意什么?营养不良是指因摄入不足吸收不良或过度消耗而发生的一种或几种营养素缺乏或不足而导致的一种慢性疾病。老年营养不良主要表现为体重低下身体虚弱抵抗力和应激能力下降,常感冒,容易生病。体重低多走路能预防骨质疏松吗?提醒2物多吃,腿脚有劲精神好骨质疏松是一种比较常见的骨骼问题,大部分人都不陌生,也都知道它经常出现在老年人身上。这也就导致了,很多人在上了年纪之后,会非常担心自己会患上骨质疏松,会采取一些方法预防,但容易进入中老年人一天要睡多久?几点睡好?睡够最佳时间就行,不要强求关电视!1050了,再不睡觉就熬夜了。哎呦,这枪战片还有最后一集啊,你睡你的,我看完了睡。你看完那快12点,熬这么晚很伤身啊。再说你不关电视有声音,我怎么睡得着?王大爷无奈,只好把每天喝二两酒,不喝多,一段时间后,身体会发生什么?导语每天喝二两酒,不喝多,一段时间后,身体会发生什么?从古至今,饮酒文化早已渗入在我们的日常生活中,大家聚会喝,自己喝。但俗话说得好小酌怡情,大饮伤身。喝酒归喝酒,凡事都是要注意适价格亲民的3款4nm旗舰手机,不发烫续航长,买对了能多用好长时间目前,手机芯片处理器技术已达到4nm,由于担心功耗和发热问题,很多用户并不知道如何选择。今天,我推荐三款4nm旗舰手机,它们不发热而且续航很棒。第1款小米12SUltra小米12S骁龙870持续真香,三星E4屏256GBOIS防抖67W快充,跌至1799元在过去的一年里,由于骁龙870处理器的出现,在两千档价位上诞生了很多真香手机,包括红K40,真我GTNeo2,iQOONeo5等等,其中最受欢迎的是红米K40,红米K40也是去年销小米12SUltra对比三星S22Ultra,到底谁更值得入手?分析后秒懂如果要入手高端旗舰机,个人觉得还是更建议选三星S22Ultra,因为三星采用类原生Android系统,有多流畅就不用多说了,而且拍照功能强大。从拍照到工业设计,再到IP68防水等级播求杀入世界最残酷擂台,首秀时间确定BKFC是美国知名的裸拳赛事,从2018年创办至今,BKFC已经成为了世界上最大的裸拳比赛,近两年BKFC也招纳了不少MMA拳手投身其中,其影响力可见一斑。近日,一位武粉们十分熟悉安卓旗舰,为什么就是抓着曲面屏不放?昨天晚上看见一个帖子,帖子的内容是讨论曲面屏幕,然后下面有一名果粉的吐槽,他说如果iPhone用曲面屏,那么他将不是果粉。我有些不解,不过我看这条评论上有很多赞同,同样作为果粉的我战神ZX9DA7DP旗舰级游戏笔记本,定义真正的性能强者对于经常玩电脑游戏的人来说,一款性能强悍的电脑是必不可少的。由于工作或者学习的原因,我们很难经常去网吧玩游戏,另外台式电脑无法随身携带也是一个难题,因此现在越来越多的人选择购买游戏梅婷的黑眼圈,马伊琍的光头,殷桃的痣,打了多少滤镜女明星的脸娱乐圈是个看脸的地方,很多女艺人为了逆天改命,在巴掌大的脸部做足了功夫美颜滤镜,浓妆艳抹,甚至整容在脸上动刀子。不过,并非所有女艺人都把心思放在脸上,在颜值即正义的娱乐圈里,敢以素明星到底多娇贵?30名保安护身,绊倒要坐轮椅,一个比一个离谱大咖小咖综艺咖。这些名词大家都能理解。娇贵咖也很好懂。娇生惯养架子十足是这些明星的特征。这些娇贵咖,年纪往往不大,演技很差,通过选秀男团女团等途径,靠着一时的流量,跻身影视圈。但拍iPhone14ProMax机模曝光摄像头厚得离谱苹果的iPhone14系列预计将于9月正式发布。有分析师称iPhone14系列的销量会超过iPhone13系列,零售商黄牛都十分看好。据外媒91mobiles消息,有两张新款iPh西部剧变!波普2年3000万签掘金,詹姆斯因他丢冠,库里难超老詹谁人有资格叫曼巴风骨,唯我考德威尔波普。转眼之间,波普也已经29岁了,他曾在活塞对阵湖人的比赛中,为了效仿科比,自抛自扣,惹得科比跺脚大笑。或许是科比的因素,在活塞打出身价后,活塞离谱音乐节,从涨价开始作者白露声明题图来源于网络。惊蛰研究所原创文章,如需转载请留言申请开白。随着炎炎夏日的到来,全国各地音乐节纷纷进入紧锣密鼓地筹备和宣传阶段。其中草莓音乐节率先公布了万宁武汉贵阳和成实况足球新增的全面大妖,六边形小战士数据解析,精选卡已出贝林厄姆多特蒙德俱乐部这个球员由于俱乐部没版权也是之前有又删了,如今入选英格兰国家队,22版本已经可以体验到这位强悍妖人了!位置中前卫身体186的身高75kg他的身体模型在对抗方面古驰度假胶囊系列以色彩玩转老花,蕾哈娜美妆品牌推出环保包装口红是日美好事物记者袁天云编辑楼婍沁GUCCI度假胶囊系列作为前往海边的季节性仪式颂歌,GUCCI度假胶囊系列以明亮色彩组合为特点,以双G字母为中心的图案背景重新构想了邦迪戛纳迪拜夏威夷普吉岛等世守望先锋2直接淘汰一代悲剧变喜剧还是闹剧?10月要推出变成免费游戏的Overwatch2(守望先锋2),似乎是即将要替换掉旧版的一代守望先锋,除了消灭掉原先的一代之外,就连收费的模式也变得跟原来很不一样。暴雪在2016年推鸿蒙3。0正式发布,系统不再兼容GMS,谷歌摊上事了就在今天,鸿蒙3。0正式发布的消息,传得那是沸沸扬扬,余承东果然没骗人,鸿蒙3。0将于7月月底正式发布,相信这个时候谷歌已经急了,不仅是急了而且还摊上大事了,因为鸿蒙3。0系统依然这四款手机不愧是高端旗舰产品,入手还能再战3年导读这四款手机宝刀未老,性能和体验依旧能打,不愧是高端旗舰产品,下半年入手还能轻松再战3年!还是想告诉大家一个不争的事实,就是随着大环境的影响,全球手机业务正在急剧萎缩。这也怪前几红魔7S系列成新卷王,红魔7价比百元机创历史新低价对于游戏玩家来说,一部得心应手的游戏手机可以大大提升他们的游戏体验。近日,红魔带来了新一代红魔7S系列,搭载了新一代骁龙8移动平台全新冰幻散热系统和MagicGPU自研帧稳引擎,形
华为荣耀还是一家?荣耀X40外观公布,与华为Mate50相仿对国产手机品牌比较熟悉的朋友都知道,其实华为和荣耀这两个品牌一开始就是一家,简单来说荣耀是华为为了冲击中低端市场建立的子品牌,成立之初把小米和红米当作是对标对象,没想到成立几年之后最近销量比较好的荣耀70手机可以入手吗?外观特色荣耀70在外观上采用了轻薄对称双曲面设计,屏幕与机身后盖自然对称,正面配备超级双曲屏。边框圆润日常生活使用或玩游戏手握感舒适。颜色参数荣耀70提供流光水晶冰岛幻境亮黑色墨玉截至目前,盘点荣耀搭载潜望式长焦的手机1荣耀30搭载麒麟9855GSoC芯片,采用台积电7nm制程工艺,AI性能和智慧体验再升级,5G性能更卓越正面是一块6。53英寸的三星AMOLED直屏,屏幕素质更高,画质更加清晰4这6位女星才是红毯杀手,衣品太差劲了红毯比美,自然要战个输赢。一场红毯大战下来,话题度最高的除了常胜将军热巴倪妮等人外,还有那些靠造型土出圈的女星。论视觉冲击力,其实她们才是真正的红毯杀手。一姚晨前段时间的微博之夜红李彦宏谈自动驾驶L2之后,率先商用会是L4而不是L3封面新闻记者孟梅付文超9月1日,2022世界人工智能大会(WAIC)在上海召开,今年大会主题为智联世界元生无界。百度创始人董事长兼首席执行官李彦宏受邀出席并发表题为人工智能与实体经地球是圆的还是平的,你真的确定吗?地球是圆的,这个很多小孩都张嘴就来的问题,为何到今日都还有很多争论呢?首先,我们先简单说说支持地圆说的依据教科书告诉我们,在探索大地形状的历史上,古希腊人走在了所有文明之前,早在公云海草甸山峦,人均40赏超仙世外桃源,不出京来散心太值了草甸与云海不期而遇明月出天山,苍茫云海间。秋日来临,孩子们的暑假也即将落下帷幕,你是否想去一个云海翻涌的地方你是否想去一个青草连绵的地方8月的最后,游小编为你推荐一个远离尘世喧嚣的张馨予4000万花园火了,鲜花簇拥瓜果自足,堪比世外桃源说起张馨予,你脑海中的第一印象是什么?是又美又飒乘风破浪的姐姐,还是凭借颜值和性格而圈粉的文艺女青年。结婚之后的张馨予很低调,把更多的时间用在了生活上。她不仅拥有甜蜜的爱情,而且还古力娜扎蓝衣高马尾青春有活力娜扎8。13出席节目的造型,蓝色修身针织上衣,白色短裙,造型青春活泼。自由地伸展身体的美人,双腿修长,身段美好。抱着枕头站着的娜扎,扎着高高的马尾,把她原本就圆润好看的颅顶修饰的更英超曼联10蓝狐取3连胜!桑乔一击制胜,C罗倒钩偏出北京时间9月2日凌晨300,202223赛季英超联赛第5轮的一场焦点战,莱斯特城主场迎战曼联。前4轮战罢,曼联2胜2负,积6分排名第12位莱斯特城只取得1平3负,积1分排名垫底。两不可一世的潘长江,为何走到了今天这一步?文大牌娱姐曾几何时,潘长江是大家心目中公认的喜剧明星,身材矮小其貌不扬的他,总是能给大家带来欢声笑语。无论是在春晚舞台上的小品,还是在影视剧作品中的表现,都是可圈可点,让人称赞有加
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网