2021-09-25

一些数据

拉统计局的发电数据看了下.

2021年8月全国7385.5亿千瓦时.
用过去36个月的数据肉眼forecast一下的话,大致是会比去年同期增加500亿千瓦时的样子.

而按照往年的话8月大致会是一个峰值,9-10两个月份会有一个周期性回落,12月的时候大致会跟一年的峰值相近.

所以如果安500亿千瓦时算的话,应该会是在7800亿千瓦时附近的.

另外一组数据是各类发电的构成.

核能风力大概各自在5%左右.

光伏太阳能虽然今年单比是成倍的增长,但也只占2%多一些.

大部分还是70%火力和18%水力发电.

所以总的来说,电力的构成还是以火力发电为主.

然后同样的看原煤产量数据.

2021年8月的产量是33524万吨.
看数据的话,这两年是某种程度上负增长的.

但大致还是在3w万吨的水平上.

那么一个问题就是,如果按500亿千瓦时的同期电力增幅需求的话,对应的需要多少煤呢?
在考虑全部由火力发电提供的前提下.

翻内蒙华电的2020年度报告.

年发电量是330.85亿千瓦时.
同期发电成本是120亿.
拆分的燃料成本70亿.

另外一个就是主营业务另外一项的煤炭收入.
实现营收平均单价是293.14元每吨.

假设其中的燃料成本就是煤炭成本的话,折算下大致是每亿千瓦时需要7.2万吨煤.

500亿千瓦时的话,就是需要3600万吨.
也即是大致占原煤产量的10%左右.

同样的,如果用这个数据折算8月发电量中的煤炭消耗的话.
对应的就是3.7w万吨左右的原煤,大于当期原煤产量的3.3w万吨.

类似的,如果按3.3w万吨计算发电量的话,则大致会在6651亿千瓦时的水平.
这个大致等于2019年数据的峰值.


考虑如果省份按照火电自给率来计算的话.
缺口比较大的省份是 江苏福建江西湖北广西四川.

如果把缺失数据认为是本身不产煤考虑的话,江苏浙江福建湖北广东四川云南.

两者做个交补,比较瞩目的省份就是浙江和广东了.

再一些比较不是那么权威的数据.

2021年8月进口煤炭2805.2万吨,同比增长35.76%.
换算一下就是大致增长1k万吨.
对比500w预期吨话,倒也是有空间.

另一份也是不那么权威的数据是炼焦煤进口渠道数据.
2019年全年主要依靠蒙古和澳大利亚进口,分别占45%(3377万吨)和41%(3094万吨).
2020前10月分别是1972万吨和3512万吨.

这么看的话,很多事情就比较微妙了.

从电力构成上来说,清洁能源尤其光伏是还有比较长一段路要走的.
即使不是不切实际.

而鼓励水电的发展,某种程度上来说,可以算是一种中短期的权宜之计.
毕竟相比于光伏的效能和对应的电力构成规模来说,水电本身的规模和效能还是相对实际一些的.

但考虑到实际的投入周期和生态影响.
尤其如果考虑气候变化因素的话,某种程度上来说不确定因素可能并不比光伏少.

火力发电的话,至少短期内比例上是不太会有什么可见变化的.
也就是说在原材料需求上压力还是比较迫切的.

加上如果考虑预期和周边的产业诸如新能源汽车等因素,电力需求和资源产出本身以及其他政策目标因素的综合影响.
倒像是个越来越无解的局面.

毕竟目前就属于勉强可堪的局面.






2021-08-29

一些计算

前段时间有个段子吧.
大概就是所有人努力的话,只会物价上涨.

简单的描述就是流通的过程都加一个固定百分比,剩下的就是类似现值计算的逻辑.

这里有个隐含假设就是这饿百分比是instantaneously propagate的.
或者v_{t+1} = v_{t}*(1*p) 的作用是即时超距的.

比较现实一点的模型是这种传导是有一定延迟和滞后的.
就像PPI和CPI一样.

因为供应链存在一定的生产以及更重要的账期时间.

这也是为什么有时候三角债是个难解的囚徒困境问题一样.

具体到个体的话,可能可以换一套描述.
比如何不食肉糜的模型.

给定个人的收入income i和对应的支出household,以及一定的再投资investment.
有income_{t+1}  = income_{t} - household_{t} - investment_{t} +  investment_{t}*k

即下期的收入相关与当期的收入减去当期生活/生存开支,以及当期投资项目.

引入investment的一个目的是描述某种程度的收入成长性量化方式.
可以是狭义的资产方面的投资,也可以是某种非物质性投入的折现.

非物质折现的合理性在于,它是基于当期的一些各种购买开销形成的.
比如培训和再搅匀等.

当个人的发展跟社会财富的增长幅度相匹配的时候.
比如简单的收入增长跟GDP增幅保持一致的情况.

那么,应该有income_{t+1} = income_{t} * (1+p)
->
income_{t} *p =  (k-1)*investment_{t} - household_{t}

因为househould水平是跟整体社会水平增长相关的.
即开头的household_{t+1} = (1+p)* household_{t}

再结合递推income_{t+1} *p =  (k-1)*investment_{t+1} - household_{t+1}
->
income_{t+1} *p =  (k-1)*investment_{t+1} - (1+p)* household_{t}
->
income_{t} * (1+p)*p = (k-1)*investment_{t+1} -  (1+p) * household_{t}
->
investment_{t+1}  =  (1+p)*(income_{t} *p + household_{t}) / (k-1)

当存在持续投入模式的情况下,均衡条件要求k>1,即roi应该是正的.

但是,同样地根据均衡条件,如果ROI为负的话,investment_{t+1}也为负的话.
形式上也是能维持个人和社会步调一致的.

此时有
income_{t+1}  = income_{t} - household_{t} + (k-1)*investment_{t}
->
income_{t+1}  = income_{t} - household_{t} - cost_{t}
即当一般认为的增值增长方式实际上是一个纯消费的情况,也是可以成立的.

recall一下,这个支撑的结论是要求个人收入水平跟社会总体平均水平是等同的.
即隐式地有p等同这个约束.

随之而来的就有几种模式.

一个是对于ROI为负的人群来说, investment也是负的.
或者更一般地
由income_{t+1}  = income_{t} - household_{t} - cost_{t},且要求income_{t+1} > income_{t}的话.
让数值都>0修正符号之后就形式上等价于
income_{t+1}  = income_{t} - household_{t} + funding _{t}
->
funding _{t} >  household_{t}
补助程度应该大于生存开销.

类似的,对于正ROI人群来说需要有
(k-1)*investment_{t} > household_{t} 
这可能有些meaning less.

重新考虑investment_{t+1}  =  (1+p)*(income_{t} *p + household_{t}) / (k-1)
随着增长率p的衰减,为保持平衡investment也将逐渐趋向于0.
即趋向income_{t+1}  = income_{t} - household_{t}的模式.

从通胀的角度来说,就是实际上flatten了.

这里的另外一个问题就是前面提到的.
一个隐含的假设是p是瞬时处处一致的.

但是当作一个动态宏观系统考察的话,是存在类似于弛豫时间的一个概念的.

热力学考察这个问题的时候是通过通过过程无关的最终状态模型来描述的.
因为理论上或者说目的描述上是需要converge到某个特定状态的.

在尝试用微观语义去解释的时候,引入的是系综/ensemble的概念.
即把一个大的动态系统划分为相对有限的isomorphic的子系统来解决的.

而通过这种量化方式可以用相对经典的力学框架去描述/解释状态变迁的驱动过程.

整个系统的状态converge就由这些小的构成部分的操纵来完成.

有时候你也很难说这种宏大叙事的理论框架是错的.
因为它确实能解释相当程度,并且具有一定的先验性质.

当从微观层面来看的话,又过于mystery/unstable.
以致于需要用概率去描述一些往复和纳入极端情况.

2021-08-15

路径无关

之前翻资本论有个比较有趣的衍生点.
即交易是非对称non-transitive的.

也就是所谓的使用价值和交换价值的区别.
一物之于一个人的utility是不一定等价的,所以交换价值是不定的.

从形式上来说,如果给定A=B.
那么A*C ?= B*C 是不一定成立的.

也就是说任意两个商品的交换价值是不可比或者说arbitrary的.

即relative/相对价值的概念.

而从以上形式来说,这个概念脱离其定义时刻的前后都是无意义的.
因为相对/交换价值具有任意性.

那么从理论上来说,所有交易的标价/交易价值都是可以有任意值的.

但实际上来说,真实世界里某一个商品通常具有相对确定的普适价值.

资本论给出的解释是基于劳动力/labor的ensemble.
商品的产出由一系列基准的劳动力投入来衡量.

而劳动力这一个概念又是由所谓的无差别来标准化的.

因为劳动力系统的计算中,劳动力本身被视为一种常数,所以它的标量是相关于时间的.
如果在现实中纯粹应用这个模型的话,会导致一个无视/反效率的存在.

一个例子就是机器生产与人力投入的例子.
两者的效率差导致人力投入的时间更多,但实际价值更低.

那么什么是无差别呢?

它是一种全行业的平均劳动投入.

这是一个稍微有点递归的定义.
注意,无差别是平均的话,那么什么是平均本身呢.

比如当有新技术投入的时候,这个平均值是如何变化的呢?
或者出现减产或者局部地区差异的时候,这个平均又是如何定义的呢?

热力学系统里又个类似的概念.

什么是温度.
温度是一个描述某个特定系统在热平衡时的整体状态的一个度量.

即不管平衡时内部粒子/micro system如何变化,从宏观上来说,它是一个平衡的/平均的.

用这个来解释的话,交易就是一个热力学系统的平衡过程.
或者说是其微观细节.
而价格或者说市场的概念是一种统计意义上的平均值/宏观量.

基于宏观角度的模型解决的事宏观层面的状态变迁依据和计算.

比如一种货币政策目标或者市场利率目标则类似于一个热力学系统的状态计算.
它的实现路径通常来说,并不是理论设计的一部分.

因为它的数学描述的构建就是依赖于这种势函数/路径无关性.

将压强体积等作为变量融入到系统模型到目的不过是为了便于通过这种实验易控制变量来达到状态变迁的predictable/manageable.

类似的就是宏观经济中的各个观测指标.

那么微观意义呢?
它具有微观角度的实用性/可解释性么?

热力学假设用经典力学推演微观细节的时候有个gibbs paradox.
大致就是从微观粒子角度套用热力学结论的时候,两个相同状态的系统混合时演算出现了熵不为零的情况.

熵是热力学系统描述能态变化积分路径无关的一个产物.
类似于描述一个系统不同状态的差值的函数.

即如果两个系统状态系统,那么它们的叠加状态应该也是相同的.
因为没有实际的状态变化.

但是计算的结果却是非零.

这里给出的一个补丁是计算微观状态的时候除以一个1/N!.
给出的intuition解释是N!作为N粒子系统的所有组合状态的可能数量.

但直觉上应该是为了数学上的便利/优雅性.
因为修补过程利用的斯特林公式,将lnN!近似展开得到的熵零结果.

但至少可以认为这个跟N组合相关的intuition有一定的合理性.
即宏观和微观存在一个状态空间大小N!的近似关系.

也就是基本的平均概念.

一个宏观系统要用路径无关的状态函数描述的话,那么它对应的微观表现是具体粒子状态的平均化函数.


2021-07-10

关于Go的一些相对优势

最近用Go重写了之前基于Envoy的一个东西.

性能从原来的32 CPU 20w+ 18ms latency变成40 CPU 40w+ 10ms.

这个结果初看没什么可比性.
但是拆分起来还是可以谈一谈的.

Envoy的版本大概就是到32 CPU的时候就上不去了.
也就是说之后的性能并不随着CPU资源的增加而scale.

这个之前略微谈过.

大致模型就是:
Time = sys_cost + read_fd*(read_bytes*cost_per_read_byte) + write_fd*(write_bytes*cost_per_write_byte).
sys_cost是固有的eventloop的开销.

后两项是抽象的读写每字节对应的动态抽象成本.

说是抽象的原因是考虑到实际的业务场景每请求的处理开销/代价是不太一样的.
所以规约到一个字节角度的动态参数化类函数描述.

然后假设K个eventloop之后的一部分请求能够得到响应.

也就是会有
latency = K*Time = K*( sys_cost + read_fd*(read_bytes*cost_per_read_byte) + write_fd*(write_bytes*cost_per_write_byte)) / N
->
a*sys_cost + b*N*cost_per_read_byte +c*N*cost_per_write_byte

因为cost都是某种特定场景下的常数.
所以最后可以规约为
latency = const_cost + N*cost_per_hybrid_byte

所以当小于32 CPU的时候,随着CPU增加,每个CPU需要处理的N变小,所以会随着scaleup.
而当>32 CPU的时候,后一项已经significantly小于,const_cost了,所以继续增加并不能有什么改善.

所以这是它的一个缺陷或者说应用场景考虑.

而Go由于runtime的关系,模型会有一些比较不一样的地方.

因为它的IO和channel/mutex等操作会触发groutine等调度.
从逻辑上保证P是burning cpu in the right way.

也就是说,当有类似操作的时候,go会让专门的线程(netpoller/sysmon等)去做blocking的工作,而让M继续执行runtime user level的meaningful code.

所以如果不考虑IO等blocking call的capacity的话.
它的latency大致就是直接的
latency = k*CPU/(N*c)
k为一个CPU提供的请求处理能力常数.
N为请求数,c为每请求需要的cpu资源数

所以简化下就是一个简单的
latency=a*CPU/N
的关系.
性能将随着CPU数的增加而增加.

这个跟benchmark的结果的部分结果也是可解释的.
在CPU/GOMAXPROCSS<40的时候,CPU资源的增加和性能的提升是准线性/sub-linear的.

但是继续增加的话,并不能再进一步地提高性能,甚至从抖动的角度来说,还有一定的degrade.

所以,这里就必须考虑把IO等的调度/capacity考虑进去了.

revise一下就是:
latency = (k*CPU)/(N*c-f*blocking)
f代表一个mean的平均调度周期内的从blocking->ready的G的数量.
以及bloking为对应的执行后续的开销.

intuitive的理解就是,divider是原本能调度的非blocking的user level的G减去必须调度的blocking ready的G.

这样的话,回头解释就是.
当CPU<40的时候,本质上是CPU bounded的,也就是其实并不能完全游刃有余地处理全部请求.
因此能产生的ready的数量是随着CPU数当增加而增加的.
所以是一个sub-linear的关系.

而当大于40的时候,基本上可以解释为是CPU已经不是瓶颈了,而主要是runtine处理IO/调度的能力.
所以这时候的f会相对地大量增加.
如果此时f*blocking是dominated的话,那么CPU的增加就会差不多近线形地被f的增加所抵消.

这样的话,就可以解释为什么继续增加CPU资源的时候,甚至可能会有degrade的情况.

这时候可能可以考虑的就是要么减少G的数量,要么减少syscall等blocking call的数量.

本质上,后者也是减少G的数量.

这个在之前一些版本的实验/探索里大致也是有部分实践可以说明的.

早期的一个实现采用了比较多的channel.
比如读写分开,处理数据分开.处理数据的过程也有channel.

这样的实现是会产生多几个数量级的G的.

所以后来的做法就是缩减了一些到公共channel,以及合并了一些原本独立的IO channel/goroutine.
这样的话,就减少了f和调度器的开销.

另外一个尝试就是把部分的写IO换成buffer io.
也就是合并syscall等部分.

但是现有的API做网络buffer io并不是很友好.
因为你需要手动地flush保证一定的latency的keep system running.

不然可能因为没有flush导致上下游都在等对方的IO.

所以要么就是加timer去flush.
要那么就是嵌套的select,外层non- blocking,然后default分支里再加blocking的select.

无论是哪种方式,都需要额外引入一个channel和goroutine.

这样的话就变成了syscall和goroutine/channel对于scheduler来说孰轻孰重的情况了.

而如果选择后者的话,代码复杂度会上升一些.
加上实际测试的效果似乎并不理想,所以最后这部分还是改为了直接syscall的方式.

当然,这里还有另外一个方向就是用mutex.
也就是write的时候用lock而不是channel.
这样的话,就可以在不用buffer io的前提下,speculate地合并一些写.

加上mutex本身也会触发调度,形式上和channel的效果类似.

但是它的问题在于不scale.

在限制CPU为1或者相对低的情况下,mutex通常能走fastpath.
也就是简单cas就能成功,不会触发调度,所以表现是相当地promising.

但是当CPU数量上去之后,资源抢占变得比较激烈,大多数时候都是slowpath,结果就不是很理想了.

所以这里一个思路就是对于mutex和channel的选择.
在抢占相对没那么严重的情况下,mutex似乎是比chan更好的一个选择.

回到IO的问题上.

这里的本质是Go目前没有一个比较好的感知IO ready情况的一个API.
理想状况下,需要是一个类似
func Write([]byte) <-chan IOResult
的API,
这样的话就能比较方便地做
for{
   ioCh:=w.Write(buf)
   select{
     case r<-ioCh:
       //if r.Err() != nil
     case newBuf <- bufChan:
      // gather more
      //. buf = append(buf,newBuf...)
   }
}
之类的.进一步减少groutine和syscall.

当然,初看起来这个自己实现也就是make一个channel然后go一下的问题.
ioCh := make(...)
go func(){
  defer close(ioCh) 
  size,err:=w.write()...
  //ioChan<- &{size,err}
}()

这里的问题一个是构造g的开销.
一个是不可避免地还是增加了一个g.

尽管看目前的runtime实现对g结构是有个resue/pooling的.
但是,多多少少还是涉及一些初始化的问题/开销.

而且更重要的是形式上来说,相比一个continue polling的goroutine并没有什么优势.
反而可能产生了更多的问题.

如果从runtime层面去做的话,可能就是在netpoll wait/pollDesc wait的相关路径更改/增加新的ready的回调方式了.

比如当前是的路径是挂起当前的g.

而按照上面API设计的话,可能就是netpollready的时候加个special case.
看是否有对应的g或者新的代表return chan的struct field,然后fake一个g或者更直接地,通过 channel kick start对端的g就是了.

当然,实际会更复杂一些.
比如加入之后还需要考虑pollDesc里的各个timer的处理等.


2021-06-29

从萨尔瓦多谈起

前段时间有个关于BTC的消息,就是萨尔瓦多将其作为一个官方货币/法币.
细节上来说,是发行了一个挂钩BTC的另外一个虚拟货币,作为一个流通使用的货币.

这个有趣的点在于提供了一个以前没想过的层次或者说侧面.
也就是像对于这类没有什么经济支柱产业点小国来说,BTC的可能是某种程度上的积极意义所在.

一般来说,一个国家如果没有什么完整的工商业体系,或者说没有什么支柱经济产业的话.
那么大多数时候的商品是需要通过国与国之间的跨国商业活动实现的.

而不同国别之间就存在了法币不同的情况.

这样的话,就自然而然需要有一种互通的汇率存在.

理论上来说,这个是两国之间协商互认的一个事情. 
以什么比率是可以私自决定的.
因为交易只设计两方.

但实际上这种理想情况是不太可能存在的.
一般都是多国贸易.

那么这样带来的一个问题就是种类繁多带来的系统复杂性.

所以,一般来说,会选择一个统一的可信货币作为本国汇率的锚点.

也就是一般所说的挂钩美元.

当然,也可以直接将美元作为自己的法币.
从大的方向上来说,有区别,但不大.

以锚定方式的话,至少从形式上保留了一定的货币独立性.
对于美元的变化相对来说,有一个缓冲区/时间因素.

而直接以美元作为法币的话,自然就没什么可以操作的了.

挂钩美元的话,自然理论上也可以自由定价.

比如一些汇率干涉和管制手段,单方面的维持某个牌价.

这个的话,也自然对应的会有市场驱动下的黑市牌价之类的.
作为一种群体性的价格纠正/共识机制.

所以除非是采用严格的汇率管制手段,限制/管制汇率交易,不然定价和平价总会回归到市场行为.

也就是说,当采用锚定美元的方式,并且不做或者做不到外汇管制的话,那么汇率价格的形成就会落到国际市场上.
即相对直接的买入卖出持有抛售行为上,从而形成一个公信价格/汇率.

因此为了保证相对稳定和可信的对美元汇率,一国的就需要针对美元和自身货币在市场上做相应的维持动作.

而一般他国对弱势国家的货币持有意愿是不高的.
一个是没有需求.
另外一个是风险敞口大.

当美元贬值的时候,相对来说就是本币升值.
而为了维持固定汇率,一国就需要做相应的贬值动作.

理论上来说,作为市场上几乎唯一的买方,唯一能做的就是抛售相应的美元资产,买入自身货币.
以维持对应的比率关系.
而这样做的影响就是减少了美元储备.

类似的,当美元升值的时候,就需要追加美元储备.
而追加的方式并不能通过本币的外汇市场操作达到,而需要通过以其他可能的方式增加对应的美元资产.
不然的话,固定汇率就失去了平准依据.

所以对于小国来说,需要有相当大美元资产储备和缓冲来保证国内经济的稳定性.
使得不会因为美元的波动而影响日常经济.

但小国之所以是小国就是因为没有太强的经济优势和创汇能力.
加上如果不做管制的话,国家财政是不太可能有大量甚至足够的美元储备抵御波动的.

尤其如果一国比较动荡,自由兑换的美元可能就很难通过循环回流到国库,从而形式和结果上的,进一步削减美元储备,降低本币信用,形成一个负反馈.

那么回到开头的萨尔瓦多的做法.

形式上来说,它是引入了另外一个资产标的BTC,作为锚定的目标.
并且理论上允许虚拟法币和BTC的兑换.

也就是间接的,维护了虚拟法币和美元等其他货币的一个一定汇率.

单本质上来说,因为手续上需要经过虚拟法币到BTC到一个兑换过程,所以实际上可以认为是某种形式的汇率管制.
所划的防火墙不是拒绝交易,而是作为代持.

只要虚拟法币所代表的BTC不实际地流出本国,对于本国来说,就是一定程度的dark pool操作.
并不影响BTC的对应储备.

这种方式跟美元的方式区别在于,最终用户得到的是BTC.
本质上不是一个可以快速流通变现的东西.
而当得到的是纸钞美元的话,一般情况下是可以快速流通的.

所以这是BTC作为外汇管制手段的一方面.

另一方面,因为限制了美元的持有,所以在黑市方面的价格形成压力和影响会相对地没那么大.
也就是在汇率价格形成机制上,对抗因素减少.

当传统情况下需要对锚定的美元价格动作的时候,由于缺少对手盘,所以少了出于套利的交易目的行为.
这样的话就允许不那么及时甚至不维护对应的汇率关系.

因为即使本币在市场上丧失信用,但并不影响一国以美元储备进行的商贸活动.
而由于国内锚定的是BTC代理的美元价格,所以也不会因为原有的本币波动造成影响.

当然,这里假定的是BTC本身的波动可以忽略.

这样的话,形式上就有点类似于是直接使用美元作为本国法币使用了.
因为无论对内对外来说,都直接间接的是以美元等值计价的.

唯一的不同在于,BTC的代理管制所构造的dark pool,使得一国可以在以类美元法币的形态,采取一定程度上的自主货币政策.
即通过调整dark pool的杠杆率来实现相对弹性的国内价格调整.

这点是在原有框架内,不做外汇管制的经济弱国里做不到的.

而如果都采用这种方式的话,可能确实对经济体系是一个不小的冲击.

因为国际贸易回归到ledger状态. 
单一或者多元强势货币的波动就可能没那么直接的影响.

而且这种体系天然地把国内经济活动和国际间经济活动隔离的.
原则上,国内到国际都是可追溯的.

以及因为本质上是存在防火墙代理隔离的两套系统,所以相对来说可以是彼此独立的经济体形态.
也就是说,可以不存在关联影响.

那么,这说明BTC确实有一定积极意义么?

也不是.

抛开本身的波动性和负面因素.
这套系统需要的不过是一个国际国内的代币缓冲隔离带.

所以它可以是任何形式的数字货币.

甚至是不是货币形态都不重要.

因为本质是个制度性要求.









2021-05-16

Eventloop及其他

最近一段时间写Envoy有些比较纠结的地方.

一个主要是eventloop模型,叠加C++本身的一些特性或者说问题.

写Java写Go的时候习惯于executor.submit或者go something.
毕竟closure带上上下文,交给调度器处理不在context的逻辑,一来心智上没什么负担随借随还.
二来毕竟多核,指不定并不实际影响latency,或者影响甚微.

而eventloop模型负担就比较重了.

因为本质上是一个topdown的设计,需要手工介入分时的概念,去衡量那些应该优先调度/调整顺序.

一个例子是写redis的proxy,外加一些协议层面的统计信息.
在实际debug情况下的测试结果,把统计关闭和打开大致会有几倍的性能差距.

原因也比较tuition.
相较于比较简单的协议解析来说,metric的各种lookup和string的allocation等反而开销更大.

所以一个直觉的处理方式就是异步化处理.

但是就eventloop模型来说,单线程其实没有所谓异步化处理.
最多也不过是对work queue的一些顺序调整.

当然,理论上来dealy到io事件之后处理是有可能利用一些本来就会花在poll上的时间的.
但也只是理论上.

实际上如果说真地在跑或者有了一定负载的话,大约也是io busy loop的.
所以delay到哪里其实代价都差不多.

另外一点就是envoy本身在libevent2上封装之后暴露出来的delay api要么是不停地塞到work queue的head,导致诸如delay([](){ doSomething(); delay(other)})这样的调用会直接递归出不来.

要么是timer base的delay方式粒度最小是1ms.
这多多少少不是让人很舒服.
因为是一个确定性的latency,而且不一定能容忍.

所以eventloop叠加callback或者coroutine之类的实现,本质上还是一个topdown的思维模式.
能做的最多就是次序上的调整,和基于此的一些speculative的开源节流方式.

而如果在这个思维模式下,加上C++的话又会有语言层面的一些纠结点.

既然已经在抠运行时调度的一些开销问题了,那么应对这些动作本身的开销也会多多少少考虑一些.

像比如要做delay的话,就必然涉及到把context带过去的问题.
直接如上的用lambda传递的话,在其他GC语言里可能就没那么多考虑.

毕竟即使考虑能做的也不多.
像Go可能还能做一些逃逸分析方面的考量,利用stack.
而Java类的基本就不用花这些心思了.

C++ lambda/std::function等就基本意味着要做value pass,触发copy.
而且一般来说就是memory allocation了.

当然理论上来说,可以写个stack allocator.
但对应的要做或者改造的地方就比较多了.

比如stack是哪个stack,如何保证这个stack是safe/accessible的.

一个简单的思路就是类似go的g0或者grouting stack.
用eventloop的frame层面的stack.
然后不够了fallabck会std allocator之类的.

但至少在envoy的场景这个是不太能做的.

毕竟寄宿在一层框架之上.

另外一个思路是可以在filter或者filter factory层面preallocate.
也就是在所属框架必然有的orignal/context object本身开辟一部分.

实际上也还是把自定义filter等组件作为一个goroutine stack来做allocation了而已.

这里带来的另外一个问题是这个allocation的lifecycle问题.

what if delay的operation执行的时候,这个hosted object free了?

不过在Envoy层面来说,这个问题不算大.

一来是从设计上来说,用了比较多的smart pointer,尤其比较严格的uniq_ptr.
object ownership关联关系方面的负担没那么重.

二是本身暴露的借口大多也有利用object自身的一个smart pointer做liveness check的.
在callback的时候会检查一下,所以也不用太担心.

但即使这样的话,也需要对lambda context bind的object做一些比较特殊或者类似的设计,以保证能够像envoy的这些security一样work.
而这样就意味着,如果没有follow这些practice的话,就容易或者说必然segment fault了.

关于这这点,一来是很难或者说不太容易做到这种API设计层面的约束.
二是即使做到了,接口结构层次也会显得不是太优雅.

总之,就是不太容易或者基本做不到在eventloop模型下,做一些优先级代价方面的取舍.

因为实际上无论如何都是要在一个线程内执行,只是先后的问题.
从线程自身的时间线来说,长度开销没有太大的区别.

那么跨eventloop调度呢?

Envoy的public API或者能使用到的API角度来说,是没有直接获取其他eventloop对象的.
在Envoy里叫做dispatcher.

但也只是不能直接.

因为它还提供了一个thread local相关的API借口.
是通过一个中心的main dispatcher向其管理 worker dispatcher post message切入到这些worker的context的实现.

也就是说能通过它间接地实现eventloop之间的非定向消息传递.
而既然又了非定向,结合一些比较ugly的诸如dispatcher name之类的标识ID之类的,也能实现定向message delivery.

只是做起来比较恶心.
而且还需要做一些额外的synchronize,保证在所有event loop确认触发了相应的事件/回调.

但这样一个明显的问题就是这种同步带来的不适感.
因为直觉上是对于各个eventloop的执行或者说性能不太友好的.

一个相对能够类比的例子就是GC的stop the world.

理论上体现到曲线上可能是会有一些毛刺感.

当然,就delay/async化metric统计来说,可以不用管同步问题.

一是不是特别重要.
二来,如果metric的值生成是instant/in place的,不同步处理最多也就是expose的方面有delay,影响不是很关键.

这样的话,另外一个点就是pass这些message的方式问题.

比如简单的一个std::list<Metric>,抛到另外一个eventloop的话,必然涉及到list的线程访问安全问题.

这个简单粗暴就是lock/mutex.
但考虑到像前面提到的,metric的生成速率本身比较高的话,mutex的同步开销就可能变得显著了.

一个相对优雅一些的做法是closure的binding方面做手脚.
把list move semantic,做clear cut,剥离竞争关系,一边是清空,一边是接管并顺带处理析构相关的开销.

这个算是一个观感尚佳的解决方案.

实际的问题是,这个异步eventloop可能处理不过来.
尤其前面场景,开启metric和关闭的性能是几倍到几十倍的差距.

这样的话,即使是1对1的eventloop配比关系有可能不够.

即使不考虑极端情况下worker eventloop已经把CPU跑满的情况,也需要考虑配比系数是怎样的.

而如果配比系数是adaptive的,那么随之而来的就是scale的算法和更重要的计算所需要用到的数据的synchronize或者不那么精确的approximate/estimate.
再就是进行scale时候的各个eventloop的同步以及已有callback/work queue如何/要不要re-balance的问题.

再极端一点的就是算法稳定性问题相关的,scale interval问题.
以及随之而来的re-scale的代价值不值等的问题.

最后下来可能就是重新发明了forkjoin pool/go primitive而已.

所以有时候会想,eventloop+manual memory management之于gc+runtime scheduler算是存在着代际的差异了.

至少从生产力层面来说.

而且如果说前者对于实现细节更有取舍控制权的话.
至少对于C++的实现来说,是需要有一些保留意见的.

像这里用到的例子,有比较让人不满意的差异的主要来源于debug build.
如果切换到release到话,可能差异就不是特别显著了.

这个imply的可能就是上古名言,不要过早优化所指向的真实含义.

编译器能做的优化可能比你想的更dirty/ugly.

像上面提到的lookup/string allocation等的开销.
再怎么写,可能不如丢给编译器inline加大范围的SSA/register allocation等.

就像后者的再怎么写,不如JIT的赌一把.

2021-04-18

定向

前几天的一个随想.
大意就是BTC的单位价值,对于不同时期的产出单元应该是不同的.

因为用马克思的剩余价值角度来看的话,后期BTC的算力投入是比前期大很多的.
在假设算力成本没差别的前提下,由于投入不同衍生的结果价值自然应该是不同的.

然后这里反过来的一个点就是现代货币理论的一些东西.

现代或者说西方经济学的一个重要概念说信贷.

信贷是一种对货币流动性加速的一个发明.

在没有资产负债表概念之前,交易是当场结算的.
而借贷的产生衍生出的信贷实际上就是一种异步就算.

信贷/credit则是在此基础上的进一步泛化.
如果把借贷作为是一种被动债务产生的话,信贷则是相对主动产生的.

主动就代表一定的主观性.

比如贷款评估的时候,一个资产对应的货币量是多少,这个本质上来说是非常主观的.
因为逻辑上,这个只需要借贷双方认可接受就行.
跟实际上或者说第三方认不认可没有关系.

所以一个东西,A认为能借/贷10,B认为能借/贷10w是不矛盾的.

但是把这个债务打包作为资产进行二次或者次级交易的时候,引入的是一个新的第三方.

理论上来说,只要这个新的第三方C认可,交易也可以进行下去.

以此类推地,可以到N.

问题在于这种基于主观性互相认可的匹配具有相当的随机性和不效率化.
因为撮合具有相当的随机性.

所以为了便于流动和转手,需要有一个市场存在.
而市场的构成是对于某个规则有明确认可度的个体集合.
目的是减少撮合交易的摩擦或者是匹配难度.

也就是说,市场内的个体需要对交易的物品有基本的定价共识,从而减少不必要的重复价格互认过程.

于是就有了各种估值模型.

所以本质上来说,一个或者说一类交易品的价格形成过程更多的是一种consensus.
参与个体的一种群体默契或者说潜规则认同.
模型或者公式的正确性理论上来说,并没有什么实际价值.

一个简单的例子即使股票市场的不同公司估值模型.

拿某中止上市的某集团来说,会有以科技公司估值和以金融公司估值的市值差异.

这个的字面理解就是不同的公司类型有不同的估值模型和方式.
因为不同行业的公司有着不同的经营模式和现金流/盈利模型.

而之所以需要有不同的领域模型来解释和推算是因为如果按照纯粹的盈利/收入/分红能力来price的话,有些公司是没办法有正的价格的.

比如众所周知的互联网公司,在初期可能是没有盈利能力的.

活着更一般的,没有盈利活着弱盈利能力的公司,是没办法有一个让人舒服的价格的.

即使不考虑市场交易对价格促成的影响.
单纯就一个新上市公司如何估值来说的话,如果它没有盈利能力是不是就不能有一个价格了呢.

这是合理但不feasible的.

因为证券化就是为了融资,而需要融资往往就是因为缺乏当前的现金流.
所以,如果按照这种合乎逻辑但不实际的思路的话,证券市场就是自相矛盾的存在.

因此需要有一个默契的共识,来促成交易的表面合理性.

对于已经有类似已经上市了的公司,就会有按照现有公司的一些数据做基本forecast得出的估值方式.

intuition是既然同类在现今市场上按照这个行成逻辑有这个现实价格,那么通过类比自然能逻辑上合理地得出一个预测当然值.

这个看上去解释的过去的就是所谓的市场共识.
它不一定需要正确,但是需要是大家默契接受的评价标准.

之所以说不一定正确是因为,即使模型数据能吻合,但是实际上这个是长期市场交易撮合形成的价格.
代表的是过去特定历史时期的特定历史事件和市场情绪构成.
这个并不代表将来新公司能有相同或者类似的发展机遇和路径.

所以,本质上来说,这是一种默契的对诡辩的普遍接受.
毕竟在一定程度上make sense.

既然价格/模型的形成是在特定场景下的特定自我说服的话.
那么带来的一个问题就是,价格对表的货币当量.
但是,价格的形成因素又不具有相对可比性,但却能有有具有可比性的货币数量来互相衡量.

这就比较有趣了.

一个可能稍显复杂但是可以说明的例子就是不同的crypto currency之间的价格问题.
它们都可以跟现实法定货币做兑换.

同时每个crypto又有自身的相同/类似或者不同的产生机制,和对应的各异的波动性表现.

另一个例子就是更一般的任意两个商品的价格对比.

本质上来说,它们都是不可比的.
但是,通过或者这种一般等价物使得交易可以促成.

问题在于此时的货币作为一般等价物的合理性.

最初的等价合理性在于点对点之间的peer认同.
次一级的是公共市场的参与者内部间的consensus.

一般的认同共识则是基于日常的广泛生活交易串联穿插起来的.

比如市场A的共识对于市场B是没有意义的,因为B中的个体不需要A.
但是AB可能需要一个共同的C市场的需求,所以他们有了共同的交集和货币流动.

到这里的话,另一个有意思的推论就是.
当A的generate的量或者速率/成本低于B的时候,在通过C交易的时候,A就B有着货币产生效率上的天然优势.

比如A市场的单次交易能产生X数量的货币利润,而B内的对等交易只能产生0.1X的货币盈余的话.
在共同市场C上,A对B就有绝对的货币量优势或者说购买力优势.

所以,这就是基于信贷的货币理论的一个必然结果.
它实际上抹去了一般等价物的基本先验,而只保证的相对/有限/limited等价的存在.

另外一个问题是记账方式带来的危机周期性.

因为基本的逻辑都是延迟结算或者说option运作.
在假设vest/realize没问题的时候,资产负债表是可以配平的.

但是如果中间环节有问题的话,自然就会有不对成的情况.
而不对称的结果就是账目上的货币湮灭/消失.

当维持系统运转的货币量出现流动性或者说根本性的数目缩减的时候,自然是不太期望能够继续正常运行的.

虽然理论上,可以通过同样的合理虚构填充这部分流动性和数值,也就是通常所说的干预手段.
但前提在于能防止账目的进一步崩坏和没有其他新生的账目问题.

而这点事没办法保证或者说不可能的.

因为总存在考虑不到的链条节点.

于是表现出来的就是必然的周期性.

所以似乎信贷是一个可以解释很多事情的东西.

它的根源在于对一般等价的模糊化处理.
或者说没有一个相对明确的定义.

因为它的等价概念衍生于一个行为,也就是交易本身.
而跟交易涉及的标的物是没有什么关系的.

从形式上来说,交易物本身甚至可以不是实际存在的.
比如信贷自身.

那么马克思的一般等价定义呢?

它基于的是所谓人类劳动.
这是一个相对明确的实体或者说概念定义.

但是它的问题在于.
一个是本质上来说,它是undefined的,不同商品的无差别劳动是不可测量的.
在实际运用中,它的量度还是通过市场行为来观测的.

也就是说实际上并没有解决模糊化这个问题.
或者说至少没有直接地解决.

另一个是对于现存的信贷行为,它是解释不了的.
因为即使套用框架,你也没办法将一个未发生的事情做无差别劳动计量.
至少,不能解释估值概念/框架.

但是它提供了一个思路.
就是不再以交易作为货币一般等价的概念基础.

这里带来的一个概念可能就是货币政策可能不再以量,或者至少不再最终以量的多寡作为最终评价/衡量政策效果的标准/结论.

因为量的概念在不同的交易当中是不可比的.

反过来也就是说,在不同的渠道,不同的量的干预/扭曲程度/方式是有不同意义的.

唯快不破

看最近DeepSeek的更新频率好像赶上来了. 差不多可以做到一个月一个版本. 而且就最近的版本基本都是post train的增强. 尤其前两天特别提到了RSI和所谓雷霆大思考的问题. 想想,大概就是在模型基本能力足够好的前提下,agentic的post train可能是短期内一...