自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

特效側(cè)用戶體驗優(yōu)化實戰(zhàn) —— 包體積篇

原創(chuàng) 精選
移動開發(fā)
抖音目前由多條業(yè)務(wù)線組成,每條業(yè)務(wù)線都類似中臺的角色,特效中臺是抖音其中一環(huán);目前,特效由 effect 和 lab 聚合為EffectSDK,作為一條獨(dú)立業(yè)務(wù)線結(jié)算包體積在抖音中的占比。

?1 特效包體積之于抖音

1.1 一句話解釋包體積是什么?

包體積主要指的是應(yīng)用安裝包大小的體積,比如 App Store 里的安裝包顯示的安裝大小。

1.2 為什么要優(yōu)化包體積?

隨著應(yīng)用的能力更新迭代,應(yīng)用安裝包體積將逐步增大,用戶下載應(yīng)用消耗流量產(chǎn)生資費(fèi)進(jìn)一步增長,用戶下載意愿會相對下降;另一方面,隨著包體積增大,安裝應(yīng)用的時間會相對變長,影響用戶使用感受;對于ROM較小的低端手機(jī),應(yīng)用解壓后內(nèi)存占用更大,部分手機(jī)管家會提示內(nèi)存不足提示卸載,直接影響用戶使用。

1.3 特效側(cè)在抖音里的包體積貢獻(xiàn)

抖音目前由多條業(yè)務(wù)線組成,每條業(yè)務(wù)線都類似中臺的角色,特效中臺是抖音其中一環(huán);目前,特效由 effect 和 lab 聚合為EffectSDK,作為一條獨(dú)立業(yè)務(wù)線結(jié)算包體積在抖音中的占比。

1.4 特效側(cè)的包體積組成

EffectSDK 的包體積由兩方面組成:二進(jìn)制文件(即可執(zhí)行文件)、其他資源文件(圖片、配置文件等)。二進(jìn)制文件主要是由代碼生成的可執(zhí)行文件,資源文件指代的如內(nèi)置的模型文件、素材文件、配置文件等。

作為中臺,特效 EffectSDK 中二進(jìn)制代碼占用了絕大多數(shù)體積。與抖音、頭條等應(yīng)用做包體積優(yōu)化思路不同,特效在資源壓縮等部分能做得比較少;由于特效是作為中臺對抖音進(jìn)行業(yè)務(wù)支持,通過庫的形式提供特效能力,在無用資源刪除、無用代碼去除、代碼優(yōu)化上有較大空間。因此,特效側(cè)性能優(yōu)化主要側(cè)重于在支持多功能的基礎(chǔ)上盡量減小包體積,提升代碼質(zhì)量,實現(xiàn)代碼效率與代碼體積的平衡。

圖片

2 包體積優(yōu)化的背景知識

特效側(cè)在抖音里的能力由 C++ 代碼編寫支撐,編譯后生成靜態(tài)庫,最后鏈接至可執(zhí)行文件中。從代碼至二進(jìn)制文件的過程中,由編譯器為我們做好預(yù)處理、編譯、匯編、鏈接等過程,最后 Android 端生成 ELF 格式文件,iOS 端生成 Mach-O 文件。ELF 格式的文件有四種,包括可重定位文件(Relocatable File)、可執(zhí)行文件(Executable File)、共享目標(biāo)文件(Shared Object File)、核心轉(zhuǎn)儲文件(Core Dump File),其中,共享目標(biāo)文件,即 xxx.so 文件,包含可在兩種上下文中鏈接的代碼和數(shù)據(jù),鏈接編輯器可以將它和其它可重定位文件和共享目標(biāo)文件一起處理,生成另外一個目標(biāo)文件;另外,動態(tài)鏈接器(Dynamic Linker)可能將它與某個可執(zhí)行文件以及其它共享目標(biāo)一起組合,創(chuàng)建進(jìn)程映像。特效側(cè)即以共享目標(biāo)文件(libeffect.so)的形式做好抖音特效拍攝能力支撐。

圖片

圖片

圖片

由于ELF文件參與程序的鏈接與執(zhí)行,通常有兩種視圖方式:一種是鏈接視圖,一種是執(zhí)行視圖(下述左圖);編譯器和鏈接器會按照鏈接視圖,以節(jié)區(qū)(section)為單位,按節(jié)區(qū)頭部表(section header table)形成節(jié)區(qū)的集合;加載器將按照執(zhí)行視圖,將文件以段(segment)為單位,按照程序頭部表(program header table)將其視為段的集合。通常,可重定位文件(xxx.o)將包含節(jié)區(qū)頭部表,可執(zhí)行文件(xxx.exe)將包含程序頭部表,共享目標(biāo)文件(xxx.so)兩者都包含。

圖片

圖片

下面是使用 binutils 工具查看 effect_sdk.so 中的 section 部分信息:

$ greadelf -h libeffect_sdk.so
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0x0
Start of program headers: 64 (bytes into file)
Start of section headers: 22954168 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
$ greadelf -S libeffect_sdk.so
There are 29 section headers, starting at offset 0x15e40b8:

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.androi[...] NOTE 0000000000000200 00000200
0000000000000098 0000000000000000 A 0 0 4
[ 2] .note.gnu.bu[...] NOTE 0000000000000298 00000298
0000000000000024 0000000000000000 A 0 0 4
[ 3] .dynsym DYNSYM 00000000000002c0 000002c0
00000000000107e8 0000000000000018 A 4 1 8
[ 4] .dynstr STRTAB 0000000000010aa8 00010aa8
000000000001b0f9 0000000000000000 A 0 0 1
[ 5] .gnu.hash GNU_HASH 000000000002bba8 0002bba8
000000000000347c 0000000000000000 A 3 0 8
[ 6] .hash HASH 000000000002f028 0002f028
0000000000004c18 0000000000000004 A 3 0 8
... ...
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)

通常每個節(jié)區(qū)(section)負(fù)責(zé)不同的功能,存儲在不同的位置,節(jié)區(qū)的大小是代碼編譯后大小的反饋。說到底,特效側(cè)最終的包體積由 section 和 headers 的大小共同決定。優(yōu)化包體積,即是優(yōu)化代碼的編寫效率、編譯方式,減少各個節(jié)區(qū)的大小。

int gInitVar = 24;  //-- .data section
int gUninitedVar; //-- .bss section
void func(int i)
{
printf("%d\n", i); //-- .text section
}
int main(void)
{
static int sVar = 23; //-- .data section
static int sVar1; //-- .bss section
int a = 1;
int b;
func(sVar + sVar1 + a + b); //-- .text section
return 0;
}

圖片

3 包體積優(yōu)化技巧

在了解了基礎(chǔ)的包體積組成后,我們可以針對性的對編譯選項、代碼進(jìn)行調(diào)整,以優(yōu)化包體積。

iOS/Android 均可以通過優(yōu)化編譯選項來優(yōu)化代碼體積。整理了常用的一些。

3.1 編譯優(yōu)化

3.1.1 使用 Oz 替代 Os

編譯選項

  • 用-Oz?替代-Os
  • 示例:
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Oz")

3.1.2 減小 unused code 的體積

  • 編譯選項
  • -ffunction-sections
  • 把每個function放到自己的 COMDAT 段(COMDAT 段被多個目標(biāo)文件所定義的輔助段。該段的作用是將在多個已編譯模塊中重復(fù)的代碼和數(shù)據(jù)的邏輯塊組合在一起。COMDAT 在 C++ 的虛函數(shù)表和模板的編譯鏈接中,起著非常重要的作用。)
  • 支持 Linux/OS X,不支持windows
  • -fdata-sections
  • 為源文件中每個變量啟用一個 elf section 的生成
  • 示例:
  • set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -g")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -g")

鏈接選項

  • -Wl, --gc-sections( Android 端)

  • 當(dāng)編譯器選擇用-ffunction-sections, -fdata-sections編譯文件時,靜態(tài)的庫體積將增大,此時調(diào)用-Wl, --gc-sections,能消除dead段沒有用到的code和data的體積。
  • -dead_strip( iOS 端)

示例:

  • set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections")

3.1.3 開啟鏈接優(yōu)化

編譯選項

  • -flto Oz

鏈接選項

  • -O3 -flto

  • lto為 link-time optimization ,在編譯和鏈接時需要同時開啟。編譯時,會將各文件寫入專有的 section ,再鏈接時將它倆視為同一單元進(jìn)行轉(zhuǎn)換和優(yōu)化。但有個缺點,會在一定程度上拖慢編譯速度

  • 注意:lto編譯時可以和-Oz共存,但鏈接時只能跟O1/O2/O3共存,無法和Oz/Os共存,如果同時開啟了,將會報下面的錯誤:

  • $ clang -Os -fuse-ld=lld -flto test.c
    ld.lld: error: -plugin-opt=Os: number expected, but got 's'
    clang-9: error: linker command failed with exit code 1 (use -v to see invocation)
    $ clang -Oz -fuse-ld=lld -flto test.c
    ld.lld: error: -plugin-opt=Oz: number expected, but got 'z'
    clang-9: error: linker command failed with exit code 1 (use -v to see invocation)

示例:

  • if (NOT DEFINED ENV{DISABLE_LTO})
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto -fPIC")
    endif()
  • set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl, --gc-sections -fuse-ld=gold -Wl, --icf=safe -O2 -flto")
    if (NOT DEFINED ENV{DISABLE_LTO})
    message(STATUS "DISABLE_LTO=$ENV{DISABLE_LTO} +++ LTO enabled")
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold -Wl, --icf=safe -O2 -flto")
    else()
    message(STATUS "DISABLE_LTO=$ENV{DISABLE_LTO} +++ LTO disabled")
    endif()

3.1.4 關(guān)閉 exception 和 rtti

  • 編譯選項
  • -fno-exceptions
  • 當(dāng)開啟-fno-rtti開關(guān)時,將禁用 rtti 機(jī)制,減小包體積。
  • -fno-rtti
  • 當(dāng)開啟-fno-exceptions 開關(guān)時,將禁用 exception 機(jī)制,減小包體積。
  • 上述兩種屬于比較激進(jìn)的做法,同時也需要代碼配合,但在能保障代碼正確性和穩(wěn)定性的情況下,也能較大幅度的優(yōu)化包體積。目前特效側(cè)已經(jīng)盡量避免不必要的 rtti 和 exception 機(jī)制。
  • 注意:缺少異常處理和 rtti ,需要 coder 能寫出更高品質(zhì)的代碼。
  • -fno-excpetion需要配合一定的代碼修改:
  • if(!running)
    {
    // throw std::runtime_error("runtime error") // 不可用
    errCode = getRuntimeError();
    return errCode;
    }
  • -fno-rtti也需要配合一定代碼修改:
  • DerivedTarget &target = getTargetPtr();
    // dynamic_cast<BasicTarget *>(target.get())->fun(); // 不可再用
    static_cast<BasicTarget *>(target.get())->fun();

3.1.5 自動刪除引入的靜態(tài)庫中的符號

  • 鏈接選項
  • -Wl,--exclude-libs,ALL(Android端)
  • 刪除庫"ALL"里自動導(dǎo)出的符號(這里ALL替換成不需要的庫名,比如--exclude-libs lib,lib,...)
  • 注意:iOS 不支持這個鏈接選項,因為 macOS 將--exclude-libs作為默認(rèn)選項

(如果 iOS 要往庫里引入符號,需要手動開啟-reexport-l$(UR_LIB)選項)

if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND ANDROID)
foreach(LIB ${LINK_LIB_LIST})
set(CMAKE_SHARED_LINKER_FLAGS "{CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,lib{LIB}.a")
endforeach()
endif()

目前特效在 Android 端均采用了這個選項。

3.1.6 減少符號表

  • -fvisibility=hidden
  • 可隱藏符號的可見性,防止符號沖突,同時減小包體積。
  • 注意:出錯時上層可能無法第一時間定位問題
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -g")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -g")

目前特效側(cè)均使用-fvisibility=hidden

3.1.7 動態(tài)鏈接c++

動態(tài)鏈接 libstdc++ 庫,避免增大庫文件。

3.2 代碼優(yōu)化

一句話總結(jié):代碼量越少,包體積越小,從經(jīng)驗來看100行代碼大概占用1~5K體積;超出這個行/體積 比,代碼肯定有問題。

3.2.1 不要有無效的判斷邏輯( if...else... )

可以采用表驅(qū)動的方法實現(xiàn) if else ,減少不必要的代碼引用。

3.2.2 減少模板展開、宏展開

模板展開非常占據(jù)體積,尤其是對于同一種形式的代碼,template 會擴(kuò)充為多個不同的類。此時最好把公共的部分提取出來,聲明為一個 static method。

如下面的綁定變量的方法:

template <typename T>
static void bindArgs(const Demo& d, T func)
{
auto m = createFun(func);
m->mName = d.name
for (auto i = 0; i < m->getArgc(); ++i)
{
if (i < d.args.size())
m->mArgTypes[i].name = d.args[i];
}
}

template <typename T>
static void bindArgs(const Demo& d, T func, const Var& arg1)
{
auto m = createFun(func);
if (!m)
return;
m->mValues.push_back(arg1);
for (auto i = 0; i < m->getArgc(); ++i)
{
if (i < d.args.size())
m->mArgTypes[i].name = d.args[i];
}
}

// static void bindArgs(const Demo& d, T func, const Var& arg1, const Var& arg2)
// {

可修改為:

// bindArgs 提取出來
static void bindArgs(const Demo& d, Fun* m)
{
for (auto i = 0; i < m->getArgc(); ++i)
{
if (i < d.args.size())
m->mArgTypes[i].name = d.args[i];
}
}

template <typename T>
static void bindArgs(const Demo& d, T func)
{
auto m = createFun(func);
m->mName = d.name;
bindArgs(d, m);
}

template <typename T>
static void bindArgs(const Demo& d, T func, const Var& arg1)
{
auto m = createFun(func);
if (!m)
return;
m->mValues.push_back(arg1);
bindArgs(d, m);
}

3.2.3 避免不必要的 stl/std 使用

比如,部分回調(diào)可以使用函數(shù)指針:std::function <>作為一個 class ,它的體積成本必然比 void * fun 這樣一個函數(shù)指針要來的高;

// using FunInstantiate = std::function<FunInterface*()>; // 不再使用
using FunInstantiate = FunInterface*(*)();

比如,常量字符串引用時可以采用 const char* 類型,避免編譯器調(diào)用隱式拷貝構(gòu)造;

// void DemoClass::fun(const std::string &name, const DemoPtr &demoPtr) // 不再使用
void DemoClass::fun(const char* name, const DmoePtr &demoPtr)
{
//...
}

3.2.4 頭文件不要出現(xiàn) const、static 變量的定義

頭文件中 const / static 型的變量,會被引入至對應(yīng)的 cpp 文件,相當(dāng)于每一份.o 都引入了一長串常量字符串。

3.2.5 不要出現(xiàn)大的數(shù)組

大的數(shù)組會占用數(shù)組大小的體積。

3.2.6 減少不必要的虛基類/虛函數(shù)

// class Child : virtual public Parent // 不再使用
class Child : public Parent
{
//...
}

4 包體積監(jiān)測工具

4.1 為什么要做包體積監(jiān)測工具

抖音每個版本都會有非常多的新能力更新?lián)Q代,每次更新每個需求均會導(dǎo)致包體積的變更。為了能更好的監(jiān)測包體積的變化、確認(rèn)包體積增長的原因,提升 ROI ,引入包體積監(jiān)測工具,更直觀的確認(rèn)包體積增長原因,攔截異常增長,輸出每個每個需求帶來的包體積增長大小、包體積增長原因,及時給出包體積告警、定位異常增量 case ,減緩包體積增長,推動業(yè)務(wù)優(yōu)化。

圖片

4.2 如何進(jìn)行包體積監(jiān)測

特效側(cè)目前使用的包體積監(jiān)測工具來源于 google 的開源二進(jìn)制文件體積分析工具 bloaty ,用于分析二進(jìn)制文件(xxx.exe, xxx.bin)、共享目標(biāo)文件(xxx.so)、對象文件(xxx.o)和靜態(tài)庫(xxx.a),支持ELF\Mach-O\WebAssembly 格式。它能梳理出文件中各部分的體積組成,拆分出各個 section 大小,結(jié)合symbol信息,反推出各方法、源文件的包體積大小。

以特效側(cè) libeffect_sdk.so 為例,對 .so 文件進(jìn)行組件單元、源文件分析,截取部分輸出結(jié)果:

FILE SIZE   
--------------
10.3% 2.25Mi [section .rela.dyn]
7.2% 1.58Mi [section .rodata]
7.2% 1.57Mi Bindings.cpp
3.9% 877Ki [section .data.rel.ro]
2.0% 445Ki [section .text]
1.9% 418Ki [section .gcc_except_table]
1.0% 213Ki base/EffectManager.cpp
0.7% 149Ki bef_info_sticker_api.cpp
0.6% 140Ki base/RenderManager.cpp
0.6% 138Ki Runtime/Engine/Foundation/Bindings.cpp
...

利用上述工具,即可較為清晰的定位各文件帶來的包體積增長。

4.2.1 包體積監(jiān)控工具工作流程

包體積監(jiān)測工具是當(dāng)前特效需求上車前必過的一環(huán)。所有需求在 MR(merge request)提出、CI 打包完成后都會經(jīng)過包體積的檢查,僅包體積增量符合預(yù)期的需求允許跟版合入,所有包體積增量與需求一一對應(yīng),記錄在案。

圖片

4.2.2 包體積監(jiān)測工具的分析能力

包體積分析工具支持單個文件分析和版本迭代對比分析。

對于單文件分析,由于特效側(cè)主要通過 .so 文件進(jìn)行交付,在每個 MR 打包完成后,工具將自動獲取對應(yīng)的 .so 文件和 .so.symbol 文件后,對庫文件的包體積組成、包體積來源進(jìn)行分析,輸出所有方法函數(shù)、節(jié)區(qū)(section)、編譯單元(xxx.cpp)帶來的包體積大小,確認(rèn)大小后通過關(guān)鍵字匹配確認(rèn)包體積的增量來源模塊,給出最后的各模塊單元、編譯單元的包體積 profile 。

另一方面,由于特效側(cè)能力總是通過需求更新迭代的,每次有實質(zhì)性的需求提交時,將會對比上一版本與當(dāng)前版本的包體積差異,做好每個版本需求帶來的增量來源記錄。當(dāng)版本比對結(jié)果帶來的增量超過預(yù)期值時,將調(diào)起通訊 api ,將包體積超標(biāo)信息發(fā)出進(jìn)行報警。

圖片

圖片

4.2.3 包體積數(shù)據(jù)記錄本

所有需求的包體積增量將記錄在包體積記錄本中:當(dāng)服務(wù)收到需求事件時,將調(diào)用 bits/meego 接口,請求需求信息和包大小預(yù)設(shè) exp_pack_size 增量寫入 mr_pkg_size 表;等到本地出包完成后,實際的包大小增量 real_pack_size 將被記錄入 mr_pkg_size 表,并將預(yù)期值與實際增量進(jìn)行對比。

最終,所有的包體積增量與歷史的需求增量來源被記錄在案,并通過表查詢接口,在網(wǎng)頁端可根據(jù)需求名 / 時間段 / 分支名 / commit id 等條件按圖索驥,確認(rèn)包體積增長來源。

圖片

5 總結(jié)

經(jīng)過上述代碼體積優(yōu)化積累、實時體積監(jiān)控、需求增量落實到人三位一體,控制特效側(cè)包體積有序增長,提升代碼效能。

責(zé)任編輯:未麗燕 來源: 字節(jié)跳動技術(shù)團(tuán)隊
相關(guān)推薦

2023-07-19 22:17:21

Android資源優(yōu)化

2010-08-03 15:44:08

FlexBuilder

2016-07-22 10:28:47

業(yè)務(wù)運(yùn)維APP

2010-03-18 20:19:16

2023-09-18 23:50:25

二進(jìn)制文件裁剪Layout

2011-06-30 17:13:17

SEO用戶體驗

2022-06-01 09:18:37

抖音ReDex算法優(yōu)化

2023-03-15 21:38:43

短視頻服務(wù)器

2021-01-12 10:16:42

CSS 容器優(yōu)化滾動

2022-10-28 13:41:51

字節(jié)SDK監(jiān)控

2010-05-18 10:09:38

前端優(yōu)化

2012-04-18 09:22:40

Chrome for

2014-03-11 10:35:11

監(jiān)控寶升級驅(qū)動

2012-01-17 10:20:25

Web App最佳實踐用戶體驗

2011-01-13 16:11:13

silverlightwebasp.net

2016-09-01 09:39:20

攜程無線

2025-02-08 10:54:02

2024-03-11 09:41:15

ApacheEchartsUI

2012-11-05 10:01:32

2012-08-14 10:31:28

面試
點贊
收藏

51CTO技術(shù)棧公眾號