當前位置:首頁 > 工業(yè)控制 > 電子設計自動化
[導讀]其實我不是很會寫文章,想要把技術性文章寫的有意思就更難了。不過這一段日子總是有一種沖動想要寫點什么,把自己了解的有關Blackfin C語言優(yōu)化和系統(tǒng)優(yōu)化方面的技巧和知識寫下來,和正在從事這方面工作朋友們分享,

其實我不是很會寫文章,想要把技術性文章寫的有意思就更難了。不過這一段日子總是有一種沖動想要寫點什么,把自己了解的有關Blackfin C語言優(yōu)化和系統(tǒng)優(yōu)化方面的技巧和知識寫下來,和正在從事這方面工作朋友們分享,也許有些幫助,也算是對自己過去一段時間工作的總結(jié)。

在文章開始之前,我想先問讀者一個問題:您的DSP代碼里有多少是匯編,這些匯編里有多少是您自己寫的?

曾幾何時匯編編程是DSP工程師的一張名片。很多人到現(xiàn)在談起匯編編程還是頗為自豪的,搞得你想說自己不會都要鼓起點勇氣——那眼神是恨不得把你送回火星去。這主要是因為在最開始的時候DSP上的C語言編譯器不是很普遍,編譯器的水平也還在起步階段,很難用到DSP相應的硬件特性,編譯效率值得商榷。而且那時DSP應用場景和復雜度遠不比今天,基本上限制在數(shù)字信號處理的典型算法上,F(xiàn)FT,F(xiàn)IR,IIR濾波器,等等。這些函數(shù)和濾波器的實現(xiàn)相對今天的應用比較簡單,用匯編語言也容易突出DSP的硬件特性。還有一個原因是那時候DSP普遍都跑的很慢,基本上在幾十兆的水平。這也限制了C語言的使用。試想一下一段C代碼跑的比匯編慢十倍,幾十兆的DSP一下就變幾兆了。

但是今天再來看這所有的一切是完全不一樣了。首先是DSP的應用范圍越來越廣,客戶越來越多的希望用同一顆芯片,在同一個平臺上實現(xiàn)更多的設計和應用。這對DSP的設計,DSP和MCU的融合都帶來重大影響。DSP和MCU之間也不是過往那井水不犯河水的安寧。隨著DSP和MCU的主頻先后突破1GHz,在很多應用中DSP和MCU相伴相生的場景也開始被一顆強壯的芯代替,或者DSP或者MCU。在這樣的應用中,操作系統(tǒng),文件系統(tǒng),USB協(xié)議棧,TCP/IP,海量數(shù)據(jù)存儲,樣樣都會用到。數(shù)字信號處理也從骨灰級的濾波器變成全系列音視頻處理,OFDM基帶處理,天線陣列信號處理,彩色圖像重建… 試想一下這些應用哪一個不是成千上萬行代碼。匯編語言在編程復雜度,可移植性和可維護性上真的是遇到了前所未有的挑戰(zhàn)。而與此相對應的是C語言和C語言編譯器的蓬勃發(fā)展。今天您可以很容易找到上面提到所有這些應用和算法的C語言實現(xiàn),而C語言編譯器在編譯效率和成熟度上都有很大的突破。也讓C語言在DSP上的應用得以受到愈來愈高的重視。

但是C語言本身并不是為DSP定義的——C語言在PC上的默認條件在嵌入式處理器上不成立,比方說存儲空間無限,比方說內(nèi)存連續(xù),更不要說如何綁定DSP特殊的硬件支持。所以要充分發(fā)揮DSP的能力,C語言優(yōu)化是一下一張DSP工程師的名片。不會C語言優(yōu)化,OK,你可以回火星,地球很危險。

1. 拳譜總綱

閑話不表。在深入到細節(jié)之前,我想先從宏觀的角度討論一下C語言優(yōu)化一些大的原則。就好像我們在學七傷拳之前先來背背拳譜總綱,提綱挈領很重要。這些原則可以用圖1來說明。


圖1:C語言優(yōu)化性能曲線。

在這張圖里我們看到的是一根程序性能隨C語言和匯編語言在程序中比例變化而變化的曲線。整條性能曲線開始在A點,可以把它叫做out-of-box A點,就是程序員或者用戶用自己未經(jīng)優(yōu)化的C語言程序在DSP上編譯和運行能夠達到的性能。這個性能取決于程序的復雜度和編譯器的性能,但通常不會很高,大約在30%左右。這未經(jīng)優(yōu)化的C語言我們把它叫做out-of-box C。這里的30%是什么意思呢,就是你有一個600MHz的DSP去運行out-of-box C的程序,內(nèi)核被占滿了,但能做的有用的事情(性能)只相當于一個180MHz的DSP。為什么會這樣呢,前面已經(jīng)提到了,那就是out-of-box C不是為DSP量身定做的,不能充分用到DSP的各項性能。好,從A點開始向B點運動,我們就進入了今天要討論的范圍,也就是進入了“Box”。這個時候我們在out-of-box C的基礎上,在C語言的范圍內(nèi)面向我們要使用的DSP,對程序經(jīng)行優(yōu)化,就來到了B點。在B點,能夠達到的性能是大約70%~80%。要注意,從A點到B點,所有的工作都是在C語言的范圍內(nèi)進行的,并沒有進入?yún)R編語言的范疇。這個時候的C語言可以叫它做Optimized C,它是在out-of-box C的基礎上加入針對當前DSP的擴展而形成的。如果沿著性能曲線進一步向前,就進入了匯編語言的范疇,也就是程序員開始把一部分重要的,大量消耗cycle的程序改寫為匯編。隨著被改寫的程序的增多和進入?yún)R編領域的深入,我們達到了整條性能曲線的頂點——C點。這時大致有20%左右的代碼已經(jīng)被匯編語言代替,而程序的性能也已經(jīng)超過了90%,也就是我們基本上充分利用了整顆DSP的全部性能。在C點的位置,程序是處在一個混合編程的狀態(tài)——Optimized C和匯編的混合編程。這里可以使用C語言可調(diào)用的匯編子程序以提高重用性和可維護性。有讀者可能好奇了,如果進一步擴大匯編語言在程序中的比例是不是可以繼續(xù)提高性能呢。實際的情況跟我們想象的并不完全相同,性能不升反降了。舉一個極端的例子,如果把所有的C語言都用匯編改寫,我們就處在整條性能曲線的D點。在這里,程序的整體性能并沒有C點高。這主要是因為C語言作為一種高級語言在控制、跳轉(zhuǎn)代碼,以及對復雜數(shù)據(jù)結(jié)構(gòu)的訪問上相對匯編語言有很大優(yōu)勢。

對整條性能曲線可以做這樣的總結(jié),1)最佳性能產(chǎn)生在C和匯編按一定比例分配的情況下,80-20可以作為一個參考;2)將所有代碼都轉(zhuǎn)為匯編并不會帶來性能的進一步提高;3)在C語言編譯器的幫助下,將大多數(shù)控制代碼保留在C語言范疇中是可能的;4)要想達到最佳性能,那些消耗cycle最多的代碼應轉(zhuǎn)化為C語言可以調(diào)用的匯編函數(shù)。簡單說就是讓C和匯編語言做各自擅長的事情,在動態(tài)平衡中達到最佳性能。內(nèi)事不決問張昭,外事不決問周瑜,各司其職。

在DSP性能大幅提高的今天,如果可以如圖中B點那樣用Optimized C將C語言在DSP上的性能提高到%70以上,很有可能對于大多數(shù)應用場景就已經(jīng)足夠了,并不是一定要接觸匯編語言的。這個從A點到B點的過程也正是這篇文章要討論的重點。

2. 是騾子是馬您先別溜

說到這里,有很多朋友等不及要開始做優(yōu)化了:打開程序,一條語句、一條語句立刻看起來。很多時候我們在工作中都遇到這樣的情況,所以第一刻就要喊停,等我先講講一些容易被忽略的東西。

首先最容易被忽略的是數(shù)據(jù)類型。通常編譯器對ANSI C所有數(shù)據(jù)類型都是支持的,但是硬件呢,是不是對所有的數(shù)據(jù)類型都很有效的支持呢?舉個例子,很多DSP都有專門針對16-bit定點運算的指令,特別是一些并行指令。如果在算法中可以將數(shù)據(jù)類型設計為16-bit就可以充分利用到這些指令。Blackfin每個cycle可以做2個16-bit乘法,而每個32-bit乘法則要消耗3個cycle。這中間有6倍的差距,是值得我們考慮的。另外定點芯片不直接支持浮點操作,如果算法中有浮點類型和浮點運算,則首先應該考慮在不影響動態(tài)范圍和精度的基礎上進行定點化。因為在定點芯片上每個浮點操作都可能消耗成百上千個cycle來得到近似的結(jié)果。對于小數(shù)類型,Blackfin直接支持1.15和1.31小數(shù)類型的操作,這給程序員很大的靈活度。所以我們首先要盡可能依托當前DSP最擅長的操作來確認數(shù)據(jù)類型被支持的程度,并對算法進行調(diào)整。

另一個容易被忽略的地方是算法本身。也就是被采用的算法本身是不是已經(jīng)是最高效,最優(yōu)的。考慮一下正在用的排序算法是不是還有余地改進;要用的正弦波形是計算還是查表;又或者整個算法或者部分可以被更高效的算法代替。這樣的考慮往往可以達到事半功倍的效果,就好像換了三趟公交去看朋友,下車一抬頭發(fā)現(xiàn)有條地鐵直達。

在現(xiàn)代高性能DSP中通常都有比較深的指令流水線。流水線的作用是把一個cycle里要做的事情分在多個步驟里來做。對于高主頻的芯片而言,流水線的深度是很重要的,它從某種程度上決定了可能的最高主頻速度。每一個節(jié)拍,指令流水線上不同功能單元同時并行運作,每條指令按順序流經(jīng)這些功能單元。可惜事物總有兩面性,當流水線遇到了條件跳轉(zhuǎn),它的另外一面就充分暴露出來了。那就是在跳轉(zhuǎn)的時候,當前指令之后已經(jīng)在流水線里的指令全部都要被清空,然后再讓要跳轉(zhuǎn)到的目的指令重新進入流水線。如果流水線的深度是N,那么這里損失的cycle通常為N-1。流水線越深,損失越大。如果不巧這個條件跳轉(zhuǎn)在循環(huán)里面,這個N-1的損失就會被放大了。用一些方式替代條件跳轉(zhuǎn)可以減輕這樣的損失,比方說盡可能的使用條件執(zhí)行和條件賦值,或者max和min語句,因為這些語句的執(zhí)行通??梢栽贒SP的匯編級找到對應的單周期語句。另外就是要盡可能的避免在循環(huán)中使用條件跳轉(zhuǎn)。

除法運算是我們需要注意的一種操作,因為通常除法在DSP中都是一段近似算法來實現(xiàn)的。比如說在Blackfin提供兩種除法近似,精度較低的一種需要大約40 cycle而32bit除法則需要大致400 cycle。想想一個1000次的for循環(huán)里如果有3次除法,您就大致知道您的程序會跑多慢了。所以我們要在算法中考慮到除法的影響和可能的替代方式,例如利用不等式原則可以把除法變成乘法,又或者模2的除法可以變成移位。當然了,我在這里提到的替代,包括針對前面的數(shù)據(jù)類型,算法和條件跳轉(zhuǎn),都是遵循“盡可能”的原則,沒有絕對的意思。優(yōu)化的后程序效率的高低就是體現(xiàn)在這個盡可能上。

3. 編譯器,睡在上鋪的兄弟

這一刻,你不是一個人在戰(zhàn)斗…,這話聽起來好像有點耳熟。如果把C語言優(yōu)化比作是程序員在進行的一場戰(zhàn)斗的話,程序員并不孤獨,因為我們有一個隱形的戰(zhàn)友,就是編譯器,而編譯器的優(yōu)化功能就是我們最有力的武器。以VisualDSP++為例,通常新建的工程C語言優(yōu)化缺省是不打開的,程序員可以按照程序運行的需要打開優(yōu)化。這個從不優(yōu)化到優(yōu)化的過程實際上反映了VisualDSP++編譯器在處理C語言程序過程中的兩步走。

在優(yōu)化開關沒有打開的情況下,編譯器對C代碼的處理是一一對應的直譯,就是把C代碼一句一句按照先后順序翻譯為相應的一條或者多條匯編語句。在直譯的同時,編譯器也會注意到對中間變量和中間結(jié)果的保護——不管他們接下來會不會被用到,他們都會被寫入存儲器,盡管這樣做會增加很多冗余。經(jīng)過這樣的直譯,一段C代碼對應的匯編代碼可能是多一個數(shù)量級的。一個典型的例子是,只有兩條乘累加指令的for循環(huán)代碼對應的匯編代碼是幾十條之多??上攵@樣不經(jīng)優(yōu)化的代碼執(zhí)行速度是很慢的。一個參考數(shù)據(jù)是打開優(yōu)化開關以后的代碼運行速度平均可以提高20倍。也就是說,一個600MHz的芯片,不打開優(yōu)化,相當于主頻降到30MHz。所以絕大多數(shù)情況下我們要打開編譯器的優(yōu)化開關。

編譯器的第二步走,就是對直譯產(chǎn)生的代碼進行優(yōu)化,這個過程就是充分利用DSP的硬件實現(xiàn)指令和事件最大可能并行的過程。這里的并行既有運算單元本身的并行也有運算單元和其他功能單元的并行。以Blackfin為例,每一個core里都有兩個乘法器和加法器。編譯器在優(yōu)化的時候第一個層次的并行是運算的并行,就是盡可能同時使用兩個運算單元,做乘法就盡可能做到兩個乘法器同時運算,做加法就盡可能做到兩個加法器同時運算。接下來一個層次的并行是指令的并行,就是運算單元和memory存取、或者其他功能單元之間的并行,仍以Blackfin為例,在同一個cycle中,可以有兩個乘累加和兩個數(shù)據(jù)的存或取并發(fā)執(zhí)行。這些并行都是DSP硬件本身支持的,編譯器優(yōu)化的工作就是充分利用DSP的硬件能力。

循環(huán)是編譯器在第二步走的過程中重點處理的對象。這比較好理解,因為那些大量消耗cycle的代碼往往是在循環(huán)當中的。下面我就結(jié)合編譯器對循環(huán)的處理,來看看在優(yōu)化的過程中程序員要怎么和編譯器并肩戰(zhàn)斗。編譯器對循環(huán)處理的目標就是希望在每一次循環(huán)中盡可能的并行。為了實現(xiàn)這個目標,編譯器采取的措施就是不停的打開循環(huán)、降低循環(huán)次數(shù),增加循環(huán)內(nèi)的指令個數(shù),提高指令之間并發(fā)的幾率。舉個例子,一個100次的循環(huán)中有一個乘累加,編譯器打開循環(huán),將循環(huán)次數(shù)降低一半,循環(huán)內(nèi)每次就會出現(xiàn)兩個乘累加,編譯器就有可能安排Blackfin的兩個乘累加單元同時運算,從而將執(zhí)行的效率提高一倍,這個優(yōu)化過程叫做矢量化(Vectorization)。如果這個循環(huán)中還有加法、減法、存數(shù)、取數(shù),或者其他運算,編譯器還會安排這些指令和乘累加并發(fā),或者這些指令之間并發(fā),這個優(yōu)化的過程也是實現(xiàn)軟件流水線的過程(Software Pipeline)——在優(yōu)化后的代碼中往往出現(xiàn)當前的運算和以往的存數(shù)或者未來的取數(shù)并行。編譯器對循環(huán)的打開可能是多次的,直到編譯器有足夠的指令可以充分安排并發(fā)。

說到這里我們對這位睡在上鋪的兄弟已經(jīng)有一些了解了,那么程序員在這個優(yōu)化的過程中應該做什么呢?這就要從矢量化和軟件流水線受到的限制談起。剛才提到在優(yōu)化過程中編譯器一個重要的操作就是打開循環(huán),如果循環(huán)次數(shù)是2的N次方例如8,16,32…,編譯器就可以很舒服的按照需要多次打開循環(huán)。但如果在上面的例子里循環(huán)次數(shù)是101,編譯器是無法打開循環(huán)的,對這個循環(huán)的優(yōu)化就不能有效的展開。這個時候就需要程序員做工作了:我們可以將循環(huán)里面的運算在循環(huán)外實現(xiàn)一次,讓循環(huán)次數(shù)變?yōu)?00,從而給編譯器兩次打開循環(huán)的機會(2x2x25)。矢量化和軟件流水線對操作數(shù)的存放也是有要求的。首先,對memory中操作數(shù)讀取和計算結(jié)果存放必須是順序(地址遞增或者遞減)的,如果是亂序或者隨機的,不管是運算的并行和是指令的并行都很難實現(xiàn)。我們在編寫程序和對C程序進行優(yōu)化的時候就要注意到盡可能安排數(shù)據(jù)訪問的順序性。其次,根據(jù)操作數(shù)的寬度,程序員還要注意保證數(shù)據(jù)的2字對齊或者4字對齊。這有助于在指令并行執(zhí)行時對操作時的有效讀取。程序員可以通過在定義數(shù)據(jù)(組)的時候用編譯器提供的相應編譯選項來實現(xiàn)數(shù)據(jù)的對齊。在進行矢量化和軟件流水線的過程中往往要對程序執(zhí)行的順序做局部調(diào)整,這種調(diào)整對程序整體來說雖然是微調(diào),但在某些情況下改變原始程序執(zhí)行的順序會影響到程序執(zhí)行結(jié)果的正確性。最典型的情況就是運算的操作數(shù)和結(jié)果之間存在某種聯(lián)系和依賴。比方說數(shù)組中靠后的成員數(shù)值取決于靠前的成員運算的結(jié)果,這意味著數(shù)組成員之間有依賴性,不獨立,從而不能實現(xiàn)并行計算。這在for循環(huán)中經(jīng)常體現(xiàn)為一個運算的兩個操作數(shù)指針可能是指向同一個數(shù)組的不同位置。數(shù)據(jù)獨立性是到目前位置我們看到影響客戶C代碼優(yōu)化效率最嚴重的因素。

編譯器在進行優(yōu)化的時候永遠都遵循一個基本原則,那就是優(yōu)化不能影響程序運行的正確性。所以當編譯器發(fā)現(xiàn)矢量化和軟件流水線需要滿足的那些條件不確定的時候,它的行為往往是保守的。這是一種寧可放棄性能也要保證正確性的態(tài)度,無可厚非。該出手時就出手,到了程序員幫編譯器一把的時候了。因為編譯器面對的這些不確定性,在程序員看來通常是確定,一定,以及肯定的。以前面數(shù)據(jù)獨立性的問題為例,編譯器很難判斷當前for循環(huán)中兩個指針pa,pb在運行的時候是不是會指向同一個數(shù)組,因為對編譯器來說它們只是兩個指針,對它們后面實際操作的對象毫無頭緒。而程序員卻可能清楚的知道這段程序處理的兩個數(shù)組是定義在兩段不同的物理內(nèi)存上的,也就是說這兩個指針不會指向同一段地址,數(shù)據(jù)的獨立性是有保證的。這個時候我們就可以通過相應的編譯選項通知編譯器:下面這個for循環(huán)里的數(shù)據(jù)是獨立的,放心大膽的優(yōu)化吧。這里提到的編譯選項,包括前面說的關于循環(huán)次數(shù),數(shù)據(jù)對齊,以及存儲位置等其他編譯選項都可以在VisualDSP++關于C語言編譯器的手冊中找到。

了解了編譯器的工作方式,針對矢量化和軟件流水線對代碼和數(shù)據(jù)存儲的要求,在C語言范圍內(nèi)對相關代碼進行調(diào)整,并通過編譯選項將有利于優(yōu)化的確定信息通知編譯器,依托C語言編譯器的能力實現(xiàn)代碼的高效優(yōu)化,就是程序員在這里要做的工作。

4. 打完收工,還是剛剛開始

我們已經(jīng)簡單的談了C語言優(yōu)化,特別是性能曲線從A點到B點應該遵循的主旨和一些技巧。個人認為,嵌入式系統(tǒng)上高效的C代碼優(yōu)化不是在代碼寫好以后才開始的一個獨立的步驟,而應該是在系統(tǒng)設計和編寫代碼的時候就已經(jīng)開始考慮硬件平臺有效執(zhí)行的因素,妥善安排算法,精度,數(shù)據(jù)類型,存儲空間和性能之間的關系。再加上靈活應用上面提到的技巧,可以做到事半功倍。由于篇幅的限制,這里只能提綱挈領的講一講。有興趣的讀者可以訪問http://www.analog.com/zh/embedded-processing-dsp/content/blackfin_bold_training/fca.html#ADEV001

到此為止,C語言優(yōu)化告一段落,而嵌入式系統(tǒng)的優(yōu)化才剛剛開始。片內(nèi)片外代碼和數(shù)據(jù)的分配,主頻和外頻的選擇,系統(tǒng)帶寬和DMA的使用,這些都會影響到優(yōu)化后的代碼在嵌入式系統(tǒng)里最終的性能?;鹦侨说牡厍蛑茫艅倓傞_始。



來源:柒色72次

本站聲明: 本文章由作者或相關機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉