0%

说明:

宅学部落这个网站看到一篇讲CPU流水线的文章,写的非常好,为了便于翻阅回顾,转载之。

正文:

现在的CPU处理器一般都是超流水线工作,动不动就是10级以上流水线,超高主频,这两者之间有什么关系呢?今天就跟大家科普下CPU流水线的工作原理,以及他们之间的关系。

说到流水线,很多人会想到富士康;说到富士康,很多人会想到张全蛋。作为富士康 3 号流水线资深质检员,下面就请张全蛋给大家科普下什么是流水线,大家鼓掌欢迎。

Micheal Jack 眼中的流水线

大家好,我是张全蛋,英文名叫Micheal Jack,法文名叫霍雷呆-杰Q赖,大家也可以叫我查理。作为iPhone 手机 3 号流水线的资深质检员,我很忙的,每分钟都是几百万的生意。像我们富士康这样的transnational enterprise,经常会和不同国家的客户说一些技术上的meeting啊、下班陪他们message啊,都需要英文的。像我们厂里不会说英语的啊,都会被经理 fire 掉的。在我们厂里,流水线叫法太 low了,我们都叫 pipeline,一条pipeline,每个人分工不同,从手机原材料到成品iPhone ,只需要短短几分钟。—- 《嵌入式C语言自我修养》

查理很忙,介绍到这里就走了,接下来我们继续了解下什么叫流水线。

流水线是工业大生产下的产物。在农业社会做一部手机,需要的是工匠、手艺人,就像故宫里制作钟表的那些匠人一样,是需要拜师学艺、慢慢学习的:从手机组装、质检、贴膜、包装都是一个人,什么都要学。手艺人慢工出细活,但成本很高,到了工业化社会就不一样了:大家分工合作,将做手机这个复杂过程拆分为多个简单步骤,每个人负责一个步骤,经过刻意(机械)练习和培训,就可以很快上手。每个人都做自己最擅长的,可以大大提高工作效率。

如果每个人都单独做一部手机,焊接电路、组装成品这一步骤一般人需要8分钟,测试检验一般需要4分钟,贴膜包装成盒一般需要4分钟,总共需要16分钟。每16分钟,如果有3个工人的话,一共可以生产3部手机。一个新员工从进厂开始,要培训学习三个月才能掌握所有的技能,才能上岗。如果引入生产流水线就不一样了,每个人只负责一个工序,比如赵铁柱只负责焊接电路、组装手机,李小花只负责贴膜,进厂培训3天就可以快速上手了,对工人的技能要求大大降低!而且随着时间积累,每个人对自己所负责的工序越来越熟练,每道工序需要的时间大大减少:赵铁柱焊接电路越来越顺手,花费时间从原来的8分钟缩减为4分钟;张全蛋的质量检验练得如火纯情,整个流程做完只需要2分钟;李小花的贴膜技术也越来越溜了,从贴膜到包装2分钟搞定。每16分钟,赵铁柱可以焊接4块电路板 ,整个流水线可以生产出4部手机,产能整整提升了33.33%!老板高兴,赵铁柱高兴,张全蛋和李小花更高兴,因为每做2分钟,他们还可以休息2分钟,刷刷微博滑个抖音,岂不乐哉!

看到这里可能有人抬杠了:你这么算是不对的,每道工序所用的时间都变为原来的一半,怎么可能做得到?其实要做到不难的,只要工序拆解得合理,容易上手,再加上足够时间的机械重复,很多人都可以做得到。只要奖金发到位,蛋糕店里的小姐姐夹蛋糕的速度比你眨眼的速度都快,银行柜台的小李数钞票的速度比点钞机都快,买单时饭店前台的小妹摁计算器的速度比你掏钱的速度都快。

流水线工作原理

一条指令的执行一般要经过:取指令、翻译指令、执行指令三个基本流程。CPU内部的电路分为不同的单元:取指单元、译码单元、执行单元等,指令的执行也是按照流水线工序一步一步执行的。我们假设每一个步骤执行时间都是一个时钟周期,那么一条指令执行需要3个时钟周期。

CPU 执行指令的3个时钟周期里,取指单元只在第一个时钟周期里工作,其余两个时钟周期都处于空闲状态,其它两个执行单元也是如此,效率太低了,消费者无法接受,老板也不能接受。解决方法就是引入流水线,让流水线每一颗螺丝钉都马不停蹄地运转起来,最好一刻也不要停。

引入流水线工作模式后可以看到,除了刚开始第一个时钟周期大家还可以偷懒外,其余的时间都不能闲着:从第二个时钟周期开始,当译码单元在翻译指令1时,取指单元也不能闲着,要接着去取指令2。同样如此,从第三个时钟周期开始,当执行单元执行指令1时,译码单元也不能闲着,要接着去翻译指令2,而取指单元要去取指令3。从第四个时钟周期开始,每个电路单元都会进入满荷负载工作状态,像富士康工厂里的流水线一样,源源不断地执行一条条指令。

引入流水线后,虽然每一条指令执行流程不变,还是需要3个时钟周期,但是从整条流水线的输出看来,差不多平均每个时钟周期就能执行一条指令。原来执行一条指令需要3个时钟周期,现在平均只需要1个时钟周期,CPU 性能提升了不少。

流水线的本质其实就是拿空间资源换时间。将每条指令分解为多步,指令的每一步都有独立的电路来执行,并让不同指令的各步操作重叠,从而实现几条指令并行处理,从而加快程序的运行。

CPU内部的流水线如此,富士康工厂里的iPhone流水线也是如此,通过不断往流水线增加人手来提高流水线的生产效率,也就是吞吐率。

超流水线技术

想知道什么是超流水线,让我们再回到富士康。

在富士康 3 号 iPhone 流水生产线上,因为赵铁柱工作效率不高,焊接组装一步手机需要 4 分钟,导致生产一部iPhone手机也得需要 4 分钟,从而拖累了整条生产线的生产效率。老板很生气,后果很严重,赵铁柱没干到一个月就被 fire 掉了。后面几个月,陆陆续续来了不少人:小黑、皮裤哥、红姐,都想试试这份工作,可惜干得还不如赵铁柱,挑战电子厂失败,早已提桶跑路。

老板招不到人,感觉又错怪了赵铁柱,于是决定升级生产线,并承诺加薪重新召回了赵铁柱。

老板找出了生产线的瓶颈:每道工序都是需要2分钟,只有赵铁柱这道工序耗时4分钟,老板错怪了这铁柱,这不是赵铁柱的原因,是因为这道工序太复杂。于是把这道工序进行了拆解为2道工序:焊接电路板和组装手机。焊接电路仍由赵铁柱负责,把电路板、显示屏、手机外壳组装成手机这道工序则由新招员工王建国负责。生产流水线优化后,赵铁柱焊接电路只需要2分钟,王建国组装也只需要 2 分钟,生产每部 iPhone 的时间由原来的 4 分钟缩减为 2 分钟,生产流水线的瓶颈解决了!

跟富士康流水线类似,优化CPU流水线也是提升CPU性能的有效手段。流水生产线存在木桶短板效应,我们只需要找出CPU流水线中的性能瓶颈,即耗时最长的那道工序,然后再进行细分、优化为更多的工序就可以了。每一道工序我们称为流水线的一级,流水线越深,每一道流水电路单元的执行时间就会变得越小,我们处理器的时钟周期就可以更短,从而可以通过提升CPU主频来提升CPU性能、提高工作效率。

在富士康流水生产线中,每道工序的最长耗时时间决定了整条生产线的吞吐率。在CPU内部也是如此,每个流水单元的执行时间(即时间延迟)决定了CPU流水线的性能。CPU流水线中的每一道电路单元由组合逻辑电路和寄存器组成,逻辑单路用来执行本道工序的逻辑运算,寄存器用来保存结果,并作为下一道工序的输入。

流水生产线是通过减少每一道工序的耗费时间来提升整条流水线效率的。在CPU内部也是如此,CPU内部的数字电路是靠时钟驱动来工作的,既然每条指令的执行时钟周期数不变,即执行每条指令需要3个时钟周期,但是我们可以通过缩短时钟周期的方法来提升效率,即减少每条指令所耗费的时间。减少时钟周期,也就是提升CPU主频,一个关键的制约因素就是CPU内部每一个执行单元的耗费时间。虽然说电信号在电路中的传播时间很快,可以接近光速,但是经过成千上万的晶体管,不停地信号翻转,还是会带来一定的时间延迟,这个时间延迟我们可以看做这道工作的执行时间。以上图为例,如果每个执行单元的延迟是 1.5 纳秒,那么你的时钟周期至少也得2纳秒以上,否则电路就会工作异常。如果驱动CPU工作的时钟周期是 2 纳秒,CPU的主频就是 500 MHz。现在的CPU流水线深度可以做到10级以上,流水线的每一级时间延迟可以做到皮秒级别,驱动CPU工作的时钟周期可以做到更短,因此可以把CPU的主频飙到 5 GHz 以上。

我们把5级以上的流水线称为超流水线结构。高性能的处理器,为了提升CPU主频,一般都会采用这种超流水线结构。Intel的 i7 处理器有16级流水线,AMD的速龙64系列CPU流水线为20级。史上具有最长流水线的是Intel的第三代奔腾四处理器,有31级的流水线。

想要提升CPU的主频,根本在于减少流水线中每一级流水的执行时间,消除木桶的短板效应,才能提升流水线的整体性能。解决方法有三个:一是优化流水线中各级流水线的性能,受限于当前集成电路的设计水平,这一步最难;二是依靠集成电路的制造工艺,更先进的纳米工艺,芯片面积越小,发热越小,更容易提升主频;三是不断地增加流水线,流水线越深,流水线的各级延迟就可以做得越小,更容易提高主频。

流水线是否越深越好呢?非也。流水线的本质是拿空间换时间,流水线越深,电路就会越复杂,需要更多的组合逻辑电路和寄存器,芯片面积也就越大,功耗也就随之上升了。拿功耗增长换来性能提升,在PC机和服务器上还行,但对于很多靠电池供电的移动设备的处理器来说就无法接受了,CPU设计人员需要在性能和功耗之间做一个很好的平衡。

流水线越深,就越能提升性能吗?也不一定。流水线是靠指令的并行来提升性能的,第一条指令还没有执行完,下面的第二条指令就开始取指、译码了。执行的程序指令如果是顺序结构,没有中断或跳转,流水线确实可以提高执行效率。但是当程序指令中存在跳转、分支结构时,下面预取的指令可能就要全部丢掉了,需要到要跳转的地方重新取指令执行。如下面的分支,如果BEQ条件不满足,处理器可能就会将预期的ADD指令全部丢弃,重新到here标签处取SUB指令执行。

script
1
2
3
4
5
6
7
8
BEQ R1, R2, here
ADD R2, R1, R0
ADD R5, R4, R3
...
here:
SUB R2, R1, R0
SUB R5, R4, R3
...

流水线越深,一旦预取指令失败,浪费和损失就会越严重,因为流水线中预取的几十条指令可能都要丢弃掉,流水线发生了停顿,无法按照预期继续执行,这种情况我们一般称之为流水线冒险(hazard)。在现在很多超流水线处理器中,为了避免这种情况出现,会采取各种各样的方法去避免这种情况,以免影响处理器的性能。

流水线越深,一旦预取指令失败,浪费和损失就会越严重,因为流水线中预取的几十条指令可能都要丢弃掉,流水线发生了停顿,无法按照预期继续执行,这种情况我们一般称之为流水线冒险(hazard)。在现在很多超流水线处理器中,为了避免这种情况出现,会采取各种各样的方法去避免这种情况发生,以免影响处理器的性能。


一、概述

Bloc = Business Logic Component

二、结构

BloC本身是一个独立的package、flutter-bloc基于BloC和Provider实现,其结构如下图所示:

三、 实现

3.1 BloC是基于dart的Stream API实现的发布订阅模式。
  • 通过on方法订阅事件并提供处理函数,收到Event再将Event转换成State返回给Bloc
  • 通过add方法发布事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
abstract class Bloc<Event, State> extends BlocBase<State>
implements BlocEventSink<Event> {
//所有的订阅者
final _subscriptions = <StreamSubscription<dynamic>>[];

final _eventController = StreamController<Event>.broadcast();

//添加订阅者
void on<E extends Event>(
EventHandler<E, State> handler, {
EventTransformer<E>? transformer,
}) {
//这里开始了listen
final subscription = xxx.listen(null);
_subscriptions.add(subscription);
}

@override
void add(Event event) {
try {
onEvent(event);
_eventController.add(event);
} catch (error, stackTrace) {
onError(error, stackTrace);
rethrow;
}
}

}
3.1 事件和状态之间的转换
  • 转换逻辑有业务层通过on()注入
  • 内部的核心是_Emitter以及 _eventController_stateController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(dynamic event) {
void onEmit(State state) {
if (isClosed) return;
if (this.state == state && _emitted) return;
onTransition(Transition(
currentState: this.state,
event: event as E,
nextState: state,
));
emit(state);
}

final emitter = _Emitter(onEmit);
final controller = StreamController<E>.broadcast(
sync: true,
onCancel: emitter.cancel,
);

void handleEvent() async {
void onDone() {
emitter.complete();
_emitters.remove(emitter);
if (!controller.isClosed) controller.close();
}

try {
_emitters.add(emitter);
//交给外部实现转换事件和状态
await handler(event as E, emitter);
} catch (error, stackTrace) {
onError(error, stackTrace);
rethrow;
} finally {
onDone();
}
}

handleEvent();
return controller.stream;
}

四、参考

主要参考和debug了bloc仓库里面examples中login的例子:
这里


简介

官方提供的一个简单的状态管理库。

关键点:

  • 提取状态:状态变量和UI分离
  • 访问状态:将UI和状态建立联系
  • 使用状态:读取状态变量

前置知识

1、InheritedWidget

关键点:

  • Element持有Widget对象
  • Element也是一个BuildContext对象
  • Element里面持有所有的InheritedElement

所以:通过BuildContext对象可以拿到所需的Widget对象,然后访问里面的数据。

父节点存放数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class InheritedShareWidget extends InheritedWidget {
final int data;//用于共享的数据
InheritedShareWidget({this.data, Widget child}) : super(child: child);

//定义便捷方法,方便子控件获取共享数据
static InheritedShareWidget of(BuildContext context) {

///当子控件依赖使用了我们的数据源时,数据变动会触发子控件中的 didChangeDependencies 方法
return context.dependOnInheritedWidgetOfExactType<InheritedShareWidget>();

///(前提:子控件使用了数据源)子控件中的 didChangeDependencies 方法不会被触发
// return context.getElementForInheritedWidgetOfExactType<InheritedShareWidget>().widget;
}

@override
bool updateShouldNotify(covariant InheritedShareWidget oldWidget) {
return oldWidget.data != this.data;//返回true时,才会通知子控件
}
}

子节点获取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestShareChildWidget extends StatefulWidget {
const TestShareChildWidget({Key key}) : super(key: key);
@override
_TestShareChildWidgetState createState() => _TestShareChildWidgetState();
}

class _TestShareChildWidgetState extends State<TestShareChildWidget> {
@override
void didChangeDependencies() {
///如build 方法中没有使用 InheritedShareWidget 的数据,那么它的didChangeDependencies()将不会被调用
super.didChangeDependencies();
print("enter didChangeDependencies");
}

@override
Widget build(BuildContext context) {
print("enter child build");
//获取Inherited的共享数据:
final data = InheritedShareWidget.of(context).data.toString();
return Text(data);
}
}

父子节点嵌套:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class _TestInheritedWidgetState extends State<TestInheritedWidget> {
int count = 0;

@override
Widget build(BuildContext context) {
return Center(
child: InheritedShareWidget(
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TestShareChildWidget(),
RaisedButton(
child: Text('add'),
onPressed: () {
setState(() {
++count;
});
})
],),),);}}

2、InheritedProvider

关键点:

  • 通过InheritedProvider及其子类(如:ChangeNotifierProvider)来包裹其他Widget(child),其实是将child放入了_InheritedProviderScope
  • _InheritedProviderScope是一个InheritedWidget所以,InheritedProvider及其子类将会持有child对象,及其数据。

Provider的基本使用

1
2
3
4
5
6
7
8
9
///这是状态
class Person with ChangeNotifier {
String name = "ChangeNotifierProvider";

void changName({required String newName}) {
name = newName;
notifyListeners();//1
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
class MainApp extends StatelessWidget {
const MainApp({super.key});

@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Person>(///状态和UI之间建立联系
create: (ctx) => Person(),
child: const MaterialApp(
home: ChangeNotifierProviderDemo(),
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ChangeNotifierProviderDemo extends StatelessWidget {
const ChangeNotifierProviderDemo({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("ChangeNotifierProvider")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Consumer<Person>(///使用状态
builder: (ctx, person, child) => Text(person.name),
),
Consumer<Person>(
builder: (ctx, person, child) {
return ElevatedButton(
onPressed: () => person.changName(newName: "ChangeNotifierProvider更新了"),
child: const Text("点击更新"),
);},),],),),);}
}

Provider的类结构

3.1 InheritedProvider的子类只是具体功能的实现,基础逻辑封装在InheritedProvider中
3.2 InheritedProvider将职责转嫁给_InheritedProviderScope而_InheritedProviderScope是一个InheritedWidget

如何实现监听

1、数据实现了ChangeNotifier,变更后调用notifyListeners(),触发回调。

2、ChangeNotifierProvider继承自ListenableProvider,ListenableProvider构建时开启监听:

1
2
3
4
5
6
7
static VoidCallback _startListening(
InheritedContext e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}

class InheritedContext extends BuildContext{
void markNeedsNotifyDependents();
}

3、markNeedsNotifyDependents是InheritedContext中的方法,其实现类是_InheritedProviderScopeElement

1
2
3
4
5
6
7
8
9
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}

markNeedsBuild();
_shouldNotifyDependents = true;
}

4、markNeedsBuild是Element的方法:标记需要重新构建

1
2
3
4
5
6
7
8
void markNeedsBuild() {
...
if (dirty) {
return;
}
_dirty = true;
owner!.scheduleBuildFor(this);
}

局部更新

Provider的Selector提供了缓存Widget的功能,当Widget没有变化时,将直接返回缓存,否则重新build。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
T? value;
Widget? cache;//这是缓存
Widget? oldWidget;

@override
Widget buildWithChild(BuildContext context, Widget? child) {
final selected = widget.selector(context);

final shouldInvalidateCache = oldWidget != widget ||
(widget._shouldRebuild != null &&
widget._shouldRebuild!(value as T, selected)) ||
(widget._shouldRebuild == null &&
!const DeepCollectionEquality().equals(value, selected));
if (shouldInvalidateCache) {//需要重新构建
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
return cache!;
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<T>('value', value));
}
}

参考

https://juejin.cn/post/7067356022272163847#heading-16

Row和Column分别在横向和纵向对子Widget进行布局。

  • 对于Row来讲,横向是主轴,纵向是交叉轴。
  • 对于Column来讲,横向是交叉轴,纵向是主轴。

针对主轴和交叉轴,不同大小的子Widget该如何对齐呢?Flutter提供了下列属性:

MainAxisAlignment:主轴对齐方式

属性值 解释 图示
start 靠近主轴的开始
end 靠近主轴的末尾
center 靠近主轴中间
spaceBetween 剩余空间在孩子中间平分
spaceAround 剩余空间围绕孩子平分
spaceEvenly 剩余空间在孩子之间均等分配

CrossAxisAlignment:交叉轴对齐方式

属性值 解释 图示
start 左对齐(Column)或上对齐(Row)
end 右对齐(Column)或下对齐(Row)
center 中间对齐
stretch 拉伸
baseline 基线对齐,需要配合textBaseline属性使用

本章包含三节:

  • 是什么让你相信了那些荒谬之辞
  • 光环效应与群体智慧
  • 眼见为实的想法往往让我们仓促作出决定

开篇

“同样的形状在字母的环境下就容易被看做是字母,在数字的环境下就容易被看做是数字”

“你过早地对它的“身份”下了结论,并且根本意识不到你已经赋予了某种歧义以解释。”

“你作了一个确切的选择,但自己却没有意识到自己这样做了”

是什么让你相信了那些荒谬之辞

联想记忆的运作是导致“确认偏误”的原因之一

“你试试他给出的例子:“白鱼吃糖果。”
你有可能意识到一个关于鱼和糖果的模糊印象,这个印象的产生过程,就是联想记忆自动搜索“鱼”和“糖果”这两个概念之间各种联系的过程,这一过程会使这种很荒唐的说法看起来竟有些道理了。

光环效应与群体智慧

“如果你赞同一个总统的政见,你可能也会喜爱他的声音及着装。喜爱(或讨厌)某个人就会喜爱(或讨厌)这个人的全部—包括你还没有观察到的方面—这种倾向就叫做光环效应”

Alan:聪明—勤奋—冲动—爱挑剔—固执—忌妒心强
Ben:忌妒心强—固执—爱挑剔—冲动—勤奋—聪明

“光环效应注重第一印象,而后续信息在很大程度上都被消解掉了”

眼见为实的想法往往让我们仓促作出决定

“所有受试者都充分了解了整个过程,那些只听到其中一方辩词的受试者能够很轻松地为另一方写出辩词。然而,片面的证据陈述对判断有着重大影响。
另外,只掌握一方证据的受试者比掌握了双方证据的受试者更有自信。”

“这正说明人们根据已有信息“勾勒出的故事的连贯性增强了他们的自信心。一个好故事最重要的是信息的前后一致性,而不是其完整性。
的确,你常会发现:知道得很少反而可以把已知的所有事物都囊括进连贯的思维模式中。

“眼见即为事实的理念有助于达成连贯性和认知放松的状态,从而使我们相信某个陈述是真实的。这一理念解释了我们能够快速思考的原因,解释了我们是如何弄清楚一个复杂领域中那些信息片段的含义的。很多时候,我们拼凑出的连贯情节与事实是无限接近的,完全可以用来支持理性活动”

软件开发不只是API调用和基础语法,以下是应用开发的全貌:

一、相信数据

如果你觉得系统正常运转,你应该拿出数据支撑你的想法。更重要的是,当研究证明你是错的时,你应该欣然接受实事。
这就需要做测试来验证你的代码,也需要一些流程来生成对应的数据。
你做的任何事都需要数据来辅助你下一步的决策。否则你将不知道自己做的事是对还是错。

二、软件开发不只是编码

除了编码,开发人员还需要协调、沟通、分析、设计、测试、项目管理等

三、代码就是和人沟通

如果我们真的只是“为计算机编程”,那么我们都将编写字节码。计算机最能理解它。但我们用的是一种“折衷”语言,这种语言其他人可以理解,也可以翻译成计算机可以理解的东西。

好的软件开发是一个交流的过程。这是为了确保人们理解你在做什么。你的工作是与下一个阅读它的人交流。找到最好的表达方式可能需要同理心。

四、好的流程很重要

康威定律预言,你的软件注定要反映你的团队及其沟通结构。流程是沟通的结构。

想象一下一架起飞的飞机:飞行员、副驾驶、机组人员和空中交通管制人员之间有一段非常有条理的对话。这确保了每个人都关注关键问题,每个人都有发言权。“机翼还在飞机上吗?””“检查。“没有别的飞机挡住我们的去路吗?”“可以起飞了。”

五、用结果证明自己,而不是身份低位

最糟糕的开发组织要么等级森严,要么每个开发人员有太多老板。这通常反映了管理者对地位的渴望。

考虑“角色”,而不是“地位”。我工作过的最好的组织都首先认可那些推动事情的人,而不管他们在组织中扮演什么角色。

六、每个人都能从别人那学到

如果您认为人们的种族、性别或其他因素是判断他们的技能或他们必须教给您的东西的好方法,那么您就限制了自己作为软件开发人员的发展。

七、测试你所有的猜想,并随时准备改变这些猜想

当指导年轻的开发者时,我总是强调你不应该证明自己是对的,而应该证明自己是错的。我还鼓励他们用证明自己正确的热情去做这件事。

逻辑理论往往有一种方法可以证明你是错的。如果没有,这可能不是一个很好的理论。如果你不能证明它是错的,那么,只有那时,也许你可以试着证明它是对的。这与“相信数据”类似,但这不仅仅是关于数据,还有你使用数据的方式。

参考来源


一、相关类和结构

二、基本使用

2.1 主线程的事件循环
1
2
3
4
5
6
7
8
9
10
11
12
13
package android.app;

public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {

public static void main(String[] args) {
//...
Looper.prepareMainLooper();

Looper.loop();
}
}

2.2 构建自己的事件循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LooperThread extends Thread {
public Handler mHandler;

public void run() {
Looper.prepare();

mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};

Looper.loop();
}
}

三、原理剖析

3.1 构建事件循环
3.2 发送消息
3.3 消息执行
3.4 同步屏障
3.4.1 消息的分类
  • 同步消息
  • 异步消息
  • 屏障消息
3.4.2 同步屏障是什么

一种特殊的Message,其target=null

3.4.3 同步屏障工作原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Message next() {
//...

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
//...
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//碰到同步屏障
// Stalled by a barrier. Find the next asynchronous message in the queue.
// do while循环遍历消息链表
// 跳出循环时,msg指向离表头最近的一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//...
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
//将msg从消息链表中移除
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回异步消息
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...
}
//...
}
}

当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。

3.4.4 实际应用

Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部通过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

四、其他

Epoll 实现原理

Linux select/poll机制原理分析

一、概述

  • 资产回报率(ROA)
  • 净资产回报率(ROE)
  • 投入资本回报率(ROIC)
  • 使用资本回报率(ROCE)

不同的回报率,实际上是衡量公司的不同视角。

二、资产回报率(ROA)

2.1 基本概念

运用全部资金获取利润能力的集中体现

$$
ROA=\frac{净利润}{总资产}
$$

2.2 概念拆分

$$
ROA = 净利润率NPM * 资产利用率AU = \frac{净利润}{营业总收入} * \frac{营业总收入}{总资产} = \frac{(主营业务收入+非主营业务收入)}{总资产}
$$

2.3 概念理解

ROA这个回报率衡量的是资产的回报率。
但是一个公司资产负债表上的资产价值更多的是反映了历史而不是当前,
而且不同的行业的ROA具有不可比性。
最简单的例子,很多类金融企业有无息流动负债,增加了总资产,降低了ROA,
但是无偿使用上下游资金实际上是公司有竞争力的体现。

三、净资产回报率(ROE)

2.1 基本概念

ROE的视角是从股东的角度看问题,单纯从股权的角度衡量回报,而不考虑公司的资本结构及负债情况。衡量公司赚钱的效率。

$$
净资产收益率 = \frac{净利润}{净资产}
$$

A公司:10亿净资产赚2亿。ROE = 20%
B公司:50亿净资产赚5亿。ROE = 10%

2.2 概念拆分

$$
净资产收益率 = \frac{净利润}{净资产} = \frac{净利润}{营业收入} * \frac{营业收入}{总资产} * \frac{总资产}{净资产}
$$

$$
净资产收益率 = 净利润 * 资产周转率 * 权益乘数
$$

$$
权益乘数 = \frac{总资产}{净资产}
$$

$$
资产周转率 = \frac{营业收入}{总资产}
$$

影响因素:

  • 净利润:反应盈利能力
  • 资产周转率:反应资产使用效率
  • 权益乘数:反应负债程度

提升ROE的办法:

  • 提高周转率
  • 廉价的债务杠杆
  • 更高的债务杠杆
  • 更低的所得税
  • 更高的运营利润率
2.3 概念理解

ROE对股东来说意义最大,是股票复利增长的源泉。
ROE可以在不同行业与不同企业之间横向比较。
相当于股票这种“股权债券”的收益率。
对投资人来说无论是投资铁路还是互联网,都相当于是买了“股权债券”。
在同等风险情况下,当然是收益率越高越好。
而且,ROE还可以与政府债券、企业债券的收益率跨资产类别横向比较。

不足

ROE的问题在于无法反映债务杠杆对净利润的影响。
如果一个企业借更多的债,一般就能产生更多净利润,ROE也会提高。
但是这样的提高是以牺牲企业经营稳健程度为代价的。
因此,分析ROE一定要结合杜邦公式,并与同行业进行比较。


四、投入资本回报率(ROIC)

ROIC是从资本的角度看问题,综合考虑股权与债权,衡量投资的效率。与ROIC对应的是平均资金成本(WACC)。如果ROIC小于WACC,就说明投入资本的回报小于平均资本成本,公司是在浪费资本。

ROIC=息税前收益(EBIT)*(1-税率)/投入资本

$$
ROIC = \frac{息税前收益(EBIT)*(1-税率)}{投入资本}
$$

$$
投入资本 = 股东权益 + 有息负债
$$

$$
ROIC = \frac{EBIT(1-税率)}{有息负债+权益)}
$$

公式中分子是指一家公司如果完全以权益筹资所应报告的净利润,分母是指公司所有要求回报的现金来源的总和,也就是说,尽管应付账款也是公司的一种现金来源,但因其未附带明确的成本而被排除在外。实质上,ROIC是生产经营活动中所有投入资本赚取的收益率,而不论这些投入资本是被称为债务还是权益。

投入资本回报率是EBIT(税及利息前盈利)经过税率调整比上投入的资本,也就是股东权益加负债减去非运营现金和投资。之所以用EBIT就是因为利息是由债务产生的,是对债权的回报,当计算债权加股权的总的投入资本的回报率时应该把利息加回来。

投入资本也可以表示为净运营资金(流动运营资产减去无息流动负债)加固定资产,加无形资产及商誉,加其他运营资产。

ROIC的问题在于没有考虑无息流动负债的影响。ROIC实际上是衡量投入的总资本的使用效率,与平均资金成本WACC相结合可以揭示一个企业的真正效率。ROIC < WACC 是卖空大师Jim Chanos经常用来寻找卖空对象的一个重要指标。

但是,ROIC对于类金融企业的分析不太容易。一些类金融企业如国美、苏宁、联想、格力等,占用了大量上下游资金作为无息流动负债,因此提高了EBIT。但是,和有息负债一样,无息负债也存在风险。虽然不用付利息,但是一旦经营形式改变,这些资金很有可能像“热钱”一样撤走。国美在黄光裕被捕后就陷入了这样的危险境地,只要供应商撤资,国美就会破产。这些企业虽然ROIC很高,但是静态不稳定,动态稳定。

五、使用资本回报率(ROCE = Return on Capital Employed)

$$
ROCE資本運用報酬率 = \frac{息税前收益(EBIT)}{已動用資本(Capital Employed)}
$$

$$
已動用資本 = 總資產 -流動負債
$$

ROCE实质上是从企业价值(EnterpriseValue,EV)的角度,从并购的角度看资本的回报率。ROCE中的使用资本是股东权益加有息负债再减现金。这实质上相当于1倍PB并购企业时的企业价值。因为如果你以1倍PB并购一个企业,你要付的价格就是股东权益,并且承担所有负债,但也获得所有现金。

ROCE实际上是从一个并购者的角度看问题,如果并购一个企业,付出企业价值EV后的回报率有多少?这与ROIC最大的不同是:ROIC从企业的拥有者的角度看问题,衡量投入资本的使用效率与回报率。而ROCE是从潜在并购者的角度看问题,衡量如果1倍PB并购付出EV后的回报率,也就是并购到底值不值的问题。这接近价值投资买股票就是买公司的原则。

ROCE:使用资本回报率是EBIT(税及利息前盈利)比上使用的资本,也就是所有有息负债加上股东权益。这个指标与ROIC相近,但是使用的是EBIT而没有经过税率调整,因此可以用来比较不同税率的企业。ROCE也容易忽视公司无息负债的融资效应。

总之,本质上是从四个不同的视角看问题,衡量不同的指标。只有全面综合考虑,才能更全面的看问题

六、参考来源

雪球-老韩7
ROCE資本運用報酬率


我们有一个会被重复调用的lambda表达式:

1
2
3
4
5
6
7
8
9
10
11
import kotlin.*

fun main() {
val f: () -> Unit = {
print("A")
print("B")
print("C")
}
f() // ABC
f() // ABC
}

我们给这个lambda加个参数:(String) -> Unit ,因为参数是个函数,为了能调用到函数,我们把它叫做emit吧:

1
2
3
4
5
6
7
8
9
10
11
import kotlin.*

fun main() {
val f: ((String) -> Unit) -> Unit = { emit -> // 1
emit("A")
emit("B")
emit("C")
}
f { print(it) } // ABC
f { print(it) } // ABC
}

添加的参数看起来有点乱🤔,稍微修改一下,抽出来吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlin.*

fun interface FlowCollector {
fun emit(value: String)
}

fun main() {
val f: (FlowCollector) -> Unit = {
it.emit("A")
it.emit("B")
it.emit("C")
}
f { print(it) } // ABC
f { print(it) } // ABC
}

好像能把it的调用逻辑去掉,这样更简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlin.*

fun interface FlowCollector {
fun emit(value: String)
}

fun main() {
val f: FlowCollector.() -> Unit = {
emit("A")
emit("B")
emit("C")
}
f { print(it) } // ABC
f { print(it) } // ABC
}

调用lambda表达式不是很方便。如果把它抽成接口🤔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import kotlin.*

fun interface FlowCollector {
fun emit(value: String)
}

interface Flow {
fun collect(collector: FlowCollector)
}

fun main() {
val builder: FlowCollector.() -> Unit = {
emit("A")
emit("B")
emit("C")
}
val flow: Flow = object : Flow {
override fun collect(collector: FlowCollector) {
collector.builder()
}
}
flow.collect { print(it) } // ABC
flow.collect { print(it) } // ABC
}

抽成可以重复调用的构建器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import kotlin.*

fun interface FlowCollector {
fun emit(value: String)
}

interface Flow {
fun collect(collector: FlowCollector)
}

fun flow(builder: FlowCollector.() -> Unit) = object : Flow {
override fun collect(collector: FlowCollector) {
collector.builder()
}
}

fun main() {
val f: Flow = flow {
emit("A")
emit("B")
emit("C")
}
f.collect { print(it) } // ABC
f.collect { print(it) } // ABC
}

泛型化参数更通用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import kotlin.*

fun interface FlowCollector<T> {
suspend fun emit(value: T)
}

interface Flow<T> {
suspend fun collect(collector: FlowCollector<T>)
}

fun <T> flow(builder: suspend FlowCollector<T>.() -> Unit) = object : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
collector.builder()
}
}

suspend fun main() {
val f: Flow<String> = flow {
emit("A")
emit("B")
emit("C")
}
f.collect { print(it) } // ABC
f.collect { print(it) } // ABC
}

参考

一、什么是状态

状态是一些被View、Widget订阅或观察的对象,它包含数据。任何状态(数据)的变化都会通知给观察它的View、Widget。

  • 网络断开时展示的Toast
  • 发布的博客及其评论
  • 点击按钮时的水波纹动画

二、Compose中的状态

2.1 记住状态

Composable函数可以使用remember在初始化时存储一个对象到内存中。每次recomposition时这个对象都会被返回。

2.2 可观察状态

mutableStateOf函数可以创建一个可观察的可变数据,此数据的变化将触发recomposition

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun MySwitch() {
// I have a State in Compose's MutableState
val checked = remember { mutableStateOf(false) }
Switch(
// Observe the State by accessing the value property
checked = checked.value,
onCheckedChange = {
checked.value = it
}
)
}
2.3 其他状态类型

除了使用MutableState<T>持有状态外,你还可以使用以下这些可观察数据类型来维护状态。

  • LiveData
  • Flow
  • RxJava2

但是,在使用时需要将其转成State<T>类型

  • LiveData<T>.observeAsState()
  • Flow<T>.collectAsState()
2.4 stateless

在Composable函数中保存状态将降低它的可复用性,通过将状态向外抽离来增加其复用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") }

HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
2.5 状态恢复

当Activity重建时,想要保留状态,并在重建后恢复状态,可以使用rememberSaveable将状态保存到Bundle中。

  • Parcelize
  • MapSaver
  • ListSaver

三、参考

State and Jetpack Compose
Jetpack Compose State Guideline
Managing State in Android