欧美性猛交xxxx免费看_牛牛在线视频国产免费_天堂草原电视剧在线观看免费_国产粉嫩高清在线观看_国产欧美日本亚洲精品一5区

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

M7S架構(gòu)設計內(nèi)幕

OSC開源社區(qū) ? 來源:OSC開源社區(qū) ? 2023-07-07 16:01 ? 次閱讀

Go語言本身具備出色的性能,然而在流媒體服務器這種CPU密集+IO密集的雙重壓力下,GC帶來的性能損失是最主要的矛盾。而減少GC的操作最直接的辦法就是減少內(nèi)存申請,多多復用內(nèi)存。本文將圍繞內(nèi)存復用這個主題,把M7S中相關技術(shù)原理講解一遍,也是M7S性能優(yōu)化的歷程。

讀寫內(nèi)存共享

在早期我在研究過許多流媒體服務器的數(shù)據(jù)轉(zhuǎn)發(fā)模式,基本都是在發(fā)送給訂閱者時將內(nèi)存復制一份的方式實現(xiàn)讀寫分離,雖然沒有并發(fā)問題,但是內(nèi)存頻繁的申請和復制比較消耗資源。

在M7S v1版本中,也沿用了傳統(tǒng)的方式。然而Go語言由于采用GC的方式管理內(nèi)存,導致頻繁申請內(nèi)存會加大GC的壓力。

在網(wǎng)友的啟發(fā)下,從v2版本開始,采用了基于RingBuffer的內(nèi)存共享讀寫方式。大大減少了內(nèi)存復制。

Monibuca中每一個流(Stream)對象包含多個Track(分為音視頻Track和DataTrack)每個Track包含一個RingBuffer。發(fā)布者將數(shù)據(jù)填入這個RingBuffer中,訂閱者則從RingBuffer中讀取數(shù)據(jù)再封裝到協(xié)議中發(fā)送出去,形成轉(zhuǎn)發(fā)的核心邏輯。

下面的視頻是當時開發(fā)的一個UI,實時獲取RingBuffer信息SVG繪制而成。其中發(fā)布者正在不斷寫入數(shù)據(jù),訂閱者緊隨其后不斷讀取數(shù)據(jù)。

由于發(fā)布者以及訂閱者不在同一個協(xié)程中,訪問同一個塊內(nèi)存很有可能引起并發(fā)讀寫的問題。如何解決并發(fā)讀寫呢?M7S 經(jīng)過不斷的迭代在這塊上面實踐了各種方法。既要考慮到性能,還要考慮到代碼的可讀性可維護性。

sync.RWMutex

這是最容易想到的,在M7Sv2中就采用了讀寫鎖。操作步驟如下:

  • 先鎖住Ring中的下一個待寫入單元,再將本次寫完的單元釋放寫鎖。
  • 在本讀寫單元中等待讀取的訂閱者在寫鎖釋放的同時獲取到讀鎖,開始讀取數(shù)據(jù)
有點類似人走路的方式,前腳著地后,后腳再離地。可以保證訂閱者無法跑到發(fā)布者前面。

優(yōu)點是可讀性很強,一眼就能看懂這個原理。缺點是,鎖的開銷比較大,性能損失很明顯。還有一個缺點,就是當訂閱者阻塞,會導致發(fā)布者追上訂閱者,寫鎖無法獲取從而阻塞整個流。(后來Go出了TryLock)

WaitGroup

v3 中采用了這個,但是WaitGroupWait操作是一個無限阻塞的操作,必須用Done操作才能結(jié)束等待,此時就會有一個問題,engine和發(fā)布者有可能會同時去調(diào)用Done完成釋放(具體原因另開章節(jié)介紹)。因此Done就會多調(diào)用一次導致panic。后來通過復雜的原子操作解決了(但是大大降低了代碼的可讀性)。

time.Sleep

v4 中采用了偽自旋鎖,所謂的偽自旋鎖,就是模仿自旋鎖的機制,只是用time.Sleep代替了,runtime.Gosched,減少了自旋次數(shù),從而提高性能。

forr.Frame=&r.Value;r.ctx.Err()==nil&&!r.Frame.CanRead;r.Frame.wait(){
}

CanRead不需要原子操作,有人擔心可能會有并發(fā)讀寫問題,其原理同前面說的人走路是一樣的,即便出現(xiàn)了并發(fā)讀寫,也不影響邏輯正確運行。最多就是多等待一個周期,稍微增加一點點延遲。

sync.Cond

v1版本中由于使用的是簡單的內(nèi)存復制,于是有人給了這個方案,但是我卻一直繞了一大圈,最后回到這個方案上了,也算是自作聰明。sync.Cond之所以一開始沒有選擇,是因為里面包含了一個鎖(標準庫內(nèi)部強制調(diào)用了鎖)

func(c*Cond)Wait(){
c.checker.check()
t:=runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify,t)
c.L.Lock()
}

所以就認為性能不高,直到繞了一大圈之后,才找到一個避免鎖的方案。當然這些彎路可能必須要走,因為直到自己寫了偽自旋鎖,才增加了一個是否可讀的屬性,也就是說有了這個屬性后,我們其實只需要一個喚醒的功能即可,于是想到了給sync.Cond提供一個空的鎖對象的方式避免了鎖:

typeemptyLockerstruct{}

func(emptyLocker)Lock(){}
func(emptyLocker)Unlock(){}

varEmptyLockeremptyLocker

sync.Cond在喚醒協(xié)程的時候使用的是Broadcast方法,這個方法可以多次調(diào)用而無副作用(不像WaitGroupDone方法)。也可以減少偽自旋鎖帶來的輕微延遲。

實際測試中使用Cond比偽自旋鎖大概可以節(jié)省10%左右的CPU消耗

協(xié)議轉(zhuǎn)換中的內(nèi)存復用

協(xié)議轉(zhuǎn)換可以用下面的邏輯來實現(xiàn):

b3e6dc40-1bf1-11ee-962d-dac502259ad0.png

實際情況比這個要復雜一些。所以這里面第一步需要引入go標準庫中的net.Buffers來表示“連續(xù)的內(nèi)存”(實際并不一定連續(xù))。當收到一個協(xié)議傳來的數(shù)據(jù)時盡量保留,而不去復制它。

同一個協(xié)議轉(zhuǎn)發(fā)

對于相同的協(xié)議,能復用的內(nèi)存更多一些,舉個例子:

RTMP轉(zhuǎn)發(fā)到RTMP

RTMP中傳輸視頻幀的格式為AVCC格式,這也是能復用的部分,在實際傳輸過程中這部分內(nèi)存并非一個連續(xù)內(nèi)存。RTMPchunk機制,會把AVCC切割一塊塊傳輸,并加上chunk header。

chunkheader|avccpart1|chunkheader|avccpart2······

這個分割的大小默認是128字節(jié),通常RTMP協(xié)議會經(jīng)過協(xié)商修改這個大小,因此傳入和傳出的分塊大小不一定相同。那如何復用AVCC的數(shù)據(jù)呢?此時我們需要用到net.Buffers來表示一幀AVCC數(shù)據(jù)。

|avccpart1|avccpart2······

當我們需要另一種分塊大小的數(shù)據(jù)時,可以對原始數(shù)據(jù)再分割。比如說原始數(shù)據(jù)是256字節(jié)分塊的:

|256Bytes|256Bytes······

而新的分塊要求是128Bytes的

|128Bytes|128Bytes|128Bytes|128Bytes······

我們并沒有申請新的內(nèi)存,只是多了一些切片。那有人就可能會問了,如果不是正好倍數(shù)關系呢?其實無非就是多切幾塊。比如新的分塊要求是200Bytes:

|200Bytes|56Bytes|144Bytes|112Byts|88Bytes······

用下面的圖更加直觀:

b4025fec-1bf1-11ee-962d-dac502259ad0.png

這樣發(fā)送的時候,并不是一個連續(xù)內(nèi)存,那如何發(fā)送呢?這里就用到了writev(windows對應的是WSASend)技術(shù)。在Go語言中通過net.Buffers類型寫入數(shù)據(jù)會自動判斷使用的技術(shù)。

RTSP轉(zhuǎn)發(fā)到RTSP

RTSP協(xié)議傳輸?shù)拿襟w數(shù)據(jù)是RTP包,RTP包在理想狀態(tài)下,可以完全復用,就是直接把RTP包緩存起來,等需要發(fā)送的時候直接把這個RTP數(shù)據(jù)原封不動的發(fā)出去。在m7s中,由于需要有跳幀追幀的邏輯,所以需要修改時間戳,就無法原封不動的發(fā)送RTP包,但是也可以復用其中的Payload部分。

HLS轉(zhuǎn)發(fā)到HLS

在純轉(zhuǎn)發(fā)模式下,可以直接將TS切片緩存,完全復用。如果需要將HLS轉(zhuǎn)換成其他協(xié)議,則需要將TS格式數(shù)據(jù)進行解包處理。

FLV轉(zhuǎn)發(fā)到FLV

FLV格式由于數(shù)據(jù)格式也是avcc格式,因此處理邏輯就按照avcc格式統(tǒng)一處理了,FLVtag頭無法復用,涉及到時間戳需要重新生成。

不同協(xié)議轉(zhuǎn)發(fā)

不同協(xié)議之間轉(zhuǎn)發(fā)由于兩兩排列組合很多,因此需要抽象出大類來處理。

協(xié)議分類

RTMP、FLV、MP4

該類協(xié)議視頻是AVCC格式,音頻是裸格式(RTMP包含一到兩個字節(jié)的頭)

RTSP、WebRTC

該類的視頻是RTP(Header+裸NALU)音頻是RTP(Header + AuHeaderLen + AuHeaderxN + AuxN )

HLS、GB28181

這類使用的MPEG2-TSMPEG2-PS作為傳輸協(xié)議視頻采用Header+AnnexB音頻采用Header+ADTS+AAC

內(nèi)存復用

總體而言,視頻格式都是前綴+NALU這種方式,AnnexB的前綴是00 00 00 01,而Avcc的前綴是 CTS、 NALU長度等,因此將NALU緩存起來就可以復用NALU數(shù)據(jù)。在實際實現(xiàn)中,為了方便同類型的協(xié)議轉(zhuǎn)換,會同時緩存Avcc格式、RTP格式、以及裸格式,而這三種格式的NALU部分都共用一組內(nèi)存(內(nèi)存不連續(xù))

減少發(fā)布者的GC

GC的產(chǎn)生

對于一個發(fā)布者,即需要不斷從網(wǎng)絡或是本地文件中讀取數(shù)據(jù)的對象,在不做任何優(yōu)化的情況下,都會不停的申請內(nèi)存。例如使用io.ReadAll這種操作,內(nèi)部會頻繁的申請內(nèi)存。頻繁申請內(nèi)存的結(jié)果就是GC壓力很大,尤其是高并發(fā)的時候,GC帶來的消耗可以達到50%CPU消耗。

b419072e-1bf1-11ee-962d-dac502259ad0.jpg

sync.Pool

當然我最先想到的一定是使用內(nèi)存池,也就是sync.Pool來管理需要使用的內(nèi)存,但是sync.Pool有個缺陷,就是為了協(xié)程安全內(nèi)部有鎖。盡管使用了多級緩存等一些列優(yōu)化手段,最終使用的時候也會消耗一定的性能(經(jīng)過實測性能開銷很大)。而且sync.Pool比較通用,并不是針對特定的對象使用,我們這里是針對[]byte類型進行復用。

自定義Pool

如果Pool不含有鎖,性能會大幅提升,那如何解決協(xié)程安全呢?答案是協(xié)程不安全,即我們只在一個協(xié)程里面去操作Pool的取出和放回。通常情況下一個發(fā)布者的寫入是在同一個協(xié)程中的,比如rtmp協(xié)議。少數(shù)協(xié)議如rtsp可能會有多個協(xié)程寫入數(shù)據(jù),因此最后我們是每一個Track一個Pool,保持一個Track一個協(xié)程寫入。

下圖表示的是自定義Pool的結(jié)構(gòu):

b43108ec-1bf1-11ee-962d-dac502259ad0.png

每個Pool是一個數(shù)組,數(shù)組的每一個元素是一個鏈表,鏈表的每一個元素是一個包含[]byte的類型,大小是2的數(shù)組下標次冪。

0號元素有特殊用途,由于我們需要記錄每一塊內(nèi)存所屬的鏈表來回收,因此需要有一個外殼,而外殼(ListItem)也是需要回收的。而0號元素是存放的只有外殼需要回收而無需回收Value(需要GC的對象)的鏈表。

typeList[Tany]struct{
ListItem[T]
Lengthint
}
typeListItem[Tany]struct{
ValueT
Next,Pre*ListItem[T]`json:"-"yaml:"-"`
Pool*List[T]`json:"-"yaml:"-"`//回收池
list*List[T]
}
typeBytesPool[]List[Buffer]

回收內(nèi)存

RingBuffer中的訪問單元被覆蓋時,就可以將其中所有的內(nèi)存對象進行放回Pool。由此實現(xiàn)了從內(nèi)存使用的閉環(huán),消除了GC。下圖中紅色箭頭代表內(nèi)存復用機制,可以有效避免申請內(nèi)存操作。

b4597d2c-1bf1-11ee-962d-dac502259ad0.png

后記

經(jīng)過上面三板斧的優(yōu)化后,整體性能提升了50%以上。下圖測試10000rtmp推流的對比:m7s內(nèi)存占用較高一些,原因就是采用了內(nèi)存池來減少GC造成的。使用內(nèi)存來換CPU,在這種場景下還是值得的。

b473eae0-1bf1-11ee-962d-dac502259ad0.jpg

b489fcf4-1bf1-11ee-962d-dac502259ad0.jpg

b4a265fa-1bf1-11ee-962d-dac502259ad0.jpg

b4c188cc-1bf1-11ee-962d-dac502259ad0.jpg

流媒體服務器 10000路推流CPU消耗
monibuca 90%~100%
zlm 90%~100%
srs 80%~90%
lal 160%~200%

由于livego的推流需要先調(diào)用一次HTTP獲取密鑰,所以無法使用壓測工具批量推流,本次對比無法參與。
所有流媒體服務器配置均關閉了協(xié)議轉(zhuǎn)換的開關,并以Release方式編譯。服務器也去除了所有限制,并以完全相同的操作方式進行壓測。



聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 服務器
    +關注

    關注

    12

    文章

    9321

    瀏覽量

    86104
  • 架構(gòu)設計

    關注

    0

    文章

    32

    瀏覽量

    6977
  • go語言
    +關注

    關注

    1

    文章

    158

    瀏覽量

    9094

原文標題:方法)。也可以減少偽自旋鎖帶來的輕微延遲。

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關推薦

    kintex產(chǎn)品架構(gòu)設計文檔(成為架構(gòu)師也是電子人不錯的選...

    kintex產(chǎn)品架構(gòu)設計文檔(成為架構(gòu)師也是電子人不錯的選擇) ROCE(儒仕),用心為每一位電子人!Xilinx7系列普及講座,架構(gòu)師設計方案模板,交流學習 內(nèi)容請下載附件pdf,更
    發(fā)表于 04-30 16:41

    軟件架構(gòu)設計教程

    軟件架構(gòu)設計教程
    發(fā)表于 09-26 15:27

    【汽車電氣架構(gòu)設計軟件】

    因工作需要,求整車電氣架構(gòu)設計軟件——PREEvision(盜版),價格可議,WetChat/***,非誠勿擾
    發(fā)表于 04-18 14:20

    基于ARM架構(gòu)設計的M1芯片

    提升巨大,也讓配備 M1 芯片的 Mac 跨入完全不同的層次。由于M1芯片是基于ARM架構(gòu)設計,所以無法安裝x86版本的Windows。著名的虛擬機軟件parallels desktop推出了基于
    發(fā)表于 07-23 09:02

    STM32軟件架構(gòu)設計的意義

    STM32軟件架構(gòu)1、架構(gòu)設計的意義(1)應用代碼邏輯清晰,且避免代碼冗余;(2)代碼通用性,方便軟件高速、有效的移植;(3)各功能獨立,低耦合高內(nèi)聚;2、總體架構(gòu)圖3、結(jié)構(gòu)層說明4、遵循規(guī)則5、優(yōu)劣評估6、STM32實例說明
    發(fā)表于 08-04 07:23

    為何要進行嵌入式軟件架構(gòu)設計?如何設計?

    為何要進行嵌入式軟件架構(gòu)設計?如何進行嵌入式軟件架構(gòu)設計?
    發(fā)表于 11-01 06:31

    對嵌入式系統(tǒng)中的架構(gòu)設計的理解

    【閱讀這篇文章,你能了解到什么】1. 從事嵌入式開發(fā)12年的我,對架構(gòu)設計的理解;2. 對嵌入式系統(tǒng)中的架構(gòu)設計要刻意訓練;3. 嵌入式系統(tǒng)開發(fā)過程中的一些小技巧;4. 一個用于智能家居項目
    發(fā)表于 11-08 08:23

    基于ARMCortex_M3核的SoC架構(gòu)設計及性能分析

    基于ARMCortex_M3核的SoC架構(gòu)設計及性能分析
    發(fā)表于 09-29 09:26 ?18次下載
    基于ARMCortex_<b class='flag-5'>M</b>3核的SoC<b class='flag-5'>架構(gòu)設</b>計及性能分析

    富可視發(fā)布全面屏新機“M7s”:前后攝像頭同時拍攝

    富可視(InFocus)近日在臺灣發(fā)布了一款全面屏新機“M7s”,配置不算很高但也頗有特點,性價比相當出色。 該機采用了三段式機身設計,后置指紋,重量148克,厚度8.8毫米,提供曜石黑、鉑光金兩種
    發(fā)表于 01-29 14:27 ?528次閱讀

    ARMv7-M嵌入式架構(gòu)的特點是什么

    ARM Cortex-M處理器系采用ARMv7-M架構(gòu)設定,Cortex-M0和Cortex-M0+則采用類似的ARMv6-
    發(fā)表于 10-22 14:32 ?4925次閱讀

    系統(tǒng)架構(gòu)設計的詳細講解

    上一篇,我們討論了故障度量和安全機制的ASIL等級。本篇我們來聊一聊系統(tǒng)架構(gòu)設計相關內(nèi)容。01系統(tǒng)架構(gòu)設計和TSC當我們開始寫TSC時,會涉及到下圖中一系列的內(nèi)容:當我們完成前三期(鏈接見文末)提到的安全機制規(guī)范后,我們就要開始整理好所有的安全需求并在系統(tǒng)
    的頭像 發(fā)表于 12-24 14:33 ?1773次閱讀

    SWE.2的軟件架構(gòu)設

    過程ID:SWE.2 過程名稱:軟件架構(gòu)設計 過程目的:軟件架構(gòu)設計過程目的是建立一個架構(gòu)設計,識別哪些軟件需求應該分配給軟件的哪些要素,并根據(jù)已定義的標準評估軟件架構(gòu)設計。 ? 過程
    的頭像 發(fā)表于 01-11 10:36 ?2833次閱讀

    SYS.3的系統(tǒng)架構(gòu)設

    系統(tǒng)架構(gòu)設計 過程ID:SYS.3 過程名稱:系統(tǒng)架構(gòu)設計 ? 過程目的:系統(tǒng)架構(gòu)設計過程目的,是建立系統(tǒng)架構(gòu)設計,并確定將哪些系統(tǒng)需求分配給系統(tǒng)的哪些要素,以及根據(jù)已定義的準則評估系
    的頭像 發(fā)表于 02-13 16:02 ?2752次閱讀

    架構(gòu)與微架構(gòu)設

    下面將從芯片的架構(gòu)設計、微架構(gòu)設計、使用設計文檔、設計分區(qū)、時鐘域和時鐘組、架構(gòu)調(diào)整與性能改進、處理器微架構(gòu)設計策略等角度進行說明,并以視頻H.264編碼器設計為例。
    的頭像 發(fā)表于 05-08 10:42 ?1263次閱讀
    <b class='flag-5'>架構(gòu)</b>與微<b class='flag-5'>架構(gòu)設</b>計

    SWE.2軟件架構(gòu)設

    過程ID : SWE.2 過程名稱 : 軟件架構(gòu)設計 過程目的 : 軟件架構(gòu)設計過程目的是建立一個架構(gòu)設計,識別哪些軟件需求應該分配給軟件的哪些要素,并根據(jù)已定義的標準評估軟件架構(gòu)設
    的頭像 發(fā)表于 08-24 09:43 ?995次閱讀