ART深入浅出2 -- 认识和了解Runtime Options

来源:互联网 发布:oracle数据库安装 编辑:程序博客网 时间:2024/06/07 15:51

本文基于Android 7.1,不过因为从BSP拿到的版本略有区别,所以本文提到的源码未必与读者找到的源码完全一致。本文在提供源码片断时,将按照 <源码相对android工程的路径>:<行号> <类名> <函数名> 的方式,如果行号对不上,请参考类名和函数名来找到对应的源码。


RuntimeOptions到RuntimeArgumentMap

在JNI_CreateJavaVM函数中,JavaVMOption对象被转换为RuntimeOptions对象。
RuntimeOptions实际上只是一个vector定义:
art/runtime/parsed_options.h:38
typedef std::vector<std::pair<std::string, const void*>> RuntimeOptions;
内部的parie对象,第一个参数保存的是放进去的字符串数据,第二个是附加参数。一般附加参数不用。

art/runtime/java_vm_ext.cc:947 JNI_CreateJavaVM
  for (int i = 0; i < args->nOptions; ++i) {    JavaVMOption* option = &args->options[i];    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));  }
当数据填充完成后,调用Runtime::Create方法,传递给Runtime,用于创建Runtime。
art/runtime/runtime.cc:486 Runtime::Create
bool Runtime::Create(const RuntimeOptions& raw_options, bool ignore_unrecognized) {  RuntimeArgumentMap runtime_options;  return ParseOptions(raw_options, ignore_unrecognized, &runtime_options) &&      Create(std::move(runtime_options));}
注意到return语句,先调用ParseOptions, 将RuntimeOptions转换为RuntimeArugmentMap。
本章要重点分析的就是这个ParseOptions函数。
首先看看RuntimeArugmentMap是什么结构。

Runtime::ParseOptions

该函数解析参数的入口。Runtime::ParseOptions调用 ParsedOptions:Parse函数,再调用 ParsedOptions::DoParse函数。
DoParse函数,主体有两个函数:MakeParse和RuntimeParser::Parse函数。

首先,看看MakeParse函数。

MakeParse函数

这个函数用了非常复杂的C++模板,生成一个解析数据结构。因为内容太多,我摘录了关键部分,进行解说。
请先看下面的代码
runtime/parsed_options.cc:61 ParsedOptions::MakeParser
std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognized) {  using M = RuntimeArgumentMap;  std::unique_ptr<RuntimeParser::Builder> parser_builder =      std::unique_ptr<RuntimeParser::Builder>(new RuntimeParser::Builder());  parser_builder->       Define("-Xzygote")          .IntoKey(M::Zygote)      .Define("-help")          .IntoKey(M::Help)      .....          .WithType<ParseStringList<':'>>()  // std::vector<std::string>, split by :          .IntoKey(M::BootClassPathLocations)      .Define({"-classpath _", "-cp _"})          .WithType<std::string>()          .IntoKey(M::ClassPath)      .Define("-Ximage:_")          .WithType<std::string>()          .IntoKey(M::Image)      ....      .Define("-D_")          .WithType<std::vector<std::string>>().AppendValues()          .IntoKey(M::PropertiesList)      ....      .Define({"-XX:EnableHSpaceCompactForOOM", "-XX:DisableHSpaceCompactForOOM"})          .WithValues({true, false})          .IntoKey(M::EnableHSpaceCompactForOOM)      ....      .Define("-XX:HeapTargetUtilization=_")          .WithType<double>().WithRange(0.1, 0.9)      ....      .Define("-Xint")          .WithValue(true)          .IntoKey(M::Interpret)      ....      .Define("-Xmx_")          .WithType<MemoryKiB>()          .IntoKey(M::MemoryMaximumSize)      .....      .Define("-XX:DumpNativeStackOnSigQuit:_")          .WithType<bool>()          .WithValueMap({{"false", false}, {"true", true}})          .IntoKey(M::DumpNativeStackOnSigQuit)      .....      .Define("-XX:LargeObjectSpace=_")          .WithType<gc::space::LargeObjectSpaceType>()          .WithValueMap({{"disabled", gc::space::LargeObjectSpaceType::kDisabled},                         {"freelist", gc::space::LargeObjectSpaceType::kFreeList},                         {"map",      gc::space::LargeObjectSpaceType::kMap}})          .IntoKey(M::LargeObjectSpace)      .....      .Ignore({          "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", .....          "-Xjitdisableopt", "-Xjitsuspendpoll", "-XX:mainThreadStackSize=_"})      .IgnoreUnrecognized(ignore_unrecognized);  // TODO: Move Usage information into this DSL.  return std::unique_ptr<RuntimeParser>(new RuntimeParser(parser_builder->Build()));}

该函数用了一些模板函数,创建了一个解析器。Define函数创建一个特定解析器,特殊符号'_'表示匹配剩余部分字符串。 WithType函数指定'_'匹配部分需要转换为的对象;WithValueMap函数定义了字符串与最终值之间的映射关系。最后IntoKey函数建立和一个key的关联。
这样,解析完成后,结果就存储在 RuntimeArgumentMap对象中,而key正是IntoKey指定的。

在理解之前,先了解几个类:
  • CmdlineParser::Builder   创建解析器的builder
  • UntypedArgumentBuilder  创建一个没有指定类型的参数Builder
  • template <typename TArg>  ArgumentBuilder 创建一个指定类型的参数builder
首先,Builder.Define函数创建一个UntypedArgumentBuilder实例
其次,UntypedArgumentBuilder.WithType函数创建一个ArgumentBuilder实例
第三,为ArgumentBuilder添加一系列的解析信息
最后,调用ArugmentBuilder.IntoKey,返回之前的Builder。
这里需要注意的是,UntypedArgumentBuilder和ArugmentBuilder其实都是临时对象,而Builder的实例则是一直存在的。
这样完成一个链式调用,可以方便的增加若干各模块。

ArgumentBuilder类支持若干种解析方法:
  • WithRange, 针对数值类型,指出数值的值域
  • WithValue: 如果一个参数被设置,就设置一个对应的值,否则就不设置。比如Define("-Xint").WithValue(true) 。 这里因为已经给出了具体value值,所以它的type可以确定,也就无需先用WithType产生一个ArgumentBuilder了,UntypedArgumentBuilder就能做到;
  • AppendValues 将多个参数的值收集到一个key下面。比如.Define("-D_").WithType<std::vector<std::string>>().AppendValues() 把多个-D定义的信息都放在一个模块内部。可以看到,这个属性是用vector数组保存的对象。
  • WithValues: 同WithValue,只是接受一个数组,建立一个映射值,例如.Define({"-XX:EnableHSpaceCompactForOOM", "-XX:DisableHSpaceCompactForOOM"})   .WithValues({true, false}) 如何设置第一个参数-XX:EnableHSpaceCompactForOOM,则取true,否则取false
  • WithValueMap: 将取得的值与最终建立一个映射,如 
       Define("-Xprofile:_")          .WithType<TraceClockSource>()          .WithValueMap({{"threadcpuclock", TraceClockSource::kThreadCpu},                         {"wallclock",      TraceClockSource::kWall},                         {"dualclock",      TraceClockSource::kDual}})
                "_"所匹配的值,将和map列出的值一一对应。


到目前为止,就可以了解参数解析的基本用法,并且能够修改参数了。如果您不想了解它的工作原理,您可以结束本章阅读了。如果本着死磕精神,我们就深入了解下,这种神奇的用法是如何用模板实现的。

Parse的基本原理讲解

在正式了解Parse的实现过程之前,我们先想想应该怎么实现。
根据MakeParser函数的写法,我们可以推测出开发者的意图是这样的:
  1. 建立一个解析器数组,数组内的每个解析器,都解析一个或者多个关联的参数
  2. 解析器有一个匹配器,通过字符串比较,找出要解析的参数,很多情况下,要解析参数前面的名称部分
  3. 解析器要获取参数值部分,并把参数值转换为各种内部数据结构
  4. 参数解析器还带有一个Key,将此Key和最终结果,关联存储到一个Map表中
  5. 内部的数据结构包括:
    • true/false
    • 数值,可能带有值域
    • 枚举值
    • 字符串列表
    • 带单位的数值,比如最大/最小堆大小,以MB为单位
  6. 一个Map表存储Key-Value值
OK,现在,我们试着用伪代码来表示这个想法,现在定义各个结构的名称:
  1. CmdlineParser: 管理解析器数组的类,里面有一个参数解析器数组 argument_parsers_
  2. ArgumentParser: 参数解析器类, 有函数:
    1. match: 进行参数匹配; 
    2. fetchValue:从参数中提取值
    3. covertValue:将参数值转为内部程序可以直接读取的值
    4. key:得到关联的访问key
  3. OptionsMap 保存结果的表,将key和value保存起来
参数列表是一个std::vector<string> arguments对象,那么伪代码可以这么写
CmdlineParser parser; //解析对象OptionsMap    optionsMap; //保存结果for (auto it = arguments.begin(); it != argments.end(); ++it) {    stirng arg = *it;    for (auto it_parser = parser.argument_parsers_.begin(); it_parser != parser.argument_parsers_.end(); ++it_parser) {        if (it_parser->match(arg)) {            string str_value = it_parser->fetchValue();            auto value = it_parser->covertValue(str_value);            optionsMap.put(it_parser->key(), value);        }    }}

下面,我们就一一的对应ART中的源码,看看这些想法究竟是怎么实现的?

CmdlineParser

MakeParser函数最后返回的是一个RuntimeParser的指针对象,RuntimeParser就是CmdlineParser对象:
art/runtime/parsed_options.cc:53
using RuntimeParser = CmdlineParser<RuntimeArgumentMap, RuntimeArgumentMap::Key>;
其中,RuntimeArgumentMap就是上面说的OptionsMap对象,RuntimeArgumentMap::Key是和它对应的key对象。
查看CmdlineParser类的定义,发现它的成员有
art/cmdline/cmdline_parser.h:41
template <typename TVariantMap,          template <typename TKeyValue> class TVariantMapKey>struct CmdlineParser {  .... //604private:  bool ignore_unrecognized_ = false;  std::vector<const char*> ignore_list_;  std::shared_ptr<SaveDestination> save_destination_;  std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>> completed_arguments_;};
这个类的定义很长,有600多行,但是,大部分是内部类的定义和实现函数,它的核心数据就是上面这些。
其中ignore_list_就是忽略的参数列表,这个没什么。
save_destination_,这是用来想OptionsMap存储数据的,我们也不管它。
completed_aruments_ 就是最重要的参数解析器的列表。
看到,这个列表是CmdlineParseArgumentAny对象的指针数组,为什么是指针?因为需要处理很多种不同的数据类型,只能通过虚函数来实现,所以要做成指针数组,而不能是对象数组。

TokeRange

首先介绍下TokeRange这个类。这个类辅助比较用的。因为下面进行比较时,会大量用到这个类,所以有必要先介绍它。
TokenRange类,先看看它的核心函数和成员
art/cmdline/token_range.h:35
struct TokenRange {  // Short-hand for a vector of strings. A single string and a token is synonymous.  using TokenList = std::vector<std::string>;.... //329  // Do a quick match token-by-token, and see if they match.  // Any tokens with a wildcard in them are only matched up until the wildcard.  // If this is true, then the wildcard matching later on can still fail, so this is not  // a guarantee that the argument is correct, it's more of a strong hint that the  // user-provided input *probably* was trying to match this argument.  //  // Returns how many tokens were either matched (or ignored because there was a  // wildcard present). 0 means no match. If the size() tokens are returned.  size_t MaybeMatches(const TokenRange& token_list, const std::string& wildcard) const { .... private: ....//418  const std::shared_ptr<std::vector<std::string>> token_list_;  const iterator begin_;  const iterator end_;};
token_list_其实就是一个 字符串数组,也就是arguments数组。begin_和end_保存是这个数组的开头和结尾,也就是说,TokenRange保存的是整个参数数组的一部分

因为参数解析器可能会同时解析几种参数,有几种情况:
  • 参数缩写,比如-classpath和-cp cp是classpath的缩写,两者都要识别
  • 互斥参数,比如-XX:EnableHSpaceCompactForOOM和-XX:DisableHSpaceCompactForOOM这种带有enable/dispable的参数
MaybeMatches函数返回的是有多少个匹配的token。因为包含了匹配字符"_",所以,有时候会出现匹配不精确的问题,比如 "-XAB_"和"-XABC_"这两种token都能匹配参数” -XABCDEF“,但是按照精确的原则,应该是匹配 "-XABC_"而不是"-XAB_"。

注:上面的说法是我按照注释,加上自己的猜想。但是根据MaybeMatches函数的实现,我感觉与注释的行为并不一致。开发者可能自己也糊涂了。实际上,java vm参数设计上不会出现上面举例的情况,反倒是开发者自己想多了。有兴趣的读者可以自行看代码了解。

CmdlineParseArgumentAny,template <typename TArg> CmdlineParseArgument

CmdlineParseArgumentAny是一个纯虚类,只是定义了解析器的接口。
关键的接口是:
  • MaybeMatches : 比较match的接口
  • ParseArgument : 提取参数并转换的函数
它的实现类就是 CmdlineParseArgument,这个类是一个模板类,必须指定具体类型才能实例化。
现在看看CmdlineParseArgument的实现。

CmdlineParseArgumentInfo类

CmdlineParseArgument的很多信息都是放在这个类中的,所有有必要先了解这个类的结构。
代码在art/cmdline/detail/cmdline_parse_argument_detail.h:88,不在列代码了,只列出关键的成员
  • std::vector<const char*> names_ :  这是Builder.Define函数给出的列表,用于做参数匹配等操作
  • bool using_blanks_:  names_里面是否至少有一个name包含了 "_", 有则true,否则为false
  • std::vector<TokenRange> tokenized_names_ 将names_ tokenized化后的结果,方便match
  • std::vector<TokenRange> simple_names_  除去掉"_"字符所有名字
  • appending_values_: 如果调用了AppendValues就会为true。这样会将所有的值放在同一key下
  • has_range_, TArg min_, max_; 调用了WithRange后,就会设置为true,并填充min,max的值
  • has_value_map_, std::vector<std::pair<const char*, TArg>> value_map_ 调用WithValueMap就会使用它们
  • has_value_list_, std::vector<TArg> value_list_,调用WithValues就会使用它们。主要value_list_和name_它们是用索引一一对应的,比如names_[0]就对应value_list_[0]
成员函数MaybeMatches用于实现对参数的匹配。

CmdlineParseArgument.MaybeMatches

见CmdlineParseArgumentInfo.MaybeMatches的实现。

CmdlineParseArgument.ParseArgument

该函数返回CmdlineResult对象,表示成功或者失败。如果失败了,这个对象内包含有失败的描述信息。
这个函数有两部分组成:1. 解析参数, 2. 转换值,由函数ParseArgumentSingle来实现。

参数解析的过程是这样的:
如果using_blanking为false,那么,直接调用ParseArgumentSingle,走value_list_的映射处理
否则,提取出"_"映射的部分,比如"foo:bar",提取出blank_value == "bar",然后调用ParseArgumentSingle

CmdlineParseArgument.ParseArgumentSingle

这个函数的实现看似很长,实际上,可以分为好几种情况进行处理。最终是调用SaveArgument函数,将值存入到OptionsMap中,这个函数就完成任务了。

hash_value_map_ == true

从value_map_中映射取得值
art/cmdline/detail/cmdline_parse_argument_detail.h:386 CmdlineParseArgument.ParseeArgumentSingle
        if (argument_info_.has_value_map_) {          for (auto&& value_pair : argument_info_.value_map_) {            const char* name = value_pair.first;            if (argument == name) {              return SaveArgument(value_pair.second);            }          }.......        }


has_value_list_ == true

从names_和value_list_中取得值
art/cmdline/detail/cmdline_parse_argument_detail.h:409 CmdlineParseArgument.ParseeArgumentSingle
        if (argument_info_.has_value_list_) {          size_t arg_def_idx = 0;          for (auto&& value : argument_info_.value_list_) {            auto&& arg_def_token = argument_info_.names_[arg_def_idx];            if (arg_def_token == argument) {              return SaveArgument(value);            }            ++arg_def_idx;          }     ....        }


appending_values_ == true

这有两个步骤:
1. 调用load_argument_方法(这是一个重载了operator()的类对象),获取已经加入的值,或者是新建一个值来加入
2. 调用type_parser.ParseAndAppend方法把值解析并加入进去
art/cmdline/detail/cmdline_parse_argument_detail.h:438 CmdlineParseArgument.ParseeArgumentSingle
        if (argument_info_.appending_values_) {          TArg& existing = load_argument_();          CmdlineParseResult<TArg> result = type_parser.ParseAndAppend(argument, existing);          assert(!argument_info_.has_range_);          return result;        }



最后

调用type_parser.Parse方法直接进行解析
art/cmdline/detail/cmdline_parse_argument_detail.h:447 CmdlineParseArgument.ParseeArgumentSingle
        CmdlineParseResult<TArg> result = type_parser.Parse(argument);


type_parser

type_parser这变量的类型,是UserTypeInfo,即
art/cmdline/detail/cmdline_parse_argument_detail.h:311 CmdlineParseArgument
using UserTypeInfo = CmdlineType<TArg>;
CmdLineType的定义是一个空的,但是,它有若干个半实例化的类型定义:
art/cmdline/cmdline_types.h:42
template <typename T>struct CmdlineType : CmdlineTypeParser<T> {};// Specializations for CmdlineType<T> follow:// Parse argument definitions for Unit-typed arguments.template <>struct CmdlineType<Unit> : CmdlineTypeParser<Unit> {  Result Parse(const std::string& args) {    if (args == "") {      return Result::Success(Unit{});  // NOLINT [whitespace/braces] [5]    }    return Result::Failure("Unexpected extra characters " + args);  }};template <>struct CmdlineType<JDWP::JdwpOptions> : CmdlineTypeParser<JDWP::JdwpOptions> {...  Result Parse(const std::string& options) {   ....  }  Result ParseJdwpOption(const std::string& name, const std::string& value,                         JDWP::JdwpOptions* jdwp_options) {    ....  }  static const char* Name() { return "JdwpOptions"; }};template <size_t Divisor>struct CmdlineType<Memory<Divisor>> : CmdlineTypeParser<Memory<Divisor>> {  using typename CmdlineTypeParser<Memory<Divisor>>::Result;  Result Parse(const std::string arg) { ....  }....};template <>struct CmdlineType<double> : CmdlineTypeParser<double> {  Result Parse(const std::string& str) { ....  }  static const char* Name() { return "double"; }};template <>struct CmdlineType<unsigned int> : CmdlineTypeParser<unsigned int> {  Result Parse(const std::string& str) { ....  }  static const char* Name() { return "unsigned integer"; }};template <>struct CmdlineType<std::vector<std::string>> : CmdlineTypeParser<std::vector<std::string>> {  Result Parse(const std::string& args) {    ...  }  Result ParseAndAppend(const std::string& args,                        std::vector<std::string>& existing_value) {...  }  static const char* Name() { return "std::vector<std::string>"; }};.....template <>struct CmdlineType<XGcOption> : CmdlineTypeParser<XGcOption> {  Result Parse(const std::string& option) {  // -Xgc: already stripped    .....  }  static const char* Name() { return "XgcOption"; }};....template<>struct CmdlineType<BackgroundGcOption>  : CmdlineTypeParser<BackgroundGcOption>, private BackgroundGcOption {  Result Parse(const std::string& substring) {....  }  static const char* Name() { return "BackgroundGcOption"; }};template <>struct CmdlineType<LogVerbosity> : CmdlineTypeParser<LogVerbosity> {  Result Parse(const std::string& options) {.....  }  static const char* Name() { return "LogVerbosity"; }};template <>struct CmdlineType<TestProfilerOptions> : CmdlineTypeParser<TestProfilerOptions> { ..... public:  Result ParseAndAppend(const std::string& option, TestProfilerOptions& existing) {   ....  }  static const char* Name() { return "TestProfilerOptions"; }  static constexpr bool kCanParseBlankless = true;};template<>struct CmdlineType<ExperimentalFlags> : CmdlineTypeParser<ExperimentalFlags> {  Result ParseAndAppend(const std::string& option, ExperimentalFlags& existing) { ....  }  static const char* Name() { return "ExperimentalFlags"; }};

当在WithType的时候给出什么参数, C++就会在这里挑选一个备用的半实例化CmdlineType类,而这个CmdlineType就知道如何解析该类型的参数了。
我没有列出具体的解析代码,是因为这些不重要,重要的是了解到它们的实现机理。

注意到,半实例化的CmdlineType都继承了CmdlineTypeParser类。这是因为,CmdlineType必须声明两个函数Parser和ParseAndAppend。但是,很多情况下,是不需要ParseAndAppend函数的。ART为了方便,就声明了一个CmdlineTypeParser类,大家都继承它。这样,如果需要ParseAndAppend函数时,就定义它,不需要时,就用基础类CmdlineTypeParser的,避免了重复声明。


当我们在using UserTypeInfo = CmdlineType<TArg>; 时候,C++编译器就根据TArg就选好了具体实现的CmdlineType了。因此,直接调用它的对象的Parser或者ParseAndAppend函数即可。

至于UntypedArgumentBuilder.WithType函数,只是创建了一个ArgumentBuilder类实例,只要把TArg参数传递出去,解析器就可以自动选定了。


RuntimeArgumentMap

最后来说说这个Map对象。RuntimeArgumentMap定义来自
art/runtime/runtime_options.h:50
  template <typename TValue>  struct RuntimeArgumentMapKey : VariantMapKey<TValue> {    RuntimeArgumentMapKey() {}    explicit RuntimeArgumentMapKey(TValue default_value)      : VariantMapKey<TValue>(std::move(default_value)) {}    // Don't ODR-use constexpr default values, which means that Struct::Fields    // that are declared 'static constexpr T Name = Value' don't need to have a matching definition.  };......  struct RuntimeArgumentMap : VariantMap<RuntimeArgumentMap, RuntimeArgumentMapKey> {    // This 'using' line is necessary to inherit the variadic constructor.    using VariantMap<RuntimeArgumentMap, RuntimeArgumentMapKey>::VariantMap;    // Make the next many usages of Key slightly shorter to type.    template <typename TValue>    using Key = RuntimeArgumentMapKey<TValue>;    // List of key declarations, shorthand for 'static const Key<T> Name'#define RUNTIME_OPTIONS_KEY(Type, Name, ...) static const Key<Type> Name;#include "runtime_options.def"  };

要读懂这个代码,需要一些技巧。
首先,我们看到有Variant这个关键字:VariantMap, VariantMapKey,这说明这个Map是可以存储任意类型的数据的。
其次,按照我们一般的观念,要存储任意类型,只有两种方法:1. 用union,2: 用void*只保存其指针,那么,可以推定VariantMap的value只能是二者之一
第三,不管是union还是void*,都必须知道数据的类型,否则数据无法访问,那么VariantMapKey<TValue>的模板参数TValue肯定是用来指出参数类型的;
第四,如果是作为key,那么只能是一个变量值,不能是一个类型。那么RUNTIME_OPTION_KEY宏显然是定义了一个静态key变量。显然 runtime_options.def文件里面,就是用宏RUNTIME_OPTION_KEY定义了一堆key。之所以用宏+runtime_options.def的方式定义,只是因为,在头文件中声明后,还必须在源文件中定义。可以参阅
art/runtime/runtime_options.cc:32
#define RUNTIME_OPTIONS_KEY(Type, Name, ...) const RuntimeArgumentMap::Key<Type> RuntimeArgumentMap::Name {__VA_ARGS__};  // NOLINT [readability/braces] [4]#include "runtime_options.def"
展开之后,就是key的定义了。

VariantMapKey类的定义,大家可以自行看源码(art/runtime/base/variant_map.h), 它的作用,就是将所有对TValue类型的数据进行操作:创建、拷贝、删除等。不再赘述。

然后我们看看VariantMap类的实现。这里,关键是找它存储成员 StorageMap storage_map_, 而这个StoreageMap的定义就是:
art/runtime/base/variant_map.h:402
using StorageMap = std::map<const detail::VariantMapKeyRaw*, void*, KeyComparator>;
现在清楚了,它正是用void*存储数据的。

写在最后的话

本来,参数解析即算不上什么重要的模块,也算不上什么高深技术,即便一个菜鸟,用if-else结构,也完全能够写出解析器,无非看起来很low而已。稍微有点经验的,也可以利用虚函数,写出一个结构不错、也容易维护解析器来。ART没有这样做,而是利用模板,写了一个很漂亮、很高深的解析器。这个解析器无疑要比其他类型的解析器运行速度要快,至于快多少,除非写一个“常规”的解析器,才能比较出来。

实际上,这是我读到的ART源码中最复杂的一块了,激起我下决心把它搞清楚。我觉得,相对于这部分代码的本身意义,解读的方法,更值得向大家推荐:
  • 先搞明白它的意图,要实现什么功能,达到什么效果
  • 想一个一般的实现思路
  • 按照这个思路,去源码中寻找实现这个思路的关键点,在这个过程中,成员变量比成员函数更重要,数据比过程更重要
  • 迭代这个过程,从高层函数到底层函数,层层推进,就能摸清出它的实现方法。

最后,我给ART用模板的方法实现这个功能,给一个中肯的评价:反人类!我曾经有段时间很迷恋模板,甚至研究了boost的实现,用模板实现了简单的lambda表达式。最顶峰时,用模板写了一个ARM机器码反汇编器。但是后来发现,几乎没有人能够再看懂这些代码,即便我自己也需要花些时间来温习。既然如此,用模板还有什么意义?一个设计良好的虚拟类+简单的模板运用,是不是比这样做更清晰、更容易理解、更容易维护呢?