其他
百度APP Android包体积优化实践(二)Dex行号优化
The following article is from 百度App技术 Author 政, 迪, 阳
01
02
丨2.1 Dex DebugInfo
DBG_FIRST_SPECIAL = 0x0a // the smallest special opcode
DBG_LINE_BASE = -4 // the smallest line number increment
DBG_LINE_RANGE = 15 // the number of line increments represented
adjusted_opcode = opcode - DBG_FIRST_SPECIAL
line += DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE)
address += (adjusted_opcode / DBG_LINE_RANGE)
丨2.2 DebugInfo 使用场景
// art/runtime/native/java_lang_Throwable.cc
static jobjectArray Throwable_nativeGetStackTrace(JNIEnv* env, jclass, jobject javaStackState) {
...
ScopedFastNativeObjectAccess soa(env);
return Thread::InternalStackTraceToStackTraceElementArray(soa, javaStackState); // 将StackTrace转化为StackTraceElement[]
}
// art/runtime/thread.cc
jobjectArray Thread::InternalStackTraceToStackTraceElementArray(
const ScopedObjectAccessAlreadyRunnable& soa,
jobject internal,
jobjectArray output_array,
int* stack_depth) {
...
// 遍历StackTrace
for (uint32_t i = 0; i < static_cast<uint32_t>(depth); ++i) {
ObjPtr<mirror::ObjectArray<mirror::Object>> decoded_traces = soa.Decode<mirror::Object>(internal)->AsObjectArray<mirror::Object>();
const ObjPtr<mirror::PointerArray> method_trace = ObjPtr<mirror::PointerArray>::DownCast(decoded_traces->Get(0));
// 从StackTrace中获取 ArtMethod与对应pc
ArtMethod* method = method_trace->GetElementPtrSize<ArtMethod*>(i, kRuntimePointerSize);
uint32_t dex_pc = method_trace->GetElementPtrSize<uint32_t>(i + static_cast<uint32_t>(method_trace->GetLength()) / 2, kRuntimePointerSize);
// 根据 ArtMethod与对应pc 创建 StackTraceElement对象
const ObjPtr<mirror::StackTraceElement> obj = CreateStackTraceElement(soa, method, dex_pc);
soa.Decode<mirror::ObjectArray<mirror::StackTraceElement>>(result)->Set<false>(static_cast<int32_t>(i), obj);
}
return result;
}
static ObjPtr<mirror::StackTraceElement> CreateStackTraceElement(
const ScopedObjectAccessAlreadyRunnable& soa,
ArtMethod* method,
uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) {
...
// 获取pc对应的代码行号
int32_t line_number;
line_number = method->GetLineNumFromDexPC(dex_pc);
...
}
// ... art_method.h -> code_item_accessors.h
// 当遍历debugInfo过程中,pc满足条件(大于等于StackTrace记录的pc)时,返回对应的行号
inline bool CodeItemDebugInfoAccessor::GetLineNumForPc(const uint32_t address,
uint32_t* line_num) const {
return DecodeDebugPositionInfo([&](const DexFile::PositionInfo& entry) {
if (entry.address_ > address) {
return true;
}
*line_num = entry.line_;
return entry.address_ == address;
});
}
// code_item_accessors.h -> dex_file.h
// 遍历dex中对应的debugInfo
bool DexFile::DecodeDebugPositionInfo(const uint8_t* stream,
const IndexToStringData& index_to_string_data,
const DexDebugNewPosition& position_functor) {
PositionInfo entry;
entry.line_ = DecodeDebugInfoParameterNames(&stream, VoidFunctor());
for (;;) {
uint8_t opcode = *stream++;
switch (opcode) {
case DBG_END_SEQUENCE:
return true; // end of stream.
case DBG_ADVANCE_PC:
entry.address_ += DecodeUnsignedLeb128(&stream);
break;
case DBG_ADVANCE_LINE:
entry.line_ += DecodeSignedLeb128(&stream);
break;
...
// 其他event类型处理,与局部变量、源文件相关
...
default: {
int adjopcode = opcode - DBG_FIRST_SPECIAL;
entry.address_ += adjopcode / DBG_LINE_RANGE;
entry.line_ += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE);
break;
}
}
}
}
03
丨3.1 极限优化方案
编译选项
-g:lines // 生成LineNumberTable
-g:vars // 生成LineVariableTable
-g:source // 生成SourceFile
-g:none // 不生成任何debugInfo
Proguard规则[4]
-keepattributes SourceFile // 保留SourceFile
-keepattributes LineNumberTable // 保留LineNumberTable
丨3.2 映射优化方案
// debug_info_item 相等判断逻辑(伪代码)
public boolean equals(DebugInfoItem debugInfoItem) {
return this.startLine == debugInfoItem.startLine
&& this.parameters.equals(debugInfoItem.parameters)
&& this.events.equals(debugInfoItem.events);
}
// debug_event 相等逻辑判断(伪代码)
public boolean equals(DebugEvent event) {
return this.type == event.type
&& this.value == event.value;
}
重载方法行号区间重叠问题
// 方法行号映射为:
com.example.myapplication.MethodOverloadSample.test():
1->21
2->22
com.example.myapplication.MethodOverloadSample.test(String msg):
1->34
2->35
...
// 收集映射后方法堆栈:
...
at com.example.myapplication.MethodOverloadSample.test(MethodOverloadSample.java:2)
...
丨3.3 支付宝行号优化方案
方案一
方案二
丨3.4 R8行号优化方案[6]
04
丨4.1客户端行号优化
debug_info_item 变量控制
行号映射
类名1:
方法描述符1:
映射后行号闭区间1 -> 原行号1
映射后行号闭区间2 -> 原行号2
方法描述符1:
映射后行号闭区间1 -> 原行号1
映射后行号闭区间2 -> 原行号2
类名2:
...
com.baidu.searchbox.Application:
void onCreate(android.os.Bundle):
[1000-1050] -> 20
[1051-2000] -> 22
void onCreate():
[3000-3020] -> 30
[3021-3033] -> 31
void onStop():
[1000-1050] -> 50
[1051-2000] -> 55
com.baidu.searchbox.MainActivity:
void onResume():
[1000-1050] -> 100
兼容 R8 行号优化
工具使用
体积优化效果
丨4.2 性能平台行号映射还原
性能平台整体架构图
映射文件解析服务
APP_版本_com.baidu.searchbox.Application.onCreate:
[1000-1050] -> 20
[1051-2000] -> 22
[3000-3020] -> 30
[3021-3033] -> 31
多级缓存系统
设计和实现中关键问题的解决
05
END