2020-02-07

积极意义

对于李文亮的死是没有太多悲伤和愤怒的.

它就是一个普通人,做了普通的事.
可能在后验的事实里证明,更多是一个热心甚至有点中二浪漫主义情怀的人.

会根据自己的认知演绎结果,顾及旁人.

那么为什么会有这种大规模的舆论推崇哀悼呢?

一个事实是当前疫情的蔓延情况很大程度上关联于各种不及时不恰当的反应措施.
这是一个基本被所有立场人接受的一个事实.

那么基于这个事实反推的如果之一就是,假设举措能够更迅速一些,结果就可能不太一样.

而李文亮所代表的事情就是当时可能的一个促因.

所以,这里面的逻辑在于,如果当时李文亮的发声产生了积极作用,那么现在的疫情情况可能就根本不存在.

基于这么一个因果关系.

普遍的悲伤情绪的一个点在于,为众人抱薪者,冻毙于风雪.

前半句是因果关系的复述.
后半句是对应的收尾写照.

缺失的 不可使其 是一个当然情况的否定实现.

一种自然朴素的伦理道德观念的错位证伪.
纯良风俗习惯的不现实性.

所以这种悲伤感可能更多的是一种道德层面的自然反应.

那么为什么是以悲伤这种形式表现?
或者说悲伤的内涵是什么?

悲伤对应的最直观的一种情绪表现方式是哭泣.

如果以婴儿的哭泣作为一种最为纯粹的样本来说的话.
其隐含的是一种吸引注意的求助信号和顺推的当前能力不可及达的含义.

也就是一种无能为力情况下的求助性情感表达.

于是,这种悲伤就可以理解为是对于道德结果的不预期状况的一种无力感表示.

在于对一个常识性的道德情况维护结果失败的无能力表达.

那么愤怒呢?

愤怒的点可能比较多.

一个是抱薪行为的非公平公正性对待.
一个是对不公正行为的作为者的其他行为的不合预期结果的迁怒.

这两者本质上可以说是同源于一类悲伤情感.
不能干涉非公正行为的无能感.
以及对不预期结果的反馈修正不能感.

一种一致性的无能为力感.

那么归结起来,各种悲伤和愤怒情绪指向的内涵都是统一的无能为力的求助性表示.

无能为力 这个词隐含的一个信息就是尝试为之而不可得.
也就是存在一种干涉性行为失效之后的状态.

回溯一下上下文.
一个已知的因果关系演绎是,为众人抱薪者,冻毙于风雪,而国恒忧.
一种预期是,存在某种/类干涉使得,为众人抱薪者不使其冻毙于风雪,仓廪实,知礼节.

那么存在这种干涉么?

考虑疫情暴露的问题.

预警应对及时性不够只是诸多不理想环节当中的一个.

也就说,即便当时预警生效的.
也不能说就一定没有后面这些事.

当然,理论上来说存在各个环节都有这种干涉并生效的情况.
但是这个从结果上来说,干涉的有效性也是或者说至少是概率性的,而非必然生效的.

而这种干涉本质上来说是一种对常规常理的一种重申性行为.
某种程度上来说可能不能称之为干涉,而是一种理所当然.

对于纯良风俗习惯的一般期待.

称之为干涉的原因在于,这种纯良风俗习惯的期待不能兑现.

而有多少环节是存在这种不能兑现的,是个微妙的问题.

换个角度来说,如果悲伤情绪的存在在于有不现实的预期的话.
那么,剔除这种不现实预期假设的话,其实是没有什么波澜的.

只是不见得有什么太多积极意义.






2019-12-28

Partial Order

考虑partial order.

一个set是partially order的,也就是任意地,a b不一定是comparable的.
imply的就是min/max是一个undefined的问题.

那么在此基础上做的minimize/maximize就是无意义的.

因为实际上可能并不存在这么一个可比性.
cost function可能引入了一个并不存在或者说错误的关系.

比如给定a 由于cost function一般是well defined的.
所以一定会引出a
但实际的ground truth可能是c
所以,某种程度上来说regression/statistics本质上算是一种偷换概念的行为.

因为原则上来说,这是两种不同的表达式.
或者说,实际上只是,而且只能是一个近似.

如果对这个partial order做个partition,使得其subset是well defined的.
那么它可能是完全描述的么?

也不一定.

因为subset不一定是bounded的.

所以这种近似并不能做到100%的等价.

那么它的意义在哪里呢?

给定足够多的数据和足够细的partition方式.
每一个well defined subset的近似度越高,整体的近似度也就越高.

理论上来说,是有可能完全表达训练集/样本数据的.
当存在一个partition方式,使得每个子集都是well defined的话.

如果把order关系看作一个directed graph的话.
那么就可能存在一个non-well defined的三角关系,使得不能存在一个拆分方式能够保留完全的关系表述.

一个例子就是上面提到的a
所以如果单纯是为了匹配准确度的话,拆分子集分别计算可能是最intuitive的方式.
而且考虑给定cost function去estimate效果的话,拆分本身也可以做随机化取最优方式.
或者按某种搜索方式剪裁拆分复杂度.

而如果是为了generalize的话呢?

其实可能也差不多.

因为所谓generalize只不过是对于样本之外的一种外延预期.

也就是对于ground truth的可能会是怎样,应该会是怎样的一种合理假设.
基于这个假设的分布特性,通过已有的样本空间去生成模拟空间.

然后对这个模拟空间做fitting.

从这个角度看meta learning的话,也不过是经典的search和feature engineering的组合而已.

所不同的可能是feature engineering更多是一种对概率分布的估计方法.


2019-12-21

Finite Automata

昨天看星战9,隔壁一个小哥在看到凯洛伦和雷伊最终接吻的时候表现有点兴奋.

感觉挺有意思的.

这并不算一个什么很出人意料的场景.
大抵兴奋的来源在于一种长期的预设立场被最终承认的一种价值实现带来的满足感.

那么从这个角度看饭圈/CP/偶像文化的话,大概也是类似的.

一种本质上同人创作的圈子文化.
限定在一定的共同背景设定上的自发演绎.

跟所谓的原作的契合度其实并没有实际上相关性.
就是作为一种基本的创作素材.

这样的话,一些现象也就不难解释了.

因为素材本身品质的好坏其实并不是这些二次创作者的关心点.
素材的价值在于提供了一个初始的设定/人设,帮助汇集了一些共同爱好/价值取向的人汇集在一起.

本质上是一种萤火效应.
一种类似随机光亮吸引昆虫的某种触发群体本能的一个动机.

用这个解释对偶像人设的一种苛刻性刻板要求的话.
就是一种偏离设定的不满情绪.

因为从这个角度来说,人本身反而是这种群体创作形象的一种现实的re-projection.
是一种反向作为虚拟设定向现实具像化的反向投射.

就好比针对图纸设计的实际工业产出的精度要求.

于是基于此衍生的消费品的着力点就并不在于消费品的意识形态本身.
而在于受众的接受程度或者说付费点.

所以本质上来说这是一个商业对于消费者的取悦行为.

那么它跟一般的商品买卖的区别在哪里呢?
或者说存在么.

基于供需理论的市场交易是建立在撮合机制上的.
也就是给定相互独立的供给和需求关系,在名为市场的机制下,产生一种联系对应关系.

它的一个前提是需求和供给的产生是相互独立的.

而商业在于,给定未匹配的需求提供对应的供给.

所以从这个角度来说,同人消费并没有什么特别的理论上的不同.

考虑另外一种情况.

在商业贩售本身并不可以提供某些内容的情况下,由消费者二次创作出来的内容/要素.

这一点有些作品会作为一部分要素加入到原有的体系当中,当作一种甜点.
视程度的不同会对原有体系产生不同程度的倾斜和偏移.

另外一些作品则是本身因为各种各样的原因并不会或者并不便于/能够在官方体系当中加入这些要素.
但是并不排斥消费群体的二次创作.

再一些就是从根本上排斥二次创作的.

顺便地,针对二次创作的次级二次创作也可以视为稍微调整定义放到以上适用.

那么一部作品的消费成分就有两部分.
一个是原作品本身想要叙述提供的部分.
以及为二次创作部分提供素材的部分.

除了完全排斥二次创作的作品意外,其他的商业化路径无非就是两部分的比例侧重的不同.

考虑存在混合比例的情况.
在给定一个市场最大化效益函数的情况下,自然会针对两部分做不同的倾注投入.
以产生目的的投入产出.

所以从结果上来说,无非就是最广大人民根本利益的代表,或者说反应.

它的一个结果就是针对不同的效益函数作出的比例调整,以及基于此的长期自反馈带来的长久的比例成分变化.

以及基于此的连带的周边体系生态的随迁变化.

从这个角度来说,群体养成可能是某种更高阶一点的技术手段.

因为从商业角度看,依然是群体在引导商业作出变化.
而往上的,则是上层针对养成群体的参数控制和调整的一个结果.

某种程度上来说,也算一种设计精巧的finite automata.

2019-12-15

关于Profiler的一些想法

最近写个profiler,写完发觉大概有点不切实际.

开始是基于一个相对简单的想法.
因为一般debug java的一些线上问题无非就是看下哪些是hot method.
suspect之后一层一层顺着堆栈定位下去.

过程大体是相对繁琐的.

所以想着把这段稍微自动化点.
给定一个entry point,把整个execution过程的调用做个统计列出来.
免得再人工一个个去深入.

于是一个直接的想法就是做instrument.
在每个方法的调用前后加上time span,做耗时统计.

这里当时想了两种方式.
一种是在每个invoke*指令前后插入enter/leave的时间戳.
一种是在每个方法的entry/exit做这个时间戳统计.

第一种相对来说,最后生成的字节码会比较繁琐.
而且如果考虑到异常情况的化,这个time span可能还需要针对每个invoke做一个try-catch/异常处理block.

所以选择是在entry/exit插入.

从另一个角度来说的话,选择后者也实现上会相对的leap/优雅一些.

因为就是一个简单直观的rule.
而且具有相对的普适性.
即使再考虑异常情况的话,也又一个比较简洁的框架模式可以套.

比如对于一个
void target(){
// some code
}
形态的method来说.
插入之后大概就是
void target(){
Bridge.enter();
try{
// orignal
Bridge.leave();
}catch(Throwable e){
Bridge.leave();
throw e;
}
}

针对正常和异常都能相对结构简洁优雅地处理设计统计.

这里一个题外话就是Bridge的设计.

之所以叫Bridge主要是实现上用了一个小trick.

考虑到targe JVM一般是不太能够重启的,而attach的instrument agent有时候代码需要做改动.
尤其是开发时候.

所以实现上这里做了个classloader的处理.

agent在host vm里加载的时候会污染一部分host vm的classloader.
也即是agentmain部分的class会在host classloader里存在.

而如果全部agent code都是在这个classloader里的话,那么要达到代码的hot swap是不太可能的.
除非是transform自身.

于是,这里在agentmain的入口里做个简单的壳.
在agent load的时候切换切换class loader,使得每次attach的session使用自己独立的classloader.
以达到避免污染和hot reload的效果.

大致效果上类似于JSP的一些使用.

因为本质上来说,java的class实例是跟当前调用method的所属的class的classloader绑定的.
这样的话,只要创建一个目标classloader的shell class就能让后续的execution的常规class创建绑定在这个session bounded的loader里了.

在处理了这个热加在和基本的字节码处理框架之后,剩下的就是调用链条的crawl了.

直觉上来说,这也是个相对简单的过程.
因为jvm无非就几个invoke指令,scan一些method的bytecode做个筛选然后递归一下就是了.

只是实际麻烦的点就在于这几个invoke指令的语义.
尤其是invoke virtual和invoke interface.

根据jvm specification.
method resolution主要是基于class往上回溯的.
在interface和class method存在同名的情况下,class method的resolve规则相对靠前.
也就是相对的是favor的一方.

对于两者来说,给定一个invoke some_class some_method的指令.
形式上都是做一个virtual dispatch.

是运行时需要根据具体的instance类型做vtable/itable的lookup找到实际bind的method的.

也就是说,在scan到类似invoke some_class some_method的时候,并不能直接对some_class some_method做递归instrument.
因为除了some_class可能并不是concerted的情况意外,还有就是实际是一个sub class override的情形.

因此实际上要递归的对象/目标是从class/type hierarchy中upper bound为some_class的所有子类.

这样的话,就跟最初预期有了第一个意外.

当初预期做stack trace/调用链递归分析的出发点在于尽可能地只trace interested的部分.
也就是只在调用链条内的方法开销.

所以,在具体的tracing统计了,还做了个简单的per thread的stack matching.
保证被profile的方法的call stack里包含想要trace的entry method.
使得并不是所有特定方法的调用耗时都会包括进去,而是只有特定的调用栈的会被统计进来.

而如果作为upper bounded class的话,虽然形式上来说并没有打破这个约束/优化.
但是实际上的结果是会包含引入非常多的class被instrument.

一个测试方法的数据就是大概2000多个类会被触及到.

尽管理论上来说,这里是可以做一个静态分析的去除一部分的.
因为毕竟是实际上又所有被加载类的信息,加上制定了特定堆栈约束.
也就是相当于给定的一个execution state的initial parameter.

基于此做一个静态分析/执行引擎对涉及到的invoke做类型限定推导是有可能的.

只是一个是这个代价稍微有点大和复杂.

因为本质上相当于做个interpreter.

尽管如果写了之后可以进一步作为一个fuzzer的基础.

然而即使有了这个interpreter也并不难100%的限定类型.
因为存在一个动态类型是只有运行时才能确定的.

一个简单的例子就是反射.
以及一些涉及类似serviceloader加载机制的纯运行时特性.

于是,既然这种裁剪方式并没有一个相对完美的效果的话,那就不如不做筛选.
而是直接对所有class做instrument.

这样的话,实现上也更直接,不需要去做invoke scan的递归递归扫描操作.
而是直接无差别的transform,然后再在运行时再根据call stack决定需不需要做accounting.

唯一需要考虑的就是避免在做accepting的时候陷入递归.

因为做call stack check的代码也是被instrument的.
所有如果不对一些类做白名单处理的话,就直接递归了.

白名单的构建也尝试过用invoke scan的方式尽可能地构建一个小而完整的集合.
但问题依然是没办法100%保证确切的类型限定.
最终也是会指向type upper bounded的道路.

所以最终实现是用了限定java.lang和java.util将大多数jdk class排除在外.

在这个方案基础上出来的结果是第二个不预期的情况.

最初的构想是做了stack tracing之后,直接列出整个call stack的每一步的耗时.

而实际的结果是,尽管确实有了完整的call stack的每个step的耗时统计.
但问题在于涉及的method invoke太多.

一个方法trace有时候会展开成数十甚至上百的invoke method.

这个在事后其实也不难理解.

简单考虑一个method有个k个invoke,每个invoke针对m个class.
那么一级就是大概有k*m的调用统计.
再对每个k递归的话,就是理论上指数级的展开.
即使考虑其中可能会有重复的部分.

但理论上越是复杂的函数,最终的展开级数就越多.

所以这个在实际上是没有什么太大意义.

而且另外一个问题是因为是transform几乎所有class.
所以attch的time会明显地跟class数量线性相关.

更主要的是之前提到过的.
transform实际上会触发JIT的de-optimize.

针对所有class做instrument的话,基本上就是整个jvm打回warmup状态.

尽管理论上来说,会eventually JIT的.

但是从这个角度来说,基于instrument的profiler是会有一定程度的失真的.
尤其如果问题的实际不在bytecode层面,是VM本身的一些特性的话.

所以可能确实是基于perf couter和jvmti event的方案会更优雅准确一些.
至少,它们对VM的观察不会影响已有的JIT状态和实际的VM的代码层面允许逻辑.

2019-12-07

负利率

负利率也就是说央行在做积极的cash in.

反过来说就银行自身的头寸在减少.

如果要维持增长规模的 话.
那么,要么提高利率,要么加快周转速度.

提高贷款利率的话,需要借入方存在相应等级的ROI.
不然是非rational的.

考虑如果实际负债/债务水平在合理的条件下.
反过来也就是说,确实贷款利率水平存在弹性空间的前提下,进一步提高利率是合理的.

而如果负债水平已经在一个水平之上了, 高利率显然是没有什么空间的.

这是在债务人的ROI在利率水平之上的情况下.
利率水平存在一个upper bound.

那么如果ROI是irrational的呢?
也就说,实际的收益率低于贷款利率水平的情况下.
是什么动机驱动借款的产生的呢?

这里其实是一个递归过程.

把出借方置换成央行,借入方置换为银行.
那么借入方的动机就在于维持规模增长.

则在这种情况下就是一个同态的链式结构.
整个链条的稳定性取决于末端节点的rational的ROI水平.

而实际情况可能更复杂一下,不是一个简单的无环结构.
可能是一个复杂的网络图形态.

但不管怎么复杂,都存在一个单一的链状结构,使得整个链条的稳定性和风险聚焦于末端的状态.

如果末端出现一些不稳定因素的话.
比如default的话,那么网上回推就是 一系列的坏账.

加上环状结构的放大的话,可能就是一个系统性风险.

所以一个很自然的,在复杂系统下的简单防御策略就是控制债务规模.
降低违约风险.
至少是要在一个可控范围内,防止反推压力,以及阻断扩散.

这是在利率空间上维持增长的情况.

考虑提升周转率的途径.

在给定总量缩减的情况下,提高周转率也就因为着在单位时间内是可以达到 缩减前的规模的.
而理论上来说,提高周转率也总是一个逻辑上 较优的一个解.

同样地,如果周转率存在进一步上升空间的话,是没什么问题的.
因为可reduce成不同利率上限的情况.

如果周转率 不能进一步上升呢?

这里如果再细分一下.
即分为不同品质的周转率的 无法进一步提升.

那么显然地,高周转率的产品会具有优先偏好性.

也就是说,在这种情况下,短期贷款会优先于长期贷款.

intuitive的也是.
周期长意味着复合风险高.

所以在这种情况下,短期借贷会较长期更为繁荣.

而如果不是的话,要么说明短期符合利率upper bounded的情况.

或者在现有的规模下无法有效地触发短期借贷.
比如长期借款已经沉默/沉淀下来了,在短期内无法convert成较高流动性的成分.

在这种情况下就会有动机将长期负债转化为短期负债.

非单一债权人存在的情况下,一个合理且直接的做法就是想办法令自己的长期借款结算.
然后产生一个新的短期形态的债款.

这个短期借款可以是自己也可以是新的债权人.
而后者,整个系统来看的话,不过是风险所属方的转移而已.

那么对于针对自身的长期转短期的可行性呢?

长期的风险在于一是抵押品的流动性,二是偿还周期本身.

后者在债务人支付能力既定的情况下,只能是某种频繁短期债务置换方式.
即将一个长期债务等价分割为一个连续可展的短期性质的债务.
降低违约风险和反应时间.

前者的流动性问题的话也类似.
改变抵押物流动性的方式要么是变成可分割的.
要么是转变抵押物的构成方式,化整为零.

相对于分割方式需求的法律等其他因素变量更多之外.
抵押物置换拆分的方式可能更合理且现行可操作.

而对于短期借贷本身来说,因为形式上求解的过程使得周期本身是一个常量.
所以风险的分散可能在于额度.

这么一想的话,能串起来的就多了.




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不完整反而受累的情况.












牛来餐馆

看了欢迎来龙餐馆. 总的来说,作为一个院线电影,成品多少是有点不太匹配的. 看起来更多像是一部网络大电影. 尤其涉及到一些大场面特效的时候,可以看得出服化道的降本增效. 剧本层面有一些比较有意思或者说闪光点. 就是试图用一个餐馆或者厨师来串起一个比较宏大的叙事题材. 这个可以说是...