讀書筆記(UIKit性能優化解析和Block經典實用)(一)

讀書筆記(一)

一、instancetype 和 id 作爲初始化實例 返回值的不同

Objective-C的一些使用慣例不僅僅是好的編程習慣,更是給編譯器的隱藏指令。
例如, alloc 和 init 的返回類型都是 id ,然而在Xcode中,編譯器會檢查所有正確類型。它是怎麼做到的呢?
在Cocoa中,約定 alloc 或 init 的方法總是返回接收器類實例的對象。據說這些方法有一個相關返回類型。
雖然類構造方法也是返回 id ,但是類構造方法並沒有做同樣的類型檢查,因爲它們不遵循命名規範。
你可以自己試着這樣:

[[[NSArray alloc] init] mediaPlaybackAllowsAirPlay]; // ❗ "No visible @interface for `NSArray` declares the selector `mediaPlaybackAllowsAirPlay`"

[[NSArray array] mediaPlaybackAllowsAirPlay]; // (No error)

由於alloc 和 init 作爲相關返回類型遵循命名規範,執行對NSArray的正確類型檢查。然而,等價類構造函數array不遵循命名規範,它被認爲是id類型。
id類型對禁用類型安全性檢查非常有用,但當你確實需要它的時候卻沒有時,情況會變得非常糟糕。
另一種顯示聲明返回類型(在之前例子中的 (NSArray *))的方式有了稍微的改進,但是它不利於子類的發揮。
所以編譯器從這裏介入以解決Objective-C類型系統的這個永恆邊界情況:
instancetype 關鍵字,它可以表示一個方法的相關返回類型。例如:

@interface Person
+(instancetype)personWithName:(NSString *)name;
@end
  • instancetype 與 id 不一樣, instancetype 只能在方法聲明中作爲返回類型使用。

使用 instancetype ,編譯器將正確的推斷出 +personWithName: 是 Person 的一個實例。
爲了在不久的將來使用 instancetype ,你可以在Foundation中查找類構造函數。例如UICollectionViewLayoutAttributes 就已經正在使用 instancetype 了。

二、iOS爲什麼不要在init初始化方法裏調用self.view

首先.如果你調用self.view的時候,就會調用view的getter方法, 這個時候,view是空的,那麼系統就會自動給你創建一個view,然後就會觸發ViewDidLoad方法.那麼這個時候,如果你init方法裏有數組初始化.但是你還沒走到那步,而直接就給數組賦值了,那麼這個值賦值給了一個不存在的數組.這樣就容易出現錯誤.所以,儘量不要在init方法裏寫可視化控件的語句.

三、忽略編譯警告

如果你知道你的代碼不會導致內存泄露,你可以通過加入這些代碼忽略這些警告

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

[myObj performSelector:mySelector withObject:name];

#pragma clang diagnostic pop

注意我們是如何在相關代碼上下文中用 pragma 停用 -Warc-performSelector-leaks 檢查的。這確保我們沒有全局禁用。如果全局禁用,可能會導致錯誤。

使用 #pragma unused():忽略沒用使用變量的編譯警告

-(NSInteger)giveMeFive
{
    NSString *foo;
    #pragma unused (foo)

    return 5;
}

四、Block的深入學習

一些關鍵點:

block 是在棧上創建的
block 可以複製到堆上
Block會捕獲棧上的變量(或指針),將其複製爲自己私有的const(變量)。
(如果在Block中修改Block塊外的)棧上的變量和指針,那麼這些變量和指針必須用__block關鍵字申明(譯者注:否則就會跟上面的情況一樣只是捕獲他們的瞬時值)。
block 可以聲明成全局靜態的

如果 block 沒有在其他地方被保持,那麼它會隨着棧生存並且當棧幀(stack frame)返回的時候消失。僅存在於棧上時,block對對象訪問的內存管理和生命週期沒有任何影響。

如果 block 需要在棧幀返回的時候存在,它們需要明確地被複制到堆上,這樣,block 會像其他 Cocoa 對象一樣增加引用計數。當它們被複制的時候,它會帶着它們的捕獲作用域一起,retain 他們所有引用的對象。

如果一個 block引用了一個棧變量或指針,那麼這個block初始化的時候會擁有這個變量或指針的const副本,所以(被捕獲之後再在棧中改變這個變量或指針的值)是不起作用的。(譯者注:所以這時候我們在block中對這種變量進行賦值會編譯報錯:Variable is not assignable(missing __block type specifier),因爲他們是副本而且是const的.具體見下面的例程)。

當一個 block 被複制後,__block 聲明的棧變量的引用被複制到了堆裏,複製完成之後,無論是棧上的block還是剛剛產生在堆上的block(棧上block的副本)都會引用該變量在堆上的副本。

(下面代碼是譯者加的)

...
CGFloat blockInt = 10;
void (^playblock)(void) = ^{
    NSLog(@"blockInt = %zd", blockInt);
};
blockInt ++;
playblock();
...

//結果爲:blockInt = 10

最重要的事情是 __block 聲明的變量和指針在 block 裏面是作爲顯示操作真實值/對象的結構來對待的。

block 在 Objective-C 的 runtime(運行時) 裏面被當作一等公民對待:他們有一個 isa 指針,一個類也是用 isa 指針在Objective-C 運行時來訪問方法和存儲數據的。在非 ARC 環境肯定會把它搞得很糟糕,並且懸掛指針會導致 crash。__block 僅僅對 block 內的變量起作用,它只是簡單地告訴 block:

嗨,這個指針或者原始的類型依賴它們在的棧。請用一個棧上的新變量來引用它。我是說,請對它進行雙重解引用,不要 retain 它。 謝謝,哥們。

如果在定義之後但是 block 沒有被調用前,對象被釋放了,那麼 block 的執行會導致 crash。 __block 變量不會在 block 中被持有,最後… 指針、引用、解引用以及引用計數變得一團糟。

self 的循環引用

當使用代碼塊和異步分發的時候,要注意避免引用循環。 總是使用 weak 來引用對象,避免引用循環。(譯者注:這裏更爲優雅的方式是採用影子變量@weakify/@strongify 這裏有更爲詳細的說明) 此外,把持有 block 的屬性設置爲 nil (比如 self.completionBlock = nil) 是一個好的實踐。它會打破 block 捕獲的作用域帶來的引用循環。

例子:

__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
    [weakSelf doSomethingWithData:data];
}];

不要這樣:

[self executeBlock:^(NSData *data, NSError *error) {
    [self doSomethingWithData:data];
}];

多個語句的例子:

__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [strongSelf doSomethingWithData:data];
        [strongSelf doSomethingWithData:data];
    }
}];

不要這樣:

__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
    [weakSelf doSomethingWithData:data];
    [weakSelf doSomethingWithData:data];
}];

你應該把這兩行代碼作爲 snippet 加到 Xcode 裏面並且總是這樣使用它們。

__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;

這裏我們來討論下 block 裏面的 self 的 weak 和 strong 限定詞的一些微妙的地方。簡而言之,我們可以參考 self 在 block 裏面的三種不同情況。

直接在 block 裏面使用關鍵詞 self
在 block 外定義一個 __weak 的 引用到 self,並且在 block 裏面使用這個弱引用
在 block 外定義一個 __weak 的 引用到 self,並在在 block 內部通過這個弱引用定義一個 __strong 的引用。

方案 1. 直接在 block 裏面使用關鍵詞 self

如果我們直接在 block 裏面用 self 關鍵字,對象會在 block 的定義時候被 retain,(實際上 block 是 copied 但是爲了簡單我們可以忽略這個)。一個 const 的對 self 的引用在 block 裏面有自己的位置並且它會影響對象的引用計數。如果這個block被其他的類使用並且(或者)彼此間傳來傳去,我們可能想要在 block 中保留 self,就像其他在 block 中使用的對象一樣. 因爲他們是block執行所需要的.

dispatch_block_t completionBlock = ^{
    NSLog(@"%@", self);
}

MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
                animated:YES
                completion:completionHandler];

沒啥大不了。但是如果通過一個屬性中的 self 保留 了這個 block(就像下面的例程一樣),對象( self )保留了 block 會怎麼樣呢?

self.completionHandler = ^{
    NSLog(@"%@", self);
}

MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
                animated:YES
                completion:self.completionHandler];

這就是有名的 retain cycle, 並且我們通常應該避免它。這種情況下我們收到 CLANG 的警告:

Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 裏面發現了 `self` 的強引用,可能會導致循環引用)

所以 __weak 就有用武之地了。

方案 2. 在 block 外定義一個 __weak 的 引用到 self,並且在 block 裏面使用這個弱引用

這樣會避免循壞引用,也是通常情況下我們的block作爲類的屬性被self retain 的時候會做的。

__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
    NSLog(@"%@", weakSelf);
};

MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
                animated:YES
                completion:self.completionHandler];

這個情況下 block 沒有 retain 對象並且對象在屬性裏面 retain 了 block 。所以這樣我們能保證了安全的訪問 self。 不過糟糕的是,它可能被設置成 nil 的。問題是:如何讓 self 在 block 裏面安全地被銷燬。

考慮這麼個情況:block 作爲屬性(property)賦值的結果,從一個對象被複制到另一個對象(如 myController),在這個複製的 block 執行之前,前者(即之前的那個對象)已經被解除分配。

下面的更有意思。

方案 3. 在 block 外定義一個 weak 的 引用到 self,並在在 block 內部通過這個弱引用定義一個 strong 的引用

你可能會想,首先,這是避免 retain cycle 警告的一個技巧。

這不是重點,這個 self 的強引用是在block 執行時 被創建的,但是否使用 self 在 block 定義時就已經定下來了, 因此self (在block執行時) 會被 retain.

Apple 文檔 中表示 “爲了 non-trivial cycles ,你應該這樣” :

MyViewController *myController = [[MyViewController alloc] init...];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;
    if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

首先,我覺得這個例子看起來是錯誤的。如果 block 本身在 completionHandler 屬性中被 retain 了,那麼 self 如何被 delloc 和在 block 之外賦值爲 nil 呢? completionHandler 屬性可以被聲明爲 assign 或者 unsafe_unretained 的,來允許對象在 block 被傳遞之後被銷燬。

我不能理解這樣做的理由,如果其他對象需要這個對象(self),block 被傳遞的時候應該 retain 對象,所以 block 應該不被作爲屬性存儲。這種情況下不應該用weak/strong

總之,其他情況下,希望 weakSelf 變成 nil 的話,就像第二種情況解釋那麼寫(在 block 之外定義一個弱應用並且在 block 裏面使用)。

還有,Apple的 “trivial block” 是什麼呢。我們的理解是 trivial block 是一個不被傳送的 block ,它在一個良好定義和控制的作用域裏面,weak 修飾只是爲了避免循環引用。

雖然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 討論的 一 些 的 在線 參考, Matt Galloway 的 (Effective Objective-C 2.0 和 Pro Multithreading and Memory Management for iOS and OS X ,大多數開發者始終沒有弄清楚概念。

在 block 內用強引用的優點是,搶佔執行的時候的魯棒性。在 block 執行的時候, 再次溫故下上面的三個例子:

方案 1. 直接在 block 裏面使用關鍵詞 self

如果 block 被屬性 retain,self 和 block 之間會有一個循環引用並且它們不會再被釋放。如果 block 被傳送並且被其他的對象 copy 了,self 在每一個 copy 裏面被 retain

方案 2. 在 block 外定義一個 __weak 的 引用到 self,並且在 block 裏面使用這個弱引用

不管 block 是否通過屬性被 retain ,這裏都不會發生循環引用。如果 block 被傳遞或者 copy 了,在執行的時候,weakSelf 可能已經變成 nil。

block 的執行可以搶佔,而且對 weakSelf 指針的調用時序不同可以導致不同的結果(如:在一個特定的時序下 weakSelf 可能會變成nil)。

__weak typeof(self) weakSelf = self;
dispatch_block_t block =  ^{
    [weakSelf doSomething]; // weakSelf != nil
    // preemption, weakSelf turned nil
    [weakSelf doSomethingElse]; // weakSelf == nil
};

方案 3. 在 block 外定義一個 weak 的 引用到 self,並在在 block 內部通過這個弱引用定義一個 strong 的引用。

不管 block 是否通過屬性被 retain ,這裏也不會發生循環引用。如果 block 被傳遞到其他對象並且被複制了,執行的時候,weakSelf 可能被nil,因爲強引用被賦值並且不會變成nil的時候,我們確保對象 在 block 調用的完整週期裏面被 retain了,如果搶佔發生了,隨後的對 strongSelf 的執行會繼續並且會產生一樣的值。如果 strongSelf 的執行到 nil,那麼在 block 不能正確執行前已經返回了。

__weak typeof(self) weakSelf = self;
myObj.myBlock =  ^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
    [strongSelf doSomething]; // strongSelf != nil
    // preemption, strongSelf still not nil(搶佔的時候,strongSelf 還是非 nil 的)
    [strongSelf doSomethingElse]; // strongSelf != nil
    }
    else {
        // Probably nothing...
        return;
    }
};

在ARC條件中,如果嘗試用 -> 符號訪問一個實例變量,編譯器會給出非常清晰的錯誤信息:

Dereferencing a weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (對一個weak 指針的解引用不允許的,因爲可能在競態條件裏面變成 null, 所以先把他定義成 strong 的屬性)

可以用下面的代碼展示

__weak typeof(self) weakSelf = self;
myObj.myBlock =  ^{
    id localVal = weakSelf->someIVar;
};

在最後

方案 1: 只能在 block 不是作爲一個 property 的時候使用,否則會導致 retain cycle。

方案 2: 當 block 被聲明爲一個 property 的時候使用。

方案 3: 和併發執行有關。當涉及異步的服務的時候,block 可以在之後被執行,並且不會發生關於 self 是否存在的問題。

五、IOS高效添加圓角效果

誤區一:

view.layer.cornerRadius = 5

該代碼經過測試是不會造成內存性能損耗。

view.layer.masksToBounds = Yes // 遮罩layer層以下的視
圖,使圓角生效

這代碼纔是造成性能損耗的關鍵因素。由於該代碼會導致視圖離屏渲染(Color Offscreen-Renderd Yellow)相關文章:UIKit性能調優實戰講解

注意事項:我們應該儘量避免重寫 drawRect 方法,用CAShapeLayer代替圖層繪製。不恰當的使用這個方法

會導致內存暴增。舉個例子,iPhone6 上與屏幕等大的 UIView,即使重寫一個空的 drawRect 方法,它也

至少佔用 750 1134 4 字節 ≈ 3.4 Mb 的內存。在內存惡鬼drawRect 及其後續中,作者詳細介紹了

其中原理,據他測試,在 iPhone6 上空的、與屏幕等大的視圖重寫 drawRect 方法會消耗 5.2 Mb 內存。

總之,能避免重寫 drawRect 方法就儘可能避免。

其次,這種方法本質上是用遮罩層 mask 來實現,因此同樣無可避免的會導致離屏渲染。我試着將此前 34 個

視圖的圓角改用這種方法實現,結果 fps 掉到 11 左右。已經屬於卡出翔的節奏了。

高效設置圓角實戰:

爲 UIView 添加圓角

這種做法的原理是手動畫出圓角。雖然我們之前說過,爲普通的視圖直接設置 cornerRadius 屬性即可。但萬一不可避免的需要使用 masksToBounds,就可以使用下面這種方法,它的核心代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func kt_drawRectWithRoundedCorner(radius radius: CGFloat,       
                       borderWidth: CGFloat,
                                  backgroundColor: UIColor,
                                  borderColor: UIColor) -> UIImage {    
     UIGraphicsBeginImageContextWithOptions(sizeToFit, false, UIScreen.mainScreen().scale)
     let context = UIGraphicsGetCurrentContext()
     
     CGContextMoveToPoint(context, 開始位置);  // 開始座標右邊開始
     CGContextAddArcToPoint(context, x1, y1, x2, y2, radius);  // 這種類型的代碼重複四次
   
     CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)  
     let output = UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
     return output
}

這個方法返回的是 UIImage,也就是說我們利用 Core Graphics 自己畫出了一個圓角矩形。除了一些必要的代碼外,最核心的就是 CGContextAddArcToPoint 函數。它中間的四個參數表示曲線的起點和終點座標,最後一個參數表示半徑。調用了四次函數後,就可以畫出圓角矩形。最後再從當前的繪圖上下文中獲取圖片並返回。
有了這個圖片後,我們創建一個 UIImageView 並插入到視圖層級的底部:

1
2
3
4
5
6
7
8
9
10
11
12
extension UIView {
    func kt_addCorner(radius radius: CGFloat,
                      borderWidth: CGFloat,
                      backgroundColor: UIColor,
                      borderColor: UIColor) {
        let imageView = UIImageView(image: kt_drawRectWithRoundedCorner(radius: radius,
                                    borderWidth: borderWidth,
                                    backgroundColor: backgroundColor,
                                    borderColor: borderColor))
        self.insertSubview(imageView, atIndex: 0)
    }
}

完整的代碼可以在項目中找到,使用時,你只需要這樣寫:

1
2
let view = UIView(frame: CGRectMake(1,2,3,4))
view.kt_addCorner(radius: 6)

爲 UIImageView 添加圓角

相比於上面一種實現方法,爲 UIImageView 添加圓角更爲常用。它的實現思路是直接截取圖片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension UIImage {
    func kt_drawRectWithRoundedCorner(radius radius: CGFloat, _ sizetoFit: CGSize) -> UIImage {
        let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit)
         
        UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
        CGContextAddPath(UIGraphicsGetCurrentContext(),
            UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.AllCorners,
                cornerRadii: CGSize(width: radius, height: radius)).CGPath)
        CGContextClip(UIGraphicsGetCurrentContext())
         
        self.drawInRect(rect)
        CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
        let output = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
         
        return output
    }
}
`

圓角路徑直接用貝塞爾曲線繪製,一個意外的 bonus 是還可以選擇哪幾個角有圓角效果。這個函數的效果是將原來的 UIImage 剪裁出圓角。配合着這函數,我們可以爲 UIImageView 拓展一個設置圓角的方法:

1
2
3
4
5
6
7
8
9
extension UIImageView {
    /**
     / !!!只有當 imageView 不爲nil 時,調用此方法纔有效果
     :param: radius 圓角半徑
     */
    override func kt_addCorner(radius radius: CGFloat) {
        self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size)
    }
}

完整的代碼可以在項目中找到,使用時,你只需要這樣寫:

1
2
let imageView = let imgView1 = UIImageView(image: UIImage(name: ""))
imageView.kt_addCorner(radius: 6)

六、UIKit 性能優化 讀後總結

原文地址:UIKit性能調優實戰講解

首選來說 爲什麼我們要進行UIKit優化?

直接上圖:

如圖所示,正常情況圖層的渲染是通過CPU的計算和GPU的處理完成 正常的渲染流程的。

  • 當CPU和GPU的耗時時間控制在16.67ms之間(如第一部分)圖層就會正常渲染。
  • 當CPU和GPU的耗時時間超過了16.67ms(如第二部分)圖層渲染就會掉幀,即屏幕就會出現花屏或者卡頓的現象。
    所以,爲了保證圖層的正常渲染以及提高性能,我們着重從緩減CPU 和 GPU 不必要的消耗上,來做優化和調整。

1.爲什麼要把控件儘量設置成不透明,如果是透明的會有什麼影響,如何檢測這種影響?

  • 影響:如果控件帶有透明度,這就會導致其控件與下層的控件的顏色 混合在一塊。例如,上層是藍色(透明度50%),下層是紅色,那麼最終顯示的是紫色。
    這種顏色的混合,需要GPU去處理混合的結果,這就一定程度的造成GPU額外的消耗。當實際層次更加複雜的時候(三層,五層),GPU的消耗更加明顯,對性能的影響就很明顯了。如果將最上層的透明度設置爲1,這樣GPU就會忽略該層下面所有層次,節約了不必要的計算,從而提升性能。
  • 檢測:檢測步驟,通過蘋果自帶的軟件instruments中的core animation 選項 -> 選擇color Blended Layers 選項 紅色區域就是透明度的設置.
  • 優化措施:
    • opaque = true 當然這個UIView默認就是 true的
    • alpha = 1 透明度儘量設置爲1
    • 最重要的是要設置背景顏色與父類一直,如果不設置默認會是透明的,另外clearColor(背景色透明)也不能設置,。label.backgroundColor = UIColor.WhiteColor()

4.離屏渲染

離屏渲染就是相對於正常的渲染流程(將圖層直接合成到幀的緩衝區中),增加了一個額外的步驟(先創建屏幕外緩衝區,然後渲染到文理中,最後將結果渲染到幀的緩衝區),這個步驟中需要GPU額外的處理計算,造成很大消耗,所以俗稱:離屏渲染。
我們先看看正常的渲染通道(Render-Pass):

正常的渲染是OpenGL 提交一個命令到 command buff ,隨後GPU 進行渲染 ,然後將渲染結果合成到(render buff)幀緩衝區中。
但是複雜的效果無法直接渲染出結果,需要分佈渲染最後組合起來,比如添加一個蒙版(mask):

如圖所示,渲染流程中,分爲好幾部分進行的。在前兩個渲染通道中,GPU分別得到紋理(texture,也就是相機圖標)和layer(藍色的蒙版)的渲染
結果。但這兩個渲染結果沒有直接放到render buff(幀緩衝區中),也就是發生了離屏渲染,知道第三個渲染通道,才把兩者結合起來放入 render buff (幀緩衝區中)。離屏渲染實際上就是將渲染結果臨時保存起來,等到用的時候再取出來,因此相對於普通渲染佔用資源。
以下情況可能會導致離屏渲染:

1
2
3
4
1.重寫drawRect 方法。(一般情況下畫圖都用CAShapeLayer)
2.有mask(圓角)或者陰影(layer.masksToBounds,layer.shadow),模糊效果也是一種mask (當使用圓角或者陰影是最好開啓光柵化以達到緩衝的
效果)
3.光柵化 layer.shouldRasterize = true

3. 光柵化

所謂光柵化其實就是將一個layer預先渲染成位圖(bitmap),然後加入緩存中。如果對於陰影效果這樣的比較消耗資源的靜態內容進行緩存、
可以得到一定幅度的性能提升。

1
label.layer.shouldRasterize = true

緩衝中的對象有效期只有100ms(0。1s)如果超過這個時間緩存會自動清理。光柵化是把雙刃劍,先寫入緩存再讀取會消耗一定的時間,因此在沒有必要的情況下儘量不要使用。除非碰到很複雜、靜態的效果才能使用。會導致離屏渲染。

4.圖片大小

儘量保持圖片的大小合適,以及圖片的格式 GPU所支持。如果圖片偏大偏小,都需要GPU額外的去計算處理造成GPU的消耗,從而增加處理時間。
而圖片的格式如果GPU 不支持的話同樣需要GPU去做額外的轉化,消耗大量的時間。

發佈了24 篇原創文章 · 獲贊 19 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章