买了一部手机0首付分期付款买手机399,还有3400分24期还是6600,我现在想提前还那边又说我

利益相关:微软亚洲研究院实习生,研究方向是 FPGA 在数据中心的应用。&br&&br&问题「用 FPGA 代替 CPU」中,这个「代替」的说法不准确。我们并不是不用 CPU 了,而是&b&用 FPGA 加速适合它的计算任务,其他任务仍然在 CPU 上完成,让 FPGA 和 CPU 协同工作&/b&。&br&&br&本回答将涵盖三个问题:&br&&ol&&li&为什么使用 FPGA,相比 CPU、GPU、ASIC(专用芯片)有什么特点?&/li&&li&微软的 FPGA 部署在哪里?FPGA 之间、FPGA 与 CPU 之间是如何通信的?&/li&&li&未来 FPGA 在云计算平台中应充当怎样的角色?仅仅是像 GPU 一样的计算加速卡吗?&/li&&/ol&&br&&b&一、为什么使用 FPGA?&/b&&br&&br&众所周知,通用处理器(CPU)的摩尔定律已入暮年,而机器学习和 Web 服务的规模却在指数级增长。&b&人们使用定制硬件来加速常见的计算任务,然而日新月异的行业又要求这些定制的硬件可被重新编程来执行新类型的计算任务&/b&。FPGA (Field Programmable Gate Array) 正是一种硬件可重构的体系结构,常年来被用作专用芯片(ASIC)的小批量替代品,然而近年来在微软、百度等公司的数据中心大规模部署,以&b&同时提供强大的计算能力和足够的灵活性&/b&。&br&&br&&figure&&img src=&https://pic3.zhimg.com/50/v2-71bb2dab64eb85e80b815c_b.jpg& data-rawwidth=&2106& data-rawheight=&1147& class=&origin_image zh-lightbox-thumb& width=&2106& data-original=&https://pic3.zhimg.com/50/v2-71bb2dab64eb85e80b815c_r.jpg&&&/figure&&i&不同体系结构性能和灵活性的比较。&/i&&br&&br&FPGA 为什么快?「都是同行衬托得好」。&b&CPU、GPU 都属于冯·诺依曼结构,指令译码执行、共享内存。&/b&FPGA 之所以比 CPU 甚至 GPU 能效高,本质上是无指令、无需共享内存的体系结构带来的福利。&br&&br&冯氏结构中,由于执行单元(如 CPU 核)可能执行任意指令,就需要有指令存储器、译码器、各种指令的运算器、分支跳转处理逻辑。由于指令流的控制逻辑复杂,不可能有太多条独立的指令流,因此 GPU 使用 SIMD(单指令流多数据流)来让多个执行单元以同样的步调处理不同的数据,CPU 也支持 SIMD 指令。而 &b&FPGA 每个逻辑单元的功能在重编程(烧写)时就已经确定,不需要指令。&/b&&br&&br&冯氏结构中使用内存有两种作用。一是保存状态,二是在执行单元间通信。由于内存是共享的,就需要做访问仲裁;为了利用访问局部性,每个执行单元有一个私有的缓存,这就要维持执行部件间缓存的一致性。&b&对于保存状态的需求,&/b&&b&FPGA&/b& 中的寄存器和片上内存(BRAM)是属于各自的控制逻辑的,&b&无需不必要的仲裁和缓存&/b&。&b&对于通信的需求,FPGA&/b& 每个逻辑单元与周围逻辑单元的连接在重编程(烧写)时就已经确定,&b&并不需要通过共享内存来通信&/b&。&br&&br&说了这么多三千英尺高度的话,FPGA 实际的表现如何呢?我们分别来看计算密集型任务和通信密集型任务。&br&&br&&b&计算密集型任务&/b&的例子包括矩阵运算、图像处理、机器学习、压缩、非对称加密、Bing 搜索的排序等。这类任务一般是 CPU 把任务卸载(offload)给 FPGA 去执行。对这类任务,目前我们正在用的 Altera(似乎应该叫 Intel 了,我还是习惯叫 Altera……)Stratix V FPGA 的整数乘法运算性能与 20 核的 CPU 基本相当,浮点乘法运算性能与 8 核的 CPU 基本相当,而比 GPU 低一个数量级。我们即将用上的下一代 FPGA,Stratix 10,将配备更多的乘法器和硬件浮点运算部件,从而理论上可达到与现在的顶级 GPU 计算卡旗鼓相当的计算能力。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-ff703e9c57f9cf55777b_b.jpg& data-rawwidth=&1053& data-rawheight=&832& class=&origin_image zh-lightbox-thumb& width=&1053& data-original=&https://pic4.zhimg.com/50/v2-ff703e9c57f9cf55777b_r.jpg&&&/figure&&i&FPGA 的整数乘法运算能力(&/i&&i&估计值,&/i&&i&不使用 DSP,根据逻辑资源占用量估计)&/i&&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-404dcbcf292c4e31b3e12ebc228963af_b.jpg& data-rawwidth=&1070& data-rawheight=&831& class=&origin_image zh-lightbox-thumb& width=&1070& data-original=&https://pic2.zhimg.com/50/v2-404dcbcf292c4e31b3e12ebc228963af_r.jpg&&&/figure&&i&FPGA 的浮点乘法运算能力(估计值,float16 用软核,float 32 用硬核)&/i&&br&&br&&b&在数据中心,FPGA 相比 GPU 的核心优势在于延迟&/b&。像 Bing 搜索排序这样的任务,要尽可能快地返回搜索结果,就需要尽可能降低每一步的延迟。如果使用 GPU 来加速,要想充分利用 GPU 的计算能力,batch size 就不能太小,延迟将高达毫秒量级。使用 FPGA 来加速的话,只需要微秒级的 PCIe 延迟(我们现在的 FPGA 是作为一块 PCIe 加速卡)。未来 Intel 推出通过 QPI 连接的 Xeon + FPGA 之后,CPU 和 FPGA 之间的延迟更可以降到 100 纳秒以下,跟访问主存没什么区别了。&br&&br&FPGA 为什么比 GPU 的延迟低这么多?这本质上是体系结构的区别。&b&FPGA 同时拥有流水线并行和数据并行,而 GPU 几乎只有数据并行(流水线深度受限)。&/b&例如处理一个数据包有 10 个步骤,FPGA 可以搭建一个 10 级流水线,流水线的不同级在处理不同的数据包,每个数据包流经 10 级之后处理完成。每处理完成一个数据包,就能马上输出。而 GPU 的数据并行方法是做 10 个计算单元,每个计算单元也在处理不同的数据包,然而所有的计算单元必须按照统一的步调,做相同的事情(SIMD,Single Instruction Multiple Data)。这就要求 10 个数据包必须一起输入、一起输出,输入输出的延迟增加了。当任务是逐个而非成批到达的时候,流水线并行比数据并行可实现更低的延迟。因此&b&对流式计算的任务,FPGA 比 GPU 天生有延迟方面的优势。&/b&&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-1ffb204e56f3d02b0cabdcd6f6c3fb34_b.jpg& data-rawwidth=&1435& data-rawheight=&476& class=&origin_image zh-lightbox-thumb& width=&1435& data-original=&https://pic2.zhimg.com/50/v2-1ffb204e56f3d02b0cabdcd6f6c3fb34_r.jpg&&&/figure&&i&计算密集型任务,CPU、GPU、FPGA、ASIC 的数量级比较(以 16 位整数乘法为例,数字仅为数量级的估计)&/i&&br&&br&ASIC 专用芯片在吞吐量、延迟和功耗三方面都无可指摘,但微软并没有采用,出于两个原因:&br&&ol&&li&数据中心的计算任务是灵活多变的,而 ASIC 研发成本高、周期长。好不容易大规模部署了一批某种神经网络的加速卡,结果另一种神经网络更火了,钱就白费了。FPGA 只需要几百毫秒就可以更新逻辑功能。&b&FPGA 的灵活性可以保护投资,事实上,微软现在的 FPGA 玩法与最初的设想大不相同。&/b&&/li&&li&数据中心是租给不同的租户使用的,如果有的机器上有神经网络加速卡,有的机器上有 Bing 搜索加速卡,有的机器上有网络虚拟化加速卡,任务的调度和服务器的运维会很麻烦。&b&使用 FPGA 可以保持数据中心的同构性。&/b&&/li&&/ol&&br&接下来看通信密集型任务。相比计算密集型任务,通信密集型任务对每个输入数据的处理不甚复杂,基本上简单算算就输出了,这时通信往往会成为瓶颈。对称加密、防火墙、网络虚拟化都是通信密集型的例子。&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-d74634adc21db32f6fafed538c7b91ca_b.jpg& data-rawwidth=&1434& data-rawheight=&478& class=&origin_image zh-lightbox-thumb& width=&1434& data-original=&https://pic1.zhimg.com/50/v2-d74634adc21db32f6fafed538c7b91ca_r.jpg&&&/figure&&i&通信密集型任务,CPU、GPU、FPGA、ASIC 的数量级比较(以 64 字节网络数据包处理为例,&/i&&i&数字仅为数量级的估计&/i&&i&)&/i&&br&&br&&b&对通信密集型任务,FPGA 相比 CPU、GPU 的优势就更大了&/b&。从吞吐量上讲,FPGA 上的收发器可以直接接上 40 Gbps 甚至 100 Gbps 的网线,以线速处理任意大小的数据包;而 CPU 需要从网卡把数据包收上来才能处理,&b&很多网卡是不能线速处理 64 字节的小数据包的&/b&。尽管可以通过插多块网卡来达到高性能,但 CPU 和主板支持的 PCIe 插槽数量往往有限,而且网卡、交换机本身也价格不菲。&br&&br&从延迟上讲,网卡把数据包收到 CPU,CPU 再发给网卡,即使使用 DPDK 这样高性能的数据包处理框架,延迟也有 4~5 微秒。更严重的问题是,&b&通用 CPU 的延迟不够稳定&/b&。例如当负载较高时,转发延迟可能升到几十微秒甚至更高(如下图所示);现代操作系统中的时钟中断和任务调度也增加了延迟的不确定性。&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-b5b50a3c73cba5223ada6c6_b.jpg& data-rawwidth=&817& data-rawheight=&594& class=&origin_image zh-lightbox-thumb& width=&817& data-original=&https://pic2.zhimg.com/50/v2-b5b50a3c73cba5223ada6c6_r.jpg&&&/figure&&i&ClickNP(FPGA)与 Dell S6000 交换机(商用交换机芯片)、Click+DPDK(CPU)和 Linux(CPU)的转发延迟比较,error bar 表示 5% 和 95%。来源:[5]&/i&&br&&br&虽然 GPU 也可以高性能处理数据包,但 GPU 是没有网口的,意味着需要首先把数据包由网卡收上来,再让 GPU 去做处理。这样吞吐量受到 CPU 和/或网卡的限制。GPU 本身的延迟就更不必说了。&br&&br&那么为什么不把这些网络功能做进网卡,或者使用可编程交换机呢?&b&ASIC 的灵活性仍然是硬伤&/b&。尽管目前有越来越强大的可编程交换机芯片,比如支持 P4 语言的 Tofino,ASIC 仍然不能做复杂的有状态处理,比如某种自定义的加密算法。&br&&br&综上,&b&在数据中心里 FPGA 的主要优势是稳定又极低的延迟,适用于流式的计算密集型任务和通信密集型任务。&/b&&br&&br&&br&&b&二、微软部署 FPGA 的实践&/b&&br&&br&2016 年 9 月,《连线》(&i&Wired&/i&)杂志发表了一篇《微软把未来押注在 FPGA 上》的报道 [3],讲述了 Catapult 项目的前世今生。紧接着,Catapult 项目的老大 Doug Burger 在 Ignite 2016 大会上与微软 CEO Satya Nadella 一起做了 FPGA 加速机器翻译的演示。演示的总计算能力是 103 万 T ops,也就是 1.03 Exa-op,相当于 10 万块顶级 GPU 计算卡。一块 FPGA(加上板上内存和网络接口等)的功耗大约是 30 W,仅增加了整个服务器功耗的十分之一。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-de52eeb2acae767ec3a3_b.jpg& data-rawwidth=&1410& data-rawheight=&731& class=&origin_image zh-lightbox-thumb& width=&1410& data-original=&https://pic4.zhimg.com/50/v2-de52eeb2acae767ec3a3_r.jpg&&&/figure&&i&Ignite 2016 上的演示:每秒 1 Exa-op (10^18) 的机器翻译运算能力&/i&&br&&br&微软部署 FPGA 并不是一帆风顺的。&b&对于把 FPGA 部署在哪里这个问题,大致经历了三个阶段:&/b&&br&&ol&&li&&b&专用的 FPGA 集群,里面插满了 FPGA&/b&&/li&&li&&b&每台机器一块 FPGA,采用专用网络连接&/b&&/li&&li&&b&每台机器一块 FPGA,放在网卡和交换机之间,共享服务器网络&/b&&/li&&/ol&&figure&&img src=&https://pic3.zhimg.com/50/v2-880465ced11d754f07f8edd225e48cab_b.jpg& data-rawwidth=&1077& data-rawheight=&1335& class=&origin_image zh-lightbox-thumb& width=&1077& data-original=&https://pic3.zhimg.com/50/v2-880465ced11d754f07f8edd225e48cab_r.jpg&&&/figure&&i&微软 FPGA 部署方式的三个阶段,来源:[3]&/i&&br&&br&第一个阶段是专用集群,里面插满了 FPGA 加速卡,就像是一个 FPGA 组成的超级计算机。下图是最早的 BFB 实验板,一块 PCIe 卡上放了 6 块 FPGA,每台 1U 服务器上又插了 4 块 PCIe 卡。&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-8edb5cfe1cf_b.jpg& data-rawwidth=&2483& data-rawheight=&1101& class=&origin_image zh-lightbox-thumb& width=&2483& data-original=&https://pic1.zhimg.com/50/v2-8edb5cfe1cf_r.jpg&&&/figure&&i&最早的 BFB 实验板,上面放了 6 块 FPGA。来源:[1]&/i&&br&&br&可以注意到该公司的名字。在半导体行业,只要批量足够大,芯片的价格都将趋向于沙子的价格。据传闻,正是由于该公司不肯给「沙子的价格」 ,才选择了另一家公司。当然现在数据中心领域用两家公司 FPGA 的都有。&b&只要规模足够大,对 FPGA 价格过高的担心将是不必要的。&/b&&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-aa8f6a91bfa87bd5db9165_b.jpg& data-rawwidth=&714& data-rawheight=&599& class=&origin_image zh-lightbox-thumb& width=&714& data-original=&https://pic4.zhimg.com/50/v2-aa8f6a91bfa87bd5db9165_r.jpg&&&/figure&&i&最早的 BFB 实验板,1U 服务器上插了 4 块 FPGA 卡。来源:[1]&/i&&br&&br&像超级计算机一样的部署方式,意味着有专门的一个机柜全是上图这种装了 24 块 FPGA 的服务器(下图左)。这种方式有几个问题:&br&&ol&&li&不同机器的 FPGA 之间无法通信,FPGA 所能处理问题的规模受限于单台服务器上 FPGA 的数量;&/li&&li&数据中心里的其他机器要把任务集中发到这个机柜,构成了 in-cast,网络延迟很难做到稳定。&/li&&li&FPGA 专用机柜构成了单点故障,只要它一坏,谁都别想加速了;&/li&&li&装 FPGA 的服务器是定制的,冷却、运维都增加了麻烦。&/li&&/ol&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-70aa39ffd70ec_b.jpg& data-rawwidth=&2534& data-rawheight=&1206& class=&origin_image zh-lightbox-thumb& width=&2534& data-original=&https://pic2.zhimg.com/50/v2-70aa39ffd70ec_r.jpg&&&/figure&&i&部署 FPGA 的三种方式,从中心化到分布式。来源:[1]&/i&&br&&br&一种不那么激进的方式是,在每个机柜一面部署一台装满 FPGA 的服务器(上图中)。这避免了上述问题 (2)(3),但 (1)(4) 仍然没有解决。&br&&br&第二个阶段,为了&b&保证数据中心中服务器的同构性&/b&(这也是不用 ASIC 的一个重要原因),在每台服务器上插一块 FPGA(上图右),FPGA 之间通过专用网络连接。这也是微软在 ISCA'14 上所发表论文采用的部署方式。&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-7b73facc9e24d0fceca7e_b.jpg& data-rawwidth=&858& data-rawheight=&612& class=&origin_image zh-lightbox-thumb& width=&858& data-original=&https://pic2.zhimg.com/50/v2-7b73facc9e24d0fceca7e_r.jpg&&&/figure&&i&Open Compute Server 在机架中。来源:[1]&/i&&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-e23b8d5c807ad2fffbec_b.jpg& data-rawwidth=&2433& data-rawheight=&736& class=&origin_image zh-lightbox-thumb& width=&2433& data-original=&https://pic4.zhimg.com/50/v2-e23b8d5c807ad2fffbec_r.jpg&&&/figure&&i&Open Compute Server 内景。红框是放 FPGA 的位置。来源:[1]&/i&&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-f0ca82beaa1389406ebbc15_b.jpg& data-rawwidth=&1034& data-rawheight=&594& class=&origin_image zh-lightbox-thumb& width=&1034& data-original=&https://pic1.zhimg.com/50/v2-f0ca82beaa1389406ebbc15_r.jpg&&&/figure&&i&插入 FPGA 后的 Open Compute Server。来源:[1]&/i&&br&&br&&figure&&img src=&https://pic3.zhimg.com/50/v2-6d0fdf2e59d9a4b579fc59c_b.jpg& data-rawwidth=&1002& data-rawheight=&1353& class=&origin_image zh-lightbox-thumb& width=&1002& data-original=&https://pic3.zhimg.com/50/v2-6d0fdf2e59d9a4b579fc59c_r.jpg&&&/figure&&i&FPGA 与 Open Compute Server 之间的连接与固定。来源:[1]&/i&&br&&br&FPGA 采用 Stratix V D5,有 172K 个 ALM,2014 个 M20K 片上内存,1590 个 DSP。板上有一个 8GB DDR3-1333 内存,一个 PCIe Gen3 x8 接口,两个 10 Gbps 网络接口。一个机柜之间的 FPGA 采用专用网络连接,一组 10G 网口 8 个一组连成环,另一组 10G 网口 6 个一组连成环,不使用交换机。&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-a932a3ac4cd8_b.jpg& data-rawwidth=&2431& data-rawheight=&1218& class=&origin_image zh-lightbox-thumb& width=&2431& data-original=&https://pic1.zhimg.com/50/v2-a932a3ac4cd8_r.jpg&&&/figure&&i&机柜中 FPGA 之间的网络连接方式。来源:[1]&/i&&br&&br&这样一个 1632 台服务器、1632 块 FPGA 的集群,把 Bing 的搜索结果排序整体性能提高到了 2 倍(换言之,节省了一半的服务器)。如下图所示,每 8 块 FPGA 穿成一条链,中间用前面提到的 10 Gbps 专用网线来通信。这 8 块 FPGA 各司其职,有的负责从文档中提取特征(黄色),有的负责计算特征表达式(绿色),有的负责计算文档的得分(红色)。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-aaef099e0f6cf7aaf9e5be6bb3b0bc27_b.jpg& data-rawwidth=&1655& data-rawheight=&1155& class=&origin_image zh-lightbox-thumb& width=&1655& data-original=&https://pic4.zhimg.com/50/v2-aaef099e0f6cf7aaf9e5be6bb3b0bc27_r.jpg&&&/figure&&i&FPGA 加速 Bing 的搜索排序过程。来源:[1]&/i&&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-8bd4abed27f2cbb8605f_b.jpg& data-rawwidth=&963& data-rawheight=&638& class=&origin_image zh-lightbox-thumb& width=&963& data-original=&https://pic2.zhimg.com/50/v2-8bd4abed27f2cbb8605f_r.jpg&&&/figure&&i&FPGA 不仅降低了 Bing 搜索的延迟,还显著提高了延迟的稳定性。来源:[4]&/i&&br&&br&&figure&&img src=&https://pic3.zhimg.com/50/v2-bb29f3d4b687ce294b329c_b.jpg& data-rawwidth=&963& data-rawheight=&647& class=&origin_image zh-lightbox-thumb& width=&963& data-original=&https://pic3.zhimg.com/50/v2-bb29f3d4b687ce294b329c_r.jpg&&&/figure&&br&&i&本地和远程的 FPGA 均可以降低搜索延迟,远程 FPGA 的通信延迟相比搜索延迟可忽略。&/i&&i&来源:[4]&/i&&br&&br&FPGA 在 Bing 的部署取得了成功,Catapult 项目继续在公司内扩张。微软内部拥有最多服务器的,就是云计算 Azure 部门了。Azure 部门急需解决的问题是网络和存储虚拟化带来的开销。Azure 把虚拟机卖给客户,需要给虚拟机的网络提供防火墙、负载均衡、隧道、NAT 等网络功能。由于云存储的物理存储跟计算节点是分离的,需要把数据从存储节点通过网络搬运过来,还要进行压缩和加密。&br&&br&在 1 Gbps 网络和机械硬盘的时代,网络和存储虚拟化的 CPU 开销不值一提。随着网络和存储速度越来越快,网络上了 40 Gbps,一块 SSD 的吞吐量也能到 1 GB/s,CPU 渐渐变得力不从心了。例如 Hyper-V 虚拟交换机只能处理 25 Gbps 左右的流量,不能达到 40 Gbps 线速,当数据包较小时性能更差;AES-256 加密和 SHA-1 签名,每个 CPU 核只能处理 100 MB/s,只是一块 SSD 吞吐量的十分之一。&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-5aeb1ccedd0b0f00cd82_b.jpg& data-rawwidth=&1842& data-rawheight=&546& class=&origin_image zh-lightbox-thumb& width=&1842& data-original=&https://pic2.zhimg.com/50/v2-5aeb1ccedd0b0f00cd82_r.jpg&&&/figure&&i&网络隧道协议、防火墙处理 40 Gbps 需要的 CPU 核数。来源:[5]&/i&&br&&br&&b&为了加速网络功能和存储虚拟化,微软把 FPGA 部署在网卡和交换机之间。&/b&如下图所示,每个 FPGA 有一个 4 GB DDR3-1333 DRAM,通过两个 PCIe Gen3 x8 接口连接到一个 CPU socket(物理上是 PCIe Gen3 x16 接口,因为 FPGA 没有 x16 的硬核,逻辑上当成两个 x8 的用)。物理网卡(NIC)就是普通的 40 Gbps 网卡,仅用于宿主机与网络之间的通信。&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-974dfbd7fc14f_b.jpg& data-rawwidth=&1265& data-rawheight=&625& class=&origin_image zh-lightbox-thumb& width=&1265& data-original=&https://pic2.zhimg.com/50/v2-974dfbd7fc14f_r.jpg&&&/figure&&i&Azure 服务器部署 FPGA 的架构。来源:[6]&/i&&br&&br&FPGA(SmartNIC)对每个虚拟机虚拟出一块网卡,虚拟机通过 SR-IOV 直接访问这块虚拟网卡。原本在虚拟交换机里面的数据平面功能被移到了 FPGA 里面,虚拟机收发网络数据包均不需要 CPU 参与,也不需要经过物理网卡(NIC)。这样不仅节约了可用于出售的 CPU 资源,还&b&提高了虚拟机的网络性能(25 Gbps),把同数据中心虚拟机之间的网络延迟降低了 10 倍&/b&。&br&&br&&figure&&img src=&https://pic3.zhimg.com/50/v2-dad5b8d8d6ac0e047a016924_b.jpg& data-rawwidth=&2371& data-rawheight=&1316& class=&origin_image zh-lightbox-thumb& width=&2371& data-original=&https://pic3.zhimg.com/50/v2-dad5b8d8d6ac0e047a016924_r.jpg&&&/figure&&i&网络虚拟化的加速架构。来源:[6]&/i&&br&&br&这就是微软部署 FPGA 的第三代架构,也是目前「每台服务器一块 FPGA」大规模部署所采用的架构。&b&FPGA 复用主机网络的初心是加速网络和存储,更深远的影响则是把 FPGA 之间的网络连接扩展到了整个数据中心的规模&/b&,做成真正 cloud-scale 的「超级计算机」。第二代架构里面,FPGA 之间的网络连接局限于同一个机架以内,FPGA 之间专网互联的方式很难扩大规模,通过 CPU 来转发则开销太高。&br&&br&第三代架构中,FPGA 之间通过 LTL (Lightweight Transport Layer) 通信。同一机架内延迟在 3 微秒以内;8 微秒以内可达 1000 块 FPGA;20 微秒可达同一数据中心的所有 FPGA。第二代架构尽管 8 台机器以内的延迟更低,但只能通过网络访问 48 块 FPGA。为了支持大范围的 FPGA 间通信,第三代架构中的 LTL 还支持 PFC 流控协议和 DCQCN 拥塞控制协议。&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-c1dbeccbb5_b.jpg& data-rawwidth=&2209& data-rawheight=&993& class=&origin_image zh-lightbox-thumb& width=&2209& data-original=&https://pic1.zhimg.com/50/v2-c1dbeccbb5_r.jpg&&&/figure&&i&纵轴:LTL 的延迟,横轴:可达的 FPGA 数量。来源:[4]&/i&&br&&br&&figure&&img src=&https://pic3.zhimg.com/50/v2-7ce4e13c6a60fe7923ceb_b.jpg& data-rawwidth=&968& data-rawheight=&791& class=&origin_image zh-lightbox-thumb& width=&968& data-original=&https://pic3.zhimg.com/50/v2-7ce4e13c6a60fe7923ceb_r.jpg&&&/figure&&i&FPGA 内的逻辑模块关系,其中每个 Role 是用户逻辑(如 DNN 加速、网络功能加速、加密),外面的部分负责各个 Role 之间的通信及 Role 与外设之间的通信。来源:[4]&/i&&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-b9d7f53b5125aecfd5d0b719b1a4179f_b.jpg& data-rawwidth=&1272& data-rawheight=&971& class=&origin_image zh-lightbox-thumb& width=&1272& data-original=&https://pic1.zhimg.com/50/v2-b9d7f53b5125aecfd5d0b719b1a4179f_r.jpg&&&/figure&&i&FPGA 构成的数据中心加速平面,介于网络交换层(TOR、L1、L2)和传统服务器软件(CPU 上运行的软件)之间。来源:[4]&/i&&br&&br&&b&通过高带宽、低延迟的网络互联的 FPGA 构成了介于网络交换层和传统服务器软件之间的数据中心加速平面。&/b&除了每台提供云服务的服务器都需要的网络和存储虚拟化加速,FPGA 上的剩余资源还可以用来加速 Bing 搜索、深度神经网络(DNN)等计算任务。&br&&br&&b&对很多类型的应用,随着分布式 FPGA 加速器的规模扩大,其性能提升是超线性的。&/b&例如 CNN inference,当只用一块 FPGA 的时候,由于片上内存不足以放下整个模型,需要不断访问 DRAM 中的模型权重,性能瓶颈在 DRAM;如果 FPGA 的数量足够多,每块 FPGA 负责模型中的一层或者一层中的若干个特征,使得模型权重完全载入片上内存,就消除了 DRAM 的性能瓶颈,完全发挥出 FPGA 计算单元的性能。当然,拆得过细也会导致通信开销的增加。&b&把任务拆分到分布式 FPGA 集群的关键在于平衡计算和通信。&/b&&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-5a17afc6d68df612e27e2_b.jpg& data-rawwidth=&1827& data-rawheight=&394& class=&origin_image zh-lightbox-thumb& width=&1827& data-original=&https://pic1.zhimg.com/50/v2-5a17afc6d68df612e27e2_r.jpg&&&/figure&&i&从神经网络模型到 HaaS 上的 FPGA。利用模型内的并行性,模型的不同层、不同特征映射到不同 FPGA。来源:[4]&/i&&br&&br&在 MICRO'16 会议上,微软提出了 &b&Hardware as a Service (HaaS)&/b& 的概念,即把硬件作为一种可调度的云服务,使得 FPGA 服务的集中调度、管理和大规模部署成为可能。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-e87fddf2b776f27c0d37cba5a521beed_b.jpg& data-rawwidth=&1025& data-rawheight=&950& class=&origin_image zh-lightbox-thumb& width=&1025& data-original=&https://pic4.zhimg.com/50/v2-e87fddf2b776f27c0d37cba5a521beed_r.jpg&&&/figure&&i&Hardware as a Service (HaaS)。来源:[4]&/i&&br&&br&从第一代装满 FPGA 的专用服务器集群,到第二代通过专网连接的 FPGA 加速卡集群,到目前复用数据中心网络的大规模 FPGA 云,三个思想指导我们的路线:&br&&ol&&li&&b&硬件和软件不是相互取代的关系,而是合作的关系;&/b&&/li&&li&&b&必须具备灵活性,即用软件定义的能力;&/b&&/li&&li&&b&必须具备可扩放性(scalability)。&/b&&/li&&/ol&&br&&br&&b&三、FPGA 在云计算中的角色&br&&/b&&br&最后谈一点我个人对 FPGA 在云计算中角色的思考。作为三年级博士生,我在微软亚洲研究院的研究试图回答两个问题:&br&&ol&&li&FPGA 在云规模的网络互连系统中应当充当怎样的角色?&/li&&li&如何高效、可扩放地对 FPGA + CPU 的异构系统进行编程?&/li&&/ol&&br&我对 FPGA 业界主要的遗憾是,FPGA 在数据中心的主流用法,从除微软外的互联网巨头,到两大 FPGA 厂商,再到学术界,大多是把 FPGA 当作跟 GPU 一样的计算密集型任务的加速卡。然而 FPGA 真的很适合做 GPU 的事情吗?前面讲过,&b&FPGA 和 GPU 最大的区别在于体系结构,FPGA 更适合做需要低延迟的流式处理,GPU 更适合做大批量同构数据的处理。&/b&&br&&br&由于很多人打算把 FPGA 当作计算加速卡来用,两大 FPGA 厂商推出的高层次编程模型也是基于 OpenCL,模仿 GPU 基于共享内存的批处理模式。CPU 要交给 FPGA 做一件事,需要先放进 FPGA 板上的 DRAM,然后告诉 FPGA 开始执行,FPGA 把执行结果放回 DRAM,再通知 CPU 去取回。&b&CPU 和 FPGA 之间本来可以通过 PCIe 高效通信,为什么要到板上的 DRAM 绕一圈?&/b&也许是工程实现的问题,我们发现通过 OpenCL 写 DRAM、启动 kernel、读 DRAM 一个来回,需要 1.8 毫秒。而通过 PCIe DMA 来通信,却只要 1~2 微秒。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-ade077e9ffe5e9babeb857_b.jpg& data-rawwidth=&1761& data-rawheight=&647& class=&origin_image zh-lightbox-thumb& width=&1761& data-original=&https://pic4.zhimg.com/50/v2-ade077e9ffe5e9babeb857_r.jpg&&&/figure&&i&PCIe I/O channel 与 OpenCL 的性能比较。纵坐标为对数坐标。来源:[5]&/i&&br&&br&OpenCL 里面多个 kernel 之间的通信就更夸张了,默认的方式也是通过共享内存。本文开篇就讲,FPGA 比 CPU 和 GPU 能效高,体系结构上的根本优势是无指令、无需共享内存。使用共享内存在多个 kernel 之间通信,在顺序通信(FIFO)的情况下是毫无必要的。况且 FPGA 上的 DRAM 一般比 GPU 上的 DRAM 慢很多。&br&&br&因此我们提出了 ClickNP 网络编程框架 [5],&b&使用管道(channel)而非共享内存来在执行单元(element/kernel)间、执行单元和主机软件间进行通信&/b&。需要共享内存的应用,也可以在管道的基础上实现,毕竟 CSP(Communicating Sequential Process)和共享内存理论上是等价的嘛。ClickNP 目前还是在 OpenCL 基础上的一个框架,受到 C 语言描述硬件的局限性(当然 HLS 比 Verilog 的开发效率确实高多了)。理想的硬件描述语言,大概不会是 C 语言吧。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-f0af9adc093_b.jpg& data-rawwidth=&1647& data-rawheight=&618& class=&origin_image zh-lightbox-thumb& width=&1647& data-original=&https://pic4.zhimg.com/50/v2-f0af9adc093_r.jpg&&&/figure&&i&ClickNP 使用 channel 在 elements 间通信,来源:[5]&/i&&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-e6ca6cdab83c_b.jpg& data-rawwidth=&1585& data-rawheight=&359& class=&origin_image zh-lightbox-thumb& width=&1585& data-original=&https://pic2.zhimg.com/50/v2-e6ca6cdab83c_r.jpg&&&/figure&&i&ClickNP 使用 channel 在 FPGA 和 CPU 间通信,来源:[5]&/i&&br&&br&低延迟的流式处理,需要最多的地方就是通信。然而 &b&CPU 由于并行性的限制和操作系统的调度,做通信效率不高,延迟也不稳定&/b&。此外,&b&通信就必然涉及到调度和仲裁&/b&,CPU 由于单核性能的局限和核间通信的低效,调度、仲裁性能受限,硬件则很适合做这种重复工作。因此我的博士研究把 FPGA 定义为通信的「大管家」,不管是服务器跟服务器之间的通信,虚拟机跟虚拟机之间的通信,进程跟进程之间的通信,CPU 跟存储设备之间的通信,都可以用 FPGA 来加速。&br&&br&成也萧何,败也萧何。缺少指令同时是 FPGA 的优势和软肋。每做一点不同的事情,就要占用一定的 FPGA 逻辑资源。如果要做的事情复杂、重复性不强,就会占用大量的逻辑资源,其中的大部分处于闲置状态。这时就不如用冯·诺依曼结构的处理器。数据中心里的很多任务有很强的局部性和重复性:&b&一部分是虚拟化平台需要做的网络和存储,这些都属于通信;另一部分是客户计算任务里的,比如机器学习、加密解密&/b&。我们首先把 FPGA 用于它最擅长的通信,日后也许也会像 AWS 那样把 FPGA 作为计算加速卡租给客户。&br&&br&不管通信还是机器学习、加密解密,算法都是很复杂的,如果试图用 FPGA 完全取代 CPU,势必会带来 FPGA 逻辑资源极大的浪费,也会提高 FPGA 程序的开发成本。更实用的做法是 &b&FPGA 和 CPU 协同工作,局部性和重复性强的归 FPGA,复杂的归 CPU。&/b&&br&&br&当我们用 FPGA 加速了 Bing 搜索、深度学习等越来越多的服务;当网络虚拟化、存储虚拟化等基础组件的数据平面被 FPGA 把持;当 FPGA 组成的「数据中心加速平面」成为网络和服务器之间的天堑……似乎有种感觉,FPGA 将掌控全局,CPU 上的计算任务反而变得碎片化,受 FPGA 的驱使。以往我们是 CPU 为主,把重复的计算任务卸载(offload)到 FPGA 上;以后会不会变成 FPGA 为主,把复杂的计算任务卸载到 CPU 上呢?随着 Xeon + FPGA 的问世,古老的 SoC 会不会在数据中心焕发新生?&br&&br&「跨越内存墙,走向可编程世界」&i&(&/i&&i&Across the memory wall and reach a fully programmable world.&/i&)&br&&br&&b&参考文献:&/b&&br&[1] Large-Scale Reconfigurable Computing in a Microsoft Datacenter &a href=&//link.zhihu.com/?target=https%3A//www.microsoft.com/en-us/research/wp-content/uploads/2014/06/HC26.12.520-Recon-Fabric-Pulnam-Microsoft-Catapult.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&microsoft.com/en-us/res&/span&&span class=&invisible&&earch/wp-content/uploads/2014/06/HC26.12.520-Recon-Fabric-Pulnam-Microsoft-Catapult.pdf&/span&&span class=&ellipsis&&&/span&&/a&&br&[2] A Reconfigurable Fabric for Accelerating Large-Scale Datacenter Services, ISCA'14 &a href=&//link.zhihu.com/?target=https%3A//www.microsoft.com/en-us/research/wp-content/uploads/2016/02/Catapult_ISCA_2014.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&microsoft.com/en-us/res&/span&&span class=&invisible&&earch/wp-content/uploads/2016/02/Catapult_ISCA_2014.pdf&/span&&span class=&ellipsis&&&/span&&/a&&br&[3] &a href=&//link.zhihu.com/?target=https%3A//www.wired.com/2016/09/microsoft-bets-future-chip-reprogram-fly/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Microsoft Has a Whole New Kind of Computer Chip—and It’ll Change Everything&/a&&br&[4] A Cloud-Scale Acceleration Architecture, MICRO'16 &a href=&//link.zhihu.com/?target=https%3A//www.microsoft.com/en-us/research/wp-content/uploads/2016/10/Cloud-Scale-Acceleration-Architecture.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&microsoft.com/en-us/res&/span&&span class=&invisible&&earch/wp-content/uploads/2016/10/Cloud-Scale-Acceleration-Architecture.pdf&/span&&span class=&ellipsis&&&/span&&/a&&br&[5] &a href=&//link.zhihu.com/?target=https%3A//www.microsoft.com/en-us/research/publication/clicknp-highly-flexible-high-performance-network-processing-reconfigurable-hardware/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ClickNP: Highly Flexible and High-performance Network Processing with Reconfigurable Hardware - Microsoft Research&/a&&br&[6] Daniel Firestone, SmartNIC: Accelerating Azure's Network with. FPGAs on OCS servers.
利益相关:微软亚洲研究院实习生,研究方向是 FPGA 在数据中心的应用。 问题「用 FPGA 代替 CPU」中,这个「代替」的说法不准确。我们并不是不用 CPU 了,而是用 FPGA 加速适合它的计算任务,其他任务仍然在 CPU 上完成,让 FPGA 和 CPU 协同工作。 本回答…
&p&本文来自于&a href=&https://link.zhihu.com/?target=https%3A//bugly.qq.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&腾讯bugly开发者社区&/a&,非经作者同意,请勿转载,原文地址:&a href=&https://link.zhihu.com/?target=http%3A//dev.qq.com/topic/c9da& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&dev.qq.com/topic/578753&/span&&span class=&invisible&&c0c9da&/span&&span class=&ellipsis&&&/span&&/a&&/p&&h1&0、引子&/h1&&p& 话说我们做程序员的,都应该多少是个懒人,我们总是想办法驱使我们的电脑帮我们干活,所以我们学会了各式各样的语言来告诉电脑该做什么——尽管,他们有时候也会误会我们的意思。&/p&&p& 突然有一天,我觉得有些代码其实,可以按照某种规则生成,但你又不能不写——不是所有的重复代码都可以通过重构并采用高端技术比如泛型来消除的——比如我最痛恨的代码:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& TextView textView = (TextView) findViewById(R.id.text_view);
Button button = (Button) findViewById(R.id.button);
&/code&&/pre&&/div&&p& 这样的代码,你总不能不写吧,真是让人沮丧。突然想到以前背单词的故事:正着背背不过 C,倒着背背不过 V。嗯,也许写 Android app,也是写不过 findViewById 的吧。。&/p&&p& 我们今天要介绍的 ButterKnife 其实就是一个依托 Java 的注解机制来实现辅助代码生成的框架,读完本文,你将能够了解到 Java 的注解处理器的强大之处,你也会对
dagger2 和 androidannotations 这样类似的框架有一定的认识。&/p&&h1&1、初识 ButterKnife&/h1&&h2&1.1 ButterKnife 简介&/h2&&p& 说真的,我一直对于 findViewById 这个的东西有意见,后来见到了 Afinal 这个框架,于是我们就可以直接通过注解的方式来注入,哇塞,终于可以跟 findViewById 说『Byte Byte』了,真是好开心。&/p&&blockquote&&p&什么?寨见不是介么写么?&/p&&/blockquote&&p& 不过,毕竟是移动端,对于用反射实现注入的 Afinal 之类的框架,我们总是难免有一种发自内心的抵触,于是。。。&/p&&figure&&img src=&https://pic4.zhimg.com/9e5bf13c1_b.jpg& data-rawwidth=&274& data-rawheight=&175& class=&content_image& width=&274&&&/figure&&br&&blockquote&&p&别哭哈,不用反射也可以的~~&/p&&/blockquote&&p& 这个世界有家神奇的公司叫做 Square,里面有个大神叫 Jake Wharton,开源了一个神奇的框架叫做 &a href=&https://link.zhihu.com/?target=https%3A//github.com/JakeWharton/butterknife& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ButterKnife&/a&,这个框架虽然也采用了注解进行注入,不过人家可是编译期生成代码的方式,对运行时没有任何副作用,果真见效快,疗效好,只是编译期有一点点时间成本而已。&/p&&blockquote&&p&说句题外话,现如今做 Android 如果不知道 Jake Wharton,我觉得面试可以直接 Pass 掉了。。。哈哈,开玩笑啦&/p&&/blockquote&&figure&&img src=&https://pic2.zhimg.com/ca5166a26cedcfcda6785acadcddaca1_b.jpg& data-rawwidth=&168& data-rawheight=&179& class=&content_image& width=&168&&&/figure&&br&&h2&1.2 ButterKnife 怎么用?&/h2&&p& 怎么介绍一个东西,那真是一个折学问题。别老说我没文化,我的意思是比较曲折嘛。&/p&&p& 我们还是要先简单介绍一些 ButterKnife 的基本用法,这些知识你在 &a href=&https://link.zhihu.com/?target=https%3A//github.com/JakeWharton/butterknife& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ButterKnife&/a& 这里也可以看到。&/p&&p& 简单来说,使用 ButterKnife 需要三步走:&/p&&ol&&li&&p&配置编译环境,由于 Butterknife 用到了注解处理器,所以,比起一般的框架,配置稍微多了些,不过也很简单啦:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& buildscript {
repositories {
mavenCentral()
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'com.jakewharton:butterknife:8.1.0'
apt 'com.jakewharton:butterknife-compiler:8.1.0'
&/code&&/pre&&/div&&/li&&li&&p&用注解标注需要注解的对象,比如 View,比如一些事件方法(用作 onClick 之类的),例:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& @Bind(R.id.title)
@OnClick(R.id.hello)
void sayHello() {
Toast.makeText(this, &Hello, views!&, LENGTH_SHORT).show();
ButterKnife.apply(headerViews, ALPHA_FADE);
&/code&&/pre&&/div&&/li&&li&&p&在初始化布局之后,调用 bind 方法:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& setContentView(R.layout.activity_main);
ButterKnife.bind(this);//一定要在 setContentView 之后哈,不然你就等着玩空指针吧
&/code&&/pre&&/div&&p&瞧,这时候你要是编译一下,你的代码就能欢快的跑起来啦,什么 findViewById,什么 setOnClickListener,我从来没听说过~&/p&&p&哈,不过你还是要小心一点儿,你要是有本事写成这样,ButterKnife 就说『信不信我报个错给你看啊!』&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@Bind(R.id.title)
private TextV
@OnClick(R.id.hello)
private void sayHello() {
Toast.makeText(this, &Hello, views!&, LENGTH_SHORT).show();
ButterKnife.apply(headerViews, ALPHA_FADE);
&/code&&/pre&&/div&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Error:(48, 22) error: @Bind fields must not be private or static. (com.example.butterknife.SimpleActivity.title)
Error:(68, 18) error: @OnClick methods must not be private or static. (com.example.butterknife.SimpleActivity.sayHello)
&/code&&/pre&&/div&&p&这又是为神马嘞?如果你知道 ButterKnife 的机制,那么这个问题就很清晰了,前面我们已经提到,ButterKnife 是通过注解处理器来生成辅助代码进而达到自己的注入目的的,那么我们就有必要瞅瞅它究竟生成了什么鬼。&/p&&p&话说,生成的代码就在 build/generated/source/apt 下面,我们就以 ButterKnife 的官方 sample 为例,它生成的代码如下:&/p&&figure&&img src=&https://pic4.zhimg.com/86dcd1c02fdc7b54ccfff6_b.jpg& data-rawwidth=&437& data-rawheight=&199& class=&origin_image zh-lightbox-thumb& width=&437& data-original=&https://pic4.zhimg.com/86dcd1c02fdc7b54ccfff6_r.jpg&&&/figure&&br&&p&让我们看一下 SimpleActivity$$ViewBinder:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class SimpleActivity$$ViewBinder&T extends SimpleActivity& implements ViewBinder&T& {
public void bind(final Finder finder, final T target, Object source) {
Unbinder unbinder = new Unbinder(target);
//注入 title,这里的 target 其实就是我们的 Activity
view = finder.findRequiredView(source, , &field 'title'&);
target.title = finder.castView(view, , &field 'title'&);
//下面注入 hello 这个 Button,并为其设置 click 事件
view = finder.findRequiredView(source, , &field 'hello', method 'sayHello', and method 'sayGetOffMe'&);
target.hello = finder.castView(view, , &field 'hello'&);
unbinder.view =
view.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View p0) {
target.sayHello();
&/code&&/pre&&/div&&p&我们看到这里面有个叫 bind 的方法,这个方法跟我们之前调用的 ButterKnife.bind的关系可想而知——其实,后者只是个引子,调用它就是为了调用生成的代码。什么,不信?好吧,我就喜欢你们这些充满好奇的娃。我们在调用 ButterKnife.bind 之后,会进入下面的方法:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
Class&?& targetClass = target.getClass();
ViewBinder&Object& viewBinder = findViewBinderForClass(targetClass);
viewBinder.bind(finder, target, source);
} catch (Exception e) {
//省略异常处理
&/code&&/pre&&/div&&p&我们知道参数 target 和 source 在这里都是咱们的 Activity 的实例,那么找到的 viewBinder 又是什么鬼呢?&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&private static ViewBinder&Object& findViewBinderForClass(Class&?& cls)
throws IllegalAccessException, InstantiationException {
ViewBinder&Object& viewBinder = BINDERS.get(cls);
//先找缓存
if (viewBinder != null) {
return viewB
//检查下是否支持这个类
String clsName = cls.getName();
if (clsName.startsWith(&android.&) || clsName.startsWith(&java.&)) {
return NOP_VIEW_BINDER;
//找到类名为 Activity 的类名加 &$$ViewBinder& 的类,实例化,并返回
Class&?& viewBindingClass = Class.forName(clsName + &$$ViewBinder&);
//noinspection unchecked
viewBinder = (ViewBinder&Object&) viewBindingClass.newInstance();
} catch (ClassNotFoundException e) {
//注意这里支持了继承关系
viewBinder = findViewBinderForClass(cls.getSuperclass());
//缓存 viewBinder
BINDERS.put(cls, viewBinder);
return viewB
&/code&&/pre&&/div&&p&简单看下注释就很容易理解了,如果我们的 Activity 名为 SimpleActivity,那么找到的 ViewBinder 应该就是 SimpleActivity$$ViewBinder。&/p&&p&还是回到我们前面的问题,如果需要注入的成员是 private,ButterKnife 会报错,显然,如果 title 是 private,生成的代码中又写到 target.title,这不就是在搞笑么?小样儿,你以为你是生成的代码, Java 虚拟机就会让你看见不该看的东西么?&/p&&p&当然,对需要注入的成员的要求不止这些啦,我们稍后就会知道,其实对于静态成员和某些特定包下的类的成员也是不支持注入的。&/p&&h2&1.3 小结&/h2&&p&这个框架给我们的感觉就是,用起来炒鸡简单有木有。说话想当年,@ 给了我们上网冲浪的感觉,现在,我们仍然只需要在代码里面 @ 几下,就可以在后面各种浪了。&/p&&p&等等,这么简单的表象后面,究竟隐藏着怎样的秘密?它那光鲜的外表下面又有那些不可告人的故事?请看下回分解。&/p&&h1&2、ButterKnife,给我上一盘蛋炒饭&/h1&&figure&&img src=&https://pic2.zhimg.com/c66cff302ac9b1a49efad94_b.jpg& data-rawwidth=&293& data-rawheight=&293& class=&content_image& width=&293&&&/figure&&br&&blockquote&&p&Jake 大神,我赌一个月好莱坞会员,你一定是一个吃货。。&/p&&/blockquote&&p&我们把生成代码这个过程比作一次蛋炒饭,在炒的时候你要先准备炊具,接着准备用料,然后开炒,出锅。&/p&&h2&2.1 准备炊具&/h2&&p&蛋炒饭是在锅里面炒出来的,那么我们的 ButterKnife 的”锅”又是什么鬼?&/p&&p&闲话少叙,且说从我们配置好的注解,到最终生成的代码,这是个怎样的过程呢?&/p&&figure&&img src=&https://pic1.zhimg.com/b68f989e4c1f94abe1a78eda84511bf8_b.jpg& data-rawwidth=&727& data-rawheight=&386& class=&origin_image zh-lightbox-thumb& width=&727& data-original=&https://pic1.zhimg.com/b68f989e4c1f94abe1a78eda84511bf8_r.jpg&&&/figure&&br&&p&上图很清晰嘛,虽然什么都没说。额。。别动手。。&/p&&p&你看图里面 ButterKnife 很厉害的样子,其实丫是仗势欺人。仗谁的势呢?我们千呼万唤始出来滴注解处理器,这时候就要登上历史舞台啦!&/p&&p&话说 Java 编译器编译代码之前要先来个预处理,这时候编译器会对 classpath 下面有下图所示配置的注解处理器进行调用,那么这时候我们就可以干坏事儿了(怎么每到这个时候都会很兴奋呢。。)&/p&&figure&&img src=&https://pic3.zhimg.com/fde8e1d363f7bfe_b.jpg& data-rawwidth=&321& data-rawheight=&60& class=&content_image& width=&321&&&/figure&&br&&p&所以,如果你要自己写注解处理器的话,首先要继承 AbstractProcessor ,然后写下类似的配置。不过稍等一下,让我们看下 Butterknife 是怎么做的:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
&/code&&/pre&&/div&&p&AutoService 是干什么的呢?看看刚才的图,有没有注意到那个文件夹是红色?是的,它是自动生成的,而负责生成这个配置的家伙就是 &a href=&https://link.zhihu.com/?target=https%3A//github.com/google/auto& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AutoService&/a&,这是 google 的一个开源组件,非常简单,我就不多说了。&/p&&p&简而言之:注解处理器为我们打开了一扇门,让我们可以在 Java 编译器编译代码之前,执行一段我们的代码。当然这代码也不一定就是要生成别的代码了,你可以去检查那些被注解标注的代码的命名是否规范(周志明大神的 《深入理解 Java 虚拟机》一书当中有这个例子)。啊,你说你要去输出一个
“Hello World”,~~(╯﹏╰)b 也可以。。吧。。&/p&&h2&2.2 嘿蛋炒饭,最简单又最困难&/h2&&p&既然知道了程序的入口,那么我们就要来看看 ButterKnife 究竟干了什么见不得人的事儿。在这里,所有的输入就是我们在自己的代码中配置的注解,所有的输出,就是生成的用于注入对象的辅助代码。&/p&&p&关于注解处理器的更多细节请大家参考相应的资料哈,我这里直接给出 ButterKnife 的核心代码,在 ButterKnifeProcessor.process 当中:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@Override public boolean process(Set&? extends TypeElement& elements, RoundEnvironment env) {
//下面这一句解析注解
Map&TypeElement, BindingClass& targetClassMap = findAndParseTargets(env);
//解析完成以后,需要生成的代码结构已经都有了,它们都存在于每一个 BindingClass 当中
for (Map.Entry&TypeElement, BindingClass& entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
//这一步完成真正的代码生成
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, &Unable to write view binder for type %s: %s&, typeElement,
e.getMessage());
&/code&&/pre&&/div&&p&我们知道,ButterKnife 对于需要注入对象的成员有要求的,在解析注解配置时,首先要对被标注的成员进行检查,如果检查失败,直接抛异常。&/p&&figure&&img src=&https://pic2.zhimg.com/fc6c7fd7e89f3b9ebb22a_b.jpg& data-rawwidth=&489& data-rawheight=&95& class=&origin_image zh-lightbox-thumb& width=&489& data-original=&https://pic2.zhimg.com/fc6c7fd7e89f3b9ebb22a_r.jpg&&&/figure&&br&&p&在分析解析过程时,我们以 @Bind 为例,注解处理器找到用 @Bind 标注的成员,检验这些成员是否符合注入的条件(比如不能是 private,不能是 static 之类),之后将注解当中的值取出来,创建或者更新对应的 BindingClass。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&private Map&TypeElement, BindingClass& findAndParseTargets(RoundEnvironment env) {
Map&TypeElement, BindingClass& targetClassMap = new LinkedHashMap&&();
Set&String& erasedTargetNames = new LinkedHashSet&&();
// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
if (!SuperficialValidation.validateElement(element))
parseBind(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, Bind.class, e);
return targetClassM
&/code&&/pre&&/div&&p&现在以前面提到的 title 为例,解析的时候拿到的 element 其实对应的就是 title 这个变量。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&private void parseBind(Element element, Map&TypeElement, BindingClass& targetClassMap,
Set&String& erasedTargetNames) {
... 省略掉检验 element 是否符合条件的代码 ...
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.ARRAY) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (LIST_TYPE.equals(doubleErasure(elementType))) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
// 显然这里被注入的对象类型不能是 Iterable,List 除外~
error(element, &@%s must be a List or array. (%s.%s)&, Bind.class.getSimpleName(),
((TypeElement) element.getEnclosingElement()).getQualifiedName(),
element.getSimpleName());
parseBindOne(element, targetClassMap, erasedTargetNames);
&/code&&/pre&&/div&&p&在注入 title 时,对应的要接着执行 parseBindOne 方法:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&private void parseBindOne(Element element, Map&TypeElement, BindingClass& targetClassMap,
Set&String& erasedTargetNames) {
... 省略掉一些校验代码 ...
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
... 处理错误,显然被注入的必须是 View 的子类 ...
// Assemble information on the field.
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length != 1) {
... 以前已经确认是单值绑定,所以出现了参数为多个的情况就报错...
... 省略构建 BindingClass 对象的代码 ...
BindingClass bindingClass = targetClassMap.get(enclosingElement);
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
// 根据注解信息来生成注入关系,并添加到 bindingClass 当中
FieldViewBinding binding = new FieldViewBinding(name, type, required);
bindingClass.addField(id, binding);
&/code&&/pre&&/div&&p&其实每一个注解的解析流程都是类似的,解析的最终目标就是在这个 bindingClass 里 addField,这意味着什么呢?&/p&&p&通过前面的分析,其实我们已经知道解析注解的最终目标是生成那些用于注入的代码,这就好比我们让注解管理器写代码。这似乎是一个很有意思的话题,如果你的程序足够聪明,它就可以自己写代码~~&/p&&p&那么这么说 addField 就是要给生成的代码添加一个属性咯?不不不,是添加一组注入关系,后面生成代码时,注解管理器就需要根据这些解析来的关系来组织生成的代码。所以,要不要再看一下生成的代码,看看还有没有新的发现?&/p&&h2&2.3、出锅咯&/h2&&p&话说,注解配置已经解析完毕,我们已经知道我们要生成的代码长啥样了,那么下一个问题就是如何真正的生成代码。这里用到了一个工具 &a href=&https://link.zhihu.com/?target=https%3A//github.com/square/javapoet& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JavaPoet&/a&,同样出自 Square 的大神之手。JavaPoet 提供了非常强大的代码生成功能,比如我们下面将给出生成输出 HelloWorld 的 JavaDemo 的代码:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&MethodSpec main = MethodSpec.methodBuilder(&main&)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, &args&)
.addStatement(&$T.out.println($S)&, System.class, &Hello, JavaPoet!&)
TypeSpec helloWorld = TypeSpec.classBuilder(&HelloWorld&)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
JavaFile javaFile = JavaFile.builder(&com.example.helloworld&, helloWorld)
javaFile.writeTo(System.out);
&/code&&/pre&&/div&&p&这样就可以生成下面的代码了:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.example.
public final class HelloWorld {
public static void main(String[] args) {
System.out.println(&Hello, JavaPoet!&);
&/code&&/pre&&/div&&p&其实我们自己写个程序生成一些代码并不难,不过导包这个事情却非常的令人焦灼,别担心,JavaPoet 可以把这些统统搞定。&/p&&p&有了个简单的认识之后,我们要看下 ButterKnife 都用 JavaPoet 干了什么。还记得下面的代码么:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&bindingClass.brewJava().writeTo(filer);
&/code&&/pre&&/div&&p&这句代码将 brew 出来的什么鬼东西写到了 filer 当中,Filer 嘛发挥想象力就知道是类似于文件的东西,换句话说,这句代码就是完成代码生成到指定文件的过程。&/p&&blockquote&&p&Brew Java !!&br&~ ~ ~ heating ~ ~ ~&br&=& =& pumping =& =&&br&[ &em& ]P coffee! [ &/em& ]P &/p&&/blockquote&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(className)
//添加修饰符为 public,生成的类是 public 的
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get(&T&, ClassName.bestGuess(targetClass)));
/*其实 Bind 过程也是有继承关系的,我有一个 Activity A 有注入,另一个 B 继承它,那么生成注入 B 的成员的代码时,就要把 A 的注入一起捎上*/
if (parentViewBinder != null) {
result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
TypeVariableName.get(&T&)));
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get(&T&)));
if (hasUnbinder()) {
result.addType(createUnbinderClass());
//这一句很关键,我们的绝大多数注入用到的代码都在这里了
result.addMethod(createBindMethod());
//输出一个 JavaFile 对象(其实这里离生成最终的代码已经很近了),完工
return JavaFile.builder(classPackage, result.build())
.addFileComment(&Generated code from Butter Knife. Do not modify!&)
&/code&&/pre&&/div&&p&现在我们需要继续看下 createBindMethod 方法,这个方法是生成代码的关键~&/p&&/li&&/ol&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
private MethodSpec createBindMethod() {
/*创建了一个叫做 bind 的方法,添加了 @Override 注解,方法可见性为 public
以及一些参数类型 */
MethodSpec.Builder result = MethodSpec.methodBuilder(&bind&)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(FINDER, &finder&, FINAL)
.addParameter(TypeVariableName.get(&T&), &target&, FINAL)
.addParameter(Object.class, &source&);
if (hasResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
result.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember(&value&, &$S&, &ResourceType&)
.build());
// Emit a call to the superclass binder, if any.
if (parentViewBinder != null) {
result.addStatement(&super.bind(finder, target, source)&);
/* 关于 unbinder,我们一直都没有提到过,如果我们有下面的注入配置:
ButterKnife.U
* 那么这时候就会在生成的代码中添加下面的代码,这实际上就是构造 unbinder
// If the caller requested an unbinder, we need to create an instance of it.
if (hasUnbinder()) {
result.addStatement(&$T unbinder = new $T($N)&, unbinderBinding.getUnbinderClassName(),
unbinderBinding.getUnbinderClassName(), &target&);
* 这里就是注入 view了,addViewBindings 这个方法其实就生成功能上类似
TextView textView = (TextView) findViewById(...) 的代码
if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {
// Local variable in which all views will be temporarily stored.
result.addStatement(&$T view&, VIEW);
// Loop over each view bindings and emit it.
for (ViewBindings bindings : viewIdMap.values()) {
addViewBindings(result, bindings);
// Loop over each collection binding and emit it.
for (Map.Entry&FieldCollectionViewBinding, int[]& entry : collectionBindings.entrySet()) {
emitCollectionBinding(result, entry.getKey(), entry.getValue());
* 注入 unbinder
// Bind unbinder if was requested.
if (hasUnbinder()) {
result.addStatement(&target.$L = unbinder&, unbinderBinding.getUnbinderFieldName());
/* ButterKnife 其实不止支持注入 View, 还支持注入 字符串,主题,图片。。
* 所有资源里面你能想象到的东西
if (hasResourceBindings()) {
//篇幅有限,我还是省略掉他们吧
return result.build();
&/code&&/pre&&/div&&p& 不知道为什么,这段代码让我想起了我写代码的样子。。那分明就是 ButterKnife 在替我们写代码嘛。&/p&&p& 当然,这只是生成的代码中最重要的最核心的部分,为了方便理解,我把 demo 里面生成的这个方法列出来方便查看:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
public void bind(final Finder finder, final T target, Object source) {
//构造 unbinder
Unbinder unbinder = new Unbinder(target);
//下面开始 注入 view
view = finder.findRequiredView(source, , &field 'title'&);
target.title = finder.castView(view, , &field 'title'&);
//... 省略掉其他成员的注入 ...
//注入 unbinder
target.unbinder =
&/code&&/pre&&/div&&h1&3、Hack 一下,定义我们自己的注解 BindLayout&/h1&&p& 我一直觉得,既然 View 都能注入了,咱能不能把 layout 也注入了呢?显然这没什么难度嘛,可为啥 Jake 大神没有做这个功能呢?我觉得主要是因为。。。你想哈,你注入个 layout,大概要这么写 &/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& @BindLayout(R.layout.main)
public class AnyActivity extends Activity{...}
&/code&&/pre&&/div&&p& 可我们平时怎么写呢?&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& public class AnyActivity extends Activity{
protected void onCreate(Bundle savedInstances){
super.onCreate(savedInstances);
setContentView(R.layout.main);
&/code&&/pre&&/div&&p& 你别说你不继承 onCreate 方法啊,所以好像始终要写一句,性价比不高?谁知道呢。。。&/p&&p& 不过呢,咱们接下来就运用我们的神功,给 ButterKnife 添砖加瓦(这怎么感觉像校长说的呢。。嗯,他说的是社河会蟹主@义),让 ButterKnife 可以 @BindLayout。先看效果:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& //注入 layout
@BindLayout(R.layout.simple_activity)
public class SimpleActivity extends Activity {
&/code&&/pre&&/div&&p& 生成的代码:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& public class SimpleActivity$$ViewBinder&T extends SimpleActivity& implements ViewBinder&T& {
public void bind(final Finder finder, final T target, Object source) {
//生成了这句代码来注入 layout
target.setContentView();
//下面省略掉的代码我们已经见过啦,就是注入 unbinder,注入 view
&/code&&/pre&&/div&&p& 那么我们要怎么做呢?一个字,顺藤摸瓜~&/p&&p& 第一步,当然是要定义注解 BindLayout&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& @Retention(CLASS) @Target(TYPE)
public @interface BindLayout {
@LayoutRes int value();
&/code&&/pre&&/div&&p& 第二步,我们要去注解处理器里面添加对这个注解的支持:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
@Override public Set&String& getSupportedAnnotationTypes() {
Set&String& types = new LinkedHashSet&&();
types.add(BindLayout.class.getCanonicalName());
&/code&&/pre&&/div&&p& 第三步,注解处理器的解析环节要添加支持:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& private Map&TypeElement, BindingClass& findAndParseTargets(RoundEnvironment env) {
Map&TypeElement, BindingClass& targetClassMap = new LinkedHashMap&&();
Set&String& erasedTargetNames = new LinkedHashSet&&();
// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(BindLayout.class)) {
if (!SuperficialValidation.validateElement(element))
parseBindLayout(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindLayout.class, e);
&/code&&/pre&&/div&&p& 下面是 parseBindLayout 方法:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& private void parseBindLayout(Element element, Map&TypeElement, BindingClass& targetClassMap, Set&String& erasedTargetNames) {
/*与其他注解解析不同,BindLayout 标注的类型就是 TYPE,所以这里直接强转为
TypeElement,其实就是对应于 Activity 的类型*/
TypeElement typeElement = (TypeElement)
Set&Modifier& modifiers = element.getModifiers();
// 只有 private 不可以访问到,static 类型不影响,这也是与其他注解不同的地方
if (modifiers.contains(PRIVATE)) {
error(element, &@%s %s must not be private. (%s.%s)&,
BindLayout.class.getSimpleName(), &types&, typeElement.getQualifiedName(),
element.getSimpleName());
// 同样的,对于 android 开头的包内的类不予支持
String qualifiedName = typeElement.getQualifiedName().toString();
if (qualifiedName.startsWith(&android.&)) {
error(element, &@%s-annotated class incorrectly in Android framework package. (%s)&,
BindLayout.class.getSimpleName(), qualifiedName);
// 同样的,对于 java 开头的包内的类不予支持
if (qualifiedName.startsWith(&java.&)) {
error(element, &@%s-annotated class incorrectly in Java framework package. (%s)&,
BindLayout.class.getSimpleName(), qualifiedName);
/* 我们暂时只支持 Activity,如果你想支持 Fragment,需要区别对待哈,
因为二者初始化 View 的代码不一样 */
if(!isSubtypeOfType(typeElement.asType(), ACTIVITY_TYPE)){
error(element, &@%s fields must extend from View or be an interface. (%s.%s)&,
BindLayout.class.getSimpleName(), typeElement.getQualifiedName(), element.getSimpleName());
// 拿到注解传入的值,比如 R.layout.main
int layoutId = typeElement.getAnnotation(BindLayout.class).value();
if(layoutId == 0){
error(element, &@%s for a Activity must specify one layout ID. Found: %s. (%s.%s)&,
BindLayout.class.getSimpleName(), layoutId, typeElement.getQualifiedName(),
element.getSimpleName());
BindingClass bindingClass = targetClassMap.get(typeElement);
if (bindingClass == null) {
bindingClass = getOrCreateTargetClass(targetClassMap, typeElement);
// 把这个布局的值塞给 bindingClass,这里我只是简单的存了下这个值
bindingClass.setContentLayoutId(layoutId);
log(element, &element:& + element + &; targetMap:& + targetClassMap + &; erasedNames: & + erasedTargetNames);
&/code&&/pre&&/div&&p& 第四步,添加相应的生成代码的支持,这个在 BindingClass.createBindMethod 当中:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
private MethodSpec createBindMethod() {
MethodSpec.Builder result = MethodSpec.methodBuilder(&bind&)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(FINDER, &finder&, FINAL)
.addParameter(TypeVariableName.get(&T&), &target&, FINAL)
.addParameter(Object.class, &source&);
if (hasResourceBindings()) {
... 省略之 ...
//如果 layoutId 不为 0 ,那说明有绑定,添加一句 setContentView 完事儿~~
//要注意的是,这句要比 view 注入在前面。。。你懂的,不然自己去玩空指针
if(layoutId != 0){
result.addStatement(&target.setContentView($L)&, layoutId);
&/code&&/pre&&/div&&p& 这样,我们就可以告别 setContentView 了,写个注解,非常清爽,随意打开个 Activity 一眼就看到了布局在哪里,哈哈哈哈哈&/p&&figure&&img src=&https://pic3.zhimg.com/eccdbaac2cdd298c3931fbda2ecc9055_b.jpg& data-rawwidth=&322& data-rawheight=&250& class=&content_image& width=&322&&&/figure&&br&&p&&strong&其实是说你胖。。&/strong&&/p&&h1&4、androidannotations 和 dagger2&/h1&&h2&4.1 androidannotations&/h2&&p&&a href=&https://link.zhihu.com/?target=http%3A//androidannotations.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&androidannotations&/a& 同样是一个注入工具,如果你稍微接触一下它,你就会发现它的原理与 ButterKnife 如出一辙。下面我们给出其中非常核心的代码:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
private void processThrowing(Set&? extends TypeElement& annotations, RoundEnvironment roundEnv) throws Exception {
if (nothingToDo(annotations, roundEnv)) {
AnnotationElementsHolder extractedModel = extractAnnotations(annotations, roundEnv);
AnnotationElementsHolder validatingHolder = extractedModel.validatingHolder();
androidAnnotationsEnv.setValidatedElements(validatingHolder);
AndroidManifest androidManifest = extractAndroidManifest();
LOGGER.info(&AndroidManifest.xml found: {}&, androidManifest);
IRClass rClass = findRClasses(androidManifest);
androidAnnotationsEnv.setAndroidEnvironment(rClass, androidManifest);
} catch (Exception e) {
AnnotationElements validatedModel = validateAnnotations(extractedModel, validatingHolder);
ModelProcessor.ProcessResult processResult = processAnnotations(validatedModel);
generateSources(processResult);
&/code&&/pre&&/div&&p& 我们就简单看下,其实也是注解解析和代码生成几个步骤,当然,由于 androidannotations 支持的功能要复杂的多,不仅仅包含 UI 注入,还包含线程切换,网络请求等等,因此它的注解解析逻辑也要复杂得多,阅读它的源码时,建议多多关注一下它的代码结构设计,非常不错。&/p&&p& 从使用的角度来说,ButterKnife 只是针对 UI 进行注入,功能比较单一,而 androidannotations 真是有些庞大和强大,究竟使用哪一个框架,那要看具体需求了。&/p&&h2&4.2 Dagger 2&/h2&&p& Dagger 2 算是超级富二代了,妈是 Square,爹是 Google—— Dagger 2 源自于 Square 的开源项目,目前已经由 Google 接管(怎么感觉 Google 喜当爹的节奏 →_→)。&/p&&p& Dagger 本是一把利刃,它也是用来注入成员的一个框架,不过相对于前面的两个框架,它&/p&&ul&&li&显得更基础,因为它不针对具体业务&/li&&li&显得更通用,因为它不依赖运行平台&/li&&li&&p&显得更复杂,因为它更关注于对象间的依赖关系&/p&&p&用它的开发者说的一句话就是(大意):有一天,我们发现我们的构造方法居然需要 3000 行,这时候我们意识到是时候写一个框架帮我们完成构造方法了。&/p&&p&换句话说,如果你的构造方法没有那么长,其实也没必要引入 Dagger 2,因为那样会让你的代码显得。。。不是那么的好懂。&/p&&p&当然,我们放到这里提一下 Dagger 2,是因为它 完全去反射,实现的思想与前面提到的两个框架也是一毛一样啊。所以你可以不假思索的说,Dagger 2 肯定至少有两个模块,一个是 compiler,里面有个注解处理器;还有一个是运行时需要依赖的模块,主要提供 Dagger 2 的注解支持等等。&/p&&h1&5、小结&/h1&&p&本文通过对 ButterKnife 的源码的分析,我们了解到了 ButterKnife 这样的注入框架的实现原理,同时我们也对 Java 的注解处理机制有了一定的认识;接着我们还对 ButterKnife 进行了扩充的简单尝试——总而言之,使用起来非常简单的 ButterKnife 框架的实现实际上涉及了较多的知识点,这些知识点相对生僻,却又非常的强大,我们可以利用这些特性来实现各种各样个性化的需求,让我们的工作效率进一步提高。&/p&&br&&br&来吧,解放我们的双手!&br&&br&更多精彩内容欢迎关注&a href=&https://link.zhihu.com/?target=https%3A//bugly.qq.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&bugly&/a&的微信公众账号:&/li&&/ul&&figure&&img src=&https://pic2.zhimg.com/3f2c1b1ff77fcedf3fb54616_b.jpg& class=&content_image&&&/figure&&ul&&li&&a href=&https://link.zhihu.com/?target=https%3A//bugly.qq.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&腾讯 Bugly&/a&是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 &a href=&https://link.zhihu.com/?target=https%3A//bugly.qq.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Crash&/a& 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!&/li&&/ul&
本文来自于,非经作者同意,请勿转载,原文地址:0、引子 话说我们做程序员的,都应该多少是个懒人,我们总是想办法驱使我们的电脑帮我们干活,所以我们学会了各式各样的语言来告诉电…
谢邀,&br&&br&&b&偷面积&/b&。这是一个房地产术语。&br&&br&就是政府只允许你盖10000平米,你盖了15000平米,多了5000平米没有房产证,所以就“送”给业主。你就偷了5000平米。&br&&br&这面是偷面积,换一面就变成&b&送面积,&/b&这也是一个房地产术语。&br&&br&各位买房的时候,经常会遇到有所谓赠送面积这一说,送露台、送车位、送地下室这些就不说了,你自己家的房子,也仍然可以送。不久前,我去温州看一个楼盘,开发商用90平米送出四居室。我进了业主的房子一看,屋子里面都是各种错综复杂的隔断,房子两面全是阳台和飘窗。&br&&br&开发商还教会我一个新名词:拓展。就是业主通过入住后的装修,把设备平台改造成卧室的动作。&br&&br&&figure&&img src=&https://pic1.zhimg.com/50/v2-c6cfcce9f0e1cbe8d37f9_b.jpg& data-rawheight=&320& data-rawwidth=&641& class=&origin_image zh-lightbox-thumb& width=&641& data-original=&https://pic1.zhimg.com/50/v2-c6cfcce9f0e1cbe8d37f9_r.jpg&&&/figure&&br&&br&你看我不是说你家四居室吗?这里就是给你的四居室,你把这个墙敲掉,然后把放空调的那个平台封起来,你就多了一间屋子不是么?&br&&br&&b&天上是不会掉馅饼的&/b&&br&&br&买的不如卖的精,这些所谓赠送其实都不是真送,只是把没有产证这部分面积违规造出来,价格摊到单价里卖给你。比如50平米房本面积送50平米,这房子还是参考周边类似房屋100平米的价格卖,但是稍微便宜一些。开发商一般不会这个房子50平米真的按照50平米卖。那还送什么,直接降价就是了。&br&&br&&b&饶了这么大的圈子,到底是为啥呢?&/b&&br&&br&第一个原因是,&b&开发商不能变更建设项目的规划&/b&。&br&&br&2008年的城乡规划法规定,土地招拍挂必须先按照城市规划确定规划条件,这个规划条件其中就包括建设项目的容积率,这是最核心的参数。我们今天看新闻里常常提到开发商买地有所谓&b&楼面价&/b&一说,就是说买地的钱不仅仅是按照土地的大小,更多是要看这宗土地允许该多高的楼,按照盖出来的房本面积来算钱。&br&&br&&blockquote&第三十八条 在城市、镇规划区内以出让方式提供国有土地使用权的,在国有土地使用权出让前,城市、县人民政府城乡规划主管部门应当依据控制性详细规划,提出出让地块的位置、使用性质、开发强度等规划条件,作为国有土地使用权出让合同的组成部分。未确定规划条件的地块,不得出让国有土地使用权。&br&&br&以出让方式取得国有土地使用权的建设项目,在签订国有土地使用权出让合同后,建设单位应当持建设项目的批准、核准、备案文件和国有土地使用权出让合同,向城市、县人民政府城乡规划主管部门领取建设用地规划许可证。&br&&br&城市、县人民政府城乡规划主管部门不得在建设用地规划许可证中,擅自改变作为国有土地使用权出让合同组成部分的规划条件。&/blockquote&&br&因为这样的法律规定,开发商从政府手里拿地,拍地的时候这块地能盖多少平米是固定的。一块1.6容积率的住宅用地,10万平米土地,上面就只能盖16万平米的房子,多一平米也不行。这是拿地的时候和政府签订的土地出让合同里面都约定好的,接下来商品房五证也好,竣工验收也好都要按照这个审批。&br&&br&第二个原因是,&b&多卖面积就要重新审批,多给政府补交土地出让金,增加成本&/b&&br&&br&如果开发商要变更规划增加面积,也不是不行,但是就要重新去发改委立项,重新申请各项手续,补交土地出让金。这一来一去几千万就没了,并不划算。土地市场和楼市一样也是有波动的,有的时候拿地贵,到了卖的时候房价却没有涨上去。2010年这一年楼市很热,开发商有房价上涨的预期,拿的地普遍价格偏高,到2012年赶上限购,这个房子开卖的时候不一定能卖到更高的价格,里外里计算开发商就亏掉了,就靠多卖面积来弥补。&br&&br&一边是不能随意变更规划,一边是高价买了地不能轻易降低价格,所以就只能在不增加建筑面积的前提下又增加面积——好矛盾,但是这是完全做得到的。&br&&br&&b&偷面积主要靠飘窗和阳台&/b&&br&&br&在商品房中计算面积一直有两套标准:《房屋测量规范》和《建筑工程建筑面积计算规范》;前者是国家质量技术监督局2000年的国标,后者是住建部的国标,最近一次更新在2014年7月实施,这两套标准计算面积的方法不完全一致。&br&&br&房屋测量规范一般用于面积测绘,面积计算规范一般用于在建工程造价。前面那套标准已经十六年没有修订过了,上面确定了一些规范主要是 xxx面积算一半面积,xxx不算面积。&br&&br&这其中最主要的就是:飘窗、阳台、设备平台。&br&&br&&figure&&img src=&https://pic4.zhimg.com/50/v2-eed45c7a54d13e8d662fc70_b.jpg& data-rawheight=&640& data-rawwidth=&480& class=&origin_image zh-lightbox-thumb& width=&480& data-original=&https://pic4.zhimg.com/50/v2-eed45c7a54d13e8d662fc70_r.jpg&&&/figure&&br&&br&&br&这是一个设备平台,按照房屋的设计图纸,这个平台是用来放空调的。开发商很明显就是让你敲掉右手边的墙,然后把前面封闭起来,这就是一个房间。照片没拍到的这一侧大概只有20公分宽,是房产证上的一个房间。如果“依法入住”,那你就得买一张15公分宽的床才能当卧室用了。&br&&br&&figure&&img src=&https://pic2.zhimg.com/50/v2-bfe3ff8a7103ef5_b.jpg& data-rawheight=&640& data-rawwidth=&480& class=&origin_image zh-lightbox-thumb& width=&480& data-original=&https://pic2.zhimg.com/50/v2-bfe3ff8a7103ef5_r.jpg&&&/figure&&br&&br&这是四居室的其中一室,左右两边都是承重墙。开发商设计的飘窗是用薄板做的,现在看着是飘窗,里面都是空心的,你入住以后你可以拆掉。不拆的话这个房间做个婴儿房勉勉强强。&br&&br&&b&既然早晚都要拆掉,那为什么不直接拆掉?&/b&&br&&br&因为直接拆掉的话,就和图纸不一致了不是吗?这里我说的图纸,是建设工程规划许可证的附图。这个图一旦审批盖章,在商品房竣工的时候,就要和图纸进行核对方能验收。要是一开始就拆掉,验收就不能通过了。&br&&br&这个过程中规划部门至少审批了两次,一次是审批设计图纸,盖了章,一次是规划验收,又盖了章。这两个章可以说是一章千金。&br&&br&由于开发商自己不能“变更规划”,所以就只能业主入住后自己去变更,同理那些所谓LOFT一层变两层也是这个道理,地下室盖室内游泳池也是这个道理,无一例外不是钻了审批的空子。为了过验收,我还见过先把草坪弄成道路通过消防验收,再把道路弄成草坪做绿化验收的。&br&&br&违法的事情开发商不肯做,自然就落到业主头上。业主要自己去封阳台,拆飘窗,封设备隔间。这样一看,开发商增加了面积,业主得到了实惠?&br&&br&&b&业主有什么损失?&/b&&br&&br&业主的所谓实惠,就是多出来的面积如果和房本这么大面积的房子比,总价会略便宜一些。毕竟有证没证成本不一样,开发商也知趣给业主一点点优惠来促销。以后业主卖这个房子,他也可以说别看我房本面积小,我这个房子实际面积可大。&br&&br&但是这世界原本就没有真的便宜,开发商得了所有的好处,几乎没有任何风险,那么就一定有人来承担风险。&br&&br&2012年南京市秦淮区某著名项目拆迁,我的客户家里的客厅上来

我要回帖

更多关于 0首付分期付款买手机 的文章

 

随机推荐