2019-11-25

巴别塔

有时候会想,什么是fake news.
为什么会有fake news.

有时候又觉得,这其实是个伪命题.
因为它可能不过是一种对于失控的一种描述性称呼/代称.

在一个旧有的舆论话语体系下面,一个”大众“的预期大体是可控的.
因为渠道和媒介就那么多.

跟重要的是人的信息半径多多少少受物理界限的约束.

不过车马书信.

事情的变化是从网络社交媒体的渗透开始.
信息的流动变得迅速而形式多样内容丰富起来.

而这种传播的基础是节点的延续性.

也就是说,只有节点间相互承认,一个信息流的声明周期才能更加地长.
于是反过来说,一个存活下来的消息,多多少少是有一定的degree/传播路径/广度的.

那么在整个network bandwidth/人的digest能力还有余地的时候,可能表现出来的就是信息的高附加值属性.
也就是常常怀念的互联网初期的单纯美好.

在一个介于数量相对少而内容相对高的阶段.

随着加入节点的增加,network congestion,信道带宽的价值变得尖锐和有利可图起来.
于是就是所谓的流量经济时代.

imply的就是每一个信息byte的对应成本可计量化.
随之而来的自然是关键节点和backbone的priority处理.

关键节点有各自的maximize策略.
而给定的channel又是有既定的fanout能力的.

所以priority的结果就类似于一种rectifier.
最终就是top n的各种信息的sub shape.

考虑即使是fully connoted的graph.
由于fanout limitation的存在,那么理论上就存在一个信息流不存在一条cover所有节点的路径.
因为它可能被一些节点劣化,从而失去了被route出去的可能性.

那么,也就存在一系列这些部分劣化的路由路径所构成的一个子图.
也就是所谓的echo chamber.

所以,即便是一个完整的全连接图,在给定一个activation阈值的话.
也是可能退化成为一个个的isolated network的.

如果把这个基本网络结果高阶为一种认知抽象或者说社会共识的话.
也就是所谓的各种独立的舆论单元.

每个单独的group/unit/团体有着自己的一种自洽且可能无法在内部证伪的一套理论.

以这套规则审视系统外的变量的时候,自然而然地就难免会有一种不真实感.

毕竟into the unknown就意味着两个子图存在着交集.
也就不能称之为distinct的子图.

fake news大概就是这种局部自洽,全局冲突的一种产物.

对于社会结构的影响在于,一个单一的社会认知/共同认知的可能性的破灭.

就像一个数据中心到一定规模比如会出现分层隔离的网络拓扑.

这是信息流动的便利性带来的overload的一个必然结果和选择.

所以一个团体对于另外一个团体的行为的可预测性和可控性就相对的变得没有意义.
或者说是未定义的.

因为本质上来说,是两套不同的行为体系指导的系统.
也就是所谓的非普世的.

没有一个能够统一思想和道德标准的可能.

所以,说控制fake news和algorithm的操纵性某种程度上来说确实是一回事.

就像做一个BGP/路由/ARP广播.
所期望的不过是以一种合理合法的方式,扩建自己的子网规模.

但从长远来说,多多少少是徒劳的.

只要信息的生成速率至于网络自身的带宽承载能力有一定的优势.
那么scale out总不是那么容易的.

所以某种程度上来说,就社会制度设计层面来说.
基于crowd的consensus架构是很难维系的.

因为没有一个很好的scale out机制.

这样的话就难免不得不面向一个不是那么让人舒服的解决方案.

那就是Qos.

因为问题的本质在于过于自由和无限的信息流动性,造成的congestion和bandwidth flaw.
那么一个直接的方式就是rate limit.
和关键节点的degrade.

这可以说是一个很让人沮丧而又不得不承认的一个事实结论.

所以从这个角度看的话.
可能不作为反而是一种略带喜闻乐见姿态的态度.
作为一种长期被批判攻击的体制的忽然的优越性体现.

尤其在consensus机制在被echo chamber/fake news困扰,苦苦挣扎进一步scale out方向的时候.

你很难说最后会不会全体转向QoS.

尽管不算优雅.

but it works.

更重要,或者说可怕的是,可能没有其他方案.
而且从技术层面上来说,做zone隔离,相对来说还是一个fail safe的基本实践.


于是人类又重新建造了一个巴别塔.

2019-11-16

JIT Inline的一些问题

大致这么一段逻辑
```
void test(item){
if(array_list == null){
array_list = new ArrayList<>();
}

if(!array_list.contains(item)) {
array_list.add(item);
}
}
```
这段代码在两台机器和不同用户之间会有些比较明显的性能差异.

具体是有两台机器A,B.
A为一台KVM,B是物理机器.
A宿主机器跟B是同型号CPU.

现象是一个benchmark在A上大概是5分钟不到.
而B是20-30分钟左右.

另外一个就是在B上以root和非root用户偶尔也会有些可见差别.

这个case的实际逻辑是是加载并解析一个配置,并且是单线程的.
所以整体过程应该是deterministic的.
理论上不应该有这么明显的差异的.

perf了一下.
B上面有一段是change_protection_range会相对显著地跟A有所区别.

大概翻了下对应内核版本的实现,看上去有一些huge page/large page相关的东西.
但是看系统参数和JVM配置,largepage相关的选项也并没有打开.

而且对比了下两者的环境变量和生效配置以及相应的.so也是一致的.

所以从系统层面是上来说,应该都是没差别的.

于是看了下VMThread的safepoint信息.

从日志里看是有一些bias lock的revoke情况.
但是从代码逻辑上来说并没有显著的synchronized应用.
这点就有些奇怪了.

把bias lock disable之后greys attach上去做profile.
然后发觉貌似性能是上去了.
两边都差不多是每秒20-30w左右的调用次数.

但是感觉不是很有说服力.

一个想法是bias lock带来的一点object的开销在这种频繁可能会有新对象创建的场景下被放大.
这样的话可能会跟perf的change_protection_range有联动.
因为可能会对cacheline有影响,毕竟一个是KVM一个是物理机器.

为了确定关联性,把profile去掉之后发觉性能差异有出现了.

那么为什么profile会影响到性能呢?
而且是positive的影响.

想起agent在retransform的时候,redefine class会触发deoptimize.

那么,如果是JIT的问题的话,agent deoptimize能提升性能,也就意味着触发了某个优化规则导致的.

尝试不同的compilation policy.
发现在non-tiered compilation(simple/stack walk)的情况下是ok的.
但是开启tiered compilation(simple threshold/advance threshold)之后则有大幅下降.

用perf-map-agent重新perf看了下.
发现non-tiered的情况下,hot code是array list的contains/indexOf.
而tiered的则是test和equals.

到这里其实就已经很明显了.

non-tiered只inline array list的相关调用,包括indexOf里的equals.
所以perf只看到了contains或者indexOf.

而tiered JIT了test和equals.

实际上看生产的assembly,除了做inline之外,同时还对indexOf做了loop unroll.

但是看perf的结果是test和equals同时被JIT了.
那么也就意味着test没有把equals inline进去.

看JIT生产的assembly证实了,对于整个调用链,也就是indexOf里的equals还是callq调用的.

所以应该是某个机制限制了把equals inline进去.
从而loop unroll之后反而引入了函数调用开销.

把inline日志打开可以看到test的inline有两类信息.
一个是callee is too large.
另外一个是already compiled into a big method.

这就可以解释了.

因为test本身不复杂,所以没有触发自身被caller inline进去.
于是hot code也只会inline这个调用链以下的.
而由于inline和loop unroll使得本身的code size变得比较大,在尝试进一步inline equals的时候被拒绝了.

调整这两个相关参数之后再看确实没有了equals的调用,同时性能保持了一致性.

而再回头看为什么KVM和物理机会有性能差异的时候,大概就是因为compiler thread的数量和并发程度不同.
因此在尝试inline equals的时候会出现时机和结果的不一致性.

所以想想的话,从代码风格上来说,确实compact和reusable的会相对来说比较友好一点.

像Stream这种batch process的风格来说的话,确实有可能for之类的手工展开的好.
因为一个loop unroll,一个是函数主题一般都比较小.
再就是手工展开的话,很难说不会触发像上面这种inline不完整反而受累的情况.












2019-11-10

关于Final的一些问题

考虑一些字节码的生成问题.

比如
```
long start = System.nanoTime();
long end = System.nanoTime();
Profiler.INSTANCE.collect(end-start);
```

按照SSA的风格话,大概会是
```
invokestatic
lstore 1
invokestatic
lstore 2
lload 2
lload 1
lsub
lstore n
getstatic
lloadn
invokestatic
```
这样的形式.

如果是手工生成的话大概会是
```
getstatic
invokestatic
lstore 1
invokestatic
lload 1
lsub
invokestatic
```

区别在于压栈的次数.
因为调整了Profiler.INSTANCE入栈顺序.
所以基本上按照最后collect参数规格维护stack的.

如果要自动地做这种重排的话呢?

本质上来说,就是针对一个函数调用入参约定的一个dependency resolution/backtrace.
就一个control flow的graph来说,可能做的还是一些prune/merge的事情.

不过如果再仔细考虑下上面的例子的话.
其实两个并不是等价的.

因为这里隐含的一个假定是INSTANCE是immutable的.
所以push的时机不影响最终的结果.

但如果INSTANCE是mutable的话,那么这种重排其实是有问题的.
因为后面的调用/指令可能会影响INSTANCE的值.

那么有没有immutable的情况呢.

理论上来说,应该是没有的.
因为如果用反射的话,对于final的field也是可以改变值的.

而且这里比较有趣的一个地方就是final本身的实现.

大概瞄了眼c2的相关代码.
主要是getfield和putfield的.

里面提到确实是会对static final和@Stable的field转成constant node.
这个容易理解.

但是putfield并没有针对final做什么过多特殊处理.

那么如果是严格按照bytecode执行的话,问题不大.
因为不管是get/set/static/field,还是反射的读写.
针对的都是同一个东西.

但是如果是JIT过的,那么在生成native code的时候依据的是转换后的constant node.
这样的话,就可能并不是很符合预期.

一个简单的例子就是一个boolean的if condition.

因为field access变成了一个constant,那么就有可能触发一个优化规则把整个if优化掉.
而当用反射把这个值置回true的话,貌似并不会触发deoptimize.

这样的话,即使从代码和逻辑层面上来说if分支应该被执行,但实际可能是并不会被执行到.

那么如果想办法让put指令里触发deoptimize的话呢?
比如动一下FieldAccessor的相关路径和生成的native code风格?

理论上来说应该是代价比较大的.
一个是如果在native code里对field access引入一个stub的话.
则在这个field被修改的时候需要知道哪些native code部分是需要做改动的.

这个如果不是stub的方式的话,基本上是不太现实的.
因为必须维护一个列表.
这样的话复杂度就不低了.

而如果用stub function的话,就没什么太大意义了.
本来就是一个最多二次寻址的问题,变成反而需要用一个更重的function call替代.

于是从这个角度看的话,set final某种程度上来说算是一种挺危险的行为.
因为这个导致的不确定行为可能还跟环境有关.

在触发JIT的情况下和在interpreter的场景还不太一样.








聊聊卡布里尼

最近看了部片叫卡布里尼,算是可能这段时间来比较有意思的一部电影. 故事也不算复杂,就是一个意大利修女去美国传教,建立慈善性质医院的故事. 某种程度上来说,也很一般的西方普世价值主旋律. 但是如果换一套叙事手法,比如共产国际的社会主义革命建立无产阶级广厦千万间的角度来看的话,也不是...