今天和人讨论了一下CPS变形为闭包回调(典型为C#和JS),以及Lua这种具有真正堆栈,可以yield和resume的coroutine,两种以同步的形式写异步处理逻辑的解决方案的优缺点。之后生出疑问,这两种做法,到底哪一种会更消耗。我自己的判断是,在一次调用只有一两个异步调用中断时(即有2次回调,或者2次yield),闭包回调的方式性能更好,因为coroutine的方式需要创建一个具有完全堆栈的协程,相对来说还是太重度了。但是如果一次调用中的异步调用非常多,那么coroutine的方式性能更好,因为不管多少次yield,coroutine始终只需要创建一次协程,而闭包回调的每一次调用都必须创建闭包函数,GC的开销不算小。直接上测试代码

CPS:

local count = 

local list1 = {}
local list2 = {}
local clock = os.clock
local insert = table.insert
local remove = table.remove local function setcb(fn)
insert(list1, fn)
end local function test1()
setcb(function() end)
end local time1 = clock()--开始
for i = , count do
test1()
end
local time2 = clock()--调用
while true do
list1, list2 = list2, list1
for i = , #list2 do
remove(list2)()
end
if #list1 == then
break
end
end
local time3 = clock()--回调完全结束 print(time2 - time1, time3 - time2)

coroutine:

local count = 

local list1 = {}
local list2 = {}
local clock = os.clock
local insert = table.insert
local remove = table.remove
local create = coroutine.create
local yield = coroutine.yield
local running = coroutine.running
local resume = coroutine.resume local function setcb()
insert(list1, running())
yield()
end local function test2()
setcb()
end local function test1()
resume(create(test2))
end local time1 = clock()--开始
for i = , count do
test1()
end
local time2 = clock()--调用
while true do
list1, list2 = list2, list1
for i = , #list2 do
resume(remove(list2))
end
if #list1 == then
break
end
end
local time3 = clock()--回调完全结束 print(time2 - time1, time3 - time2)

输出:

coroutine的调用和唤醒/回调,比闭包回调慢不少

(PS. 这里有个插曲,我之前设置的count = 10000000,但是测试coroutine时报内存不足的错误,因此只能下降一个数量级来测试了)

接下来我把单次调用的回调次数增多

CPS:

local count = 

local list1 = {}
local list2 = {}
local clock = os.clock
local insert = table.insert
local remove = table.remove local function setcb(fn)
insert(list1, fn)
end local function test1()
setcb(function()
setcb(function()
setcb(function()
setcb(function()
setcb(function()
setcb(function()
setcb(function() end)
end)
end)
end)
end)
end)
end)
end local time1 = clock()--开始
for i = , count do
test1()
end
local time2 = clock()--调用
while true do
list1, list2 = list2, list1
for i = , #list2 do
remove(list2)()
end
if #list1 == then
break
end
end
local time3 = clock()--回调完全结束 print(time2 - time1, time3 - time2)

coroutine:

local count = 

local list1 = {}
local list2 = {}
local clock = os.clock
local insert = table.insert
local remove = table.remove
local create = coroutine.create
local yield = coroutine.yield
local running = coroutine.running
local resume = coroutine.resume local function setcb()
insert(list1, running())
yield()
end local function test2()
setcb()
setcb()
setcb()
setcb()
setcb()
setcb()
setcb()
end local function test1()
resume(create(test2))
end local time1 = clock()--开始
for i = , count do
test1()
end
local time2 = clock()--调用
while true do
list1, list2 = list2, list1
for i = , #list2 do
resume(remove(list2))
end
if #list1 == then
break
end
end
local time3 = clock()--回调完全结束 print(time2 - time1, time3 - time2)

输出:

回调的消耗仍然是coroutine处于劣势,但已经比较接近了。启动的消耗,由于coroutine需要创建比较大的堆栈,相对于闭包来说还是比较重度,因此启动仍然远远慢于闭包回调的方式。

最后,我把一次调用里的异步接口调用次数,改成到10000次(需要封装成多个函数,否则lua会报错:chunk has too many syntax levels),对比如下(此时次数都改成了count = 1000):

这个时候coroutine的回调消耗优势就上来了。不过一般来说,实际应用中一次调用不可能调用这么多次异步接口。

之后再来测试内存占用

CPS:

local count = 

local list1 = {}
local list2 = {}
local clock = os.clock
local insert = table.insert
local remove = table.remove local function setcb(fn)
insert(list1, fn)
end local function test1()
setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()
setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()
setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()setcb(function()
end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)
end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)
end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)end)
end collectgarbage("collect")
collectgarbage("stop") local count1 = collectgarbage("count")
for i = , count do
test1()
end
local count2 = collectgarbage("count")
while true do
list1, list2 = list2, list1
for i = , #list2 do
remove(list2)()
end
if #list1 == then
break
end
end
local count3 = collectgarbage("count") print(count2 - count1, count3 - count2, count3 - count1)

coroutine:

local count = 

local list1 = {}
local list2 = {}
local clock = os.clock
local insert = table.insert
local remove = table.remove
local create = coroutine.create
local yield = coroutine.yield
local running = coroutine.running
local resume = coroutine.resume local function setcb()
insert(list1, running())
yield()
end local function test2()
setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb()
setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb()
setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb()
setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb()
setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb()
setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb() setcb()
end local function test1()
resume(create(test2))
end collectgarbage("collect")
collectgarbage("stop") local count1 = collectgarbage("count")
for i = , count do
test1()
end
local count2 = collectgarbage("count")
while true do
list1, list2 = list2, list1
for i = , #list2 do
resume(remove(list2))
end
if #list1 == then
break
end
end
local count3 = collectgarbage("count") print(count2 - count1, count3 - count2, count3 - count1)

输出:

coroutine的内存占用确实比闭包回调少很多。

因此,要内存还是要性能,这个看自己的取舍了。

本次测试并不全面,还有很多情况没有测试(比如加上多个局部变量,闭包回调的性能和内存占用可能会受影响)。并且因为lua没有自带的CPS变形,callback hell的存在,导致写代码的体验比coroutine差了太多。因此这个测试主要为打算自己实现编程语言的读者做为参考。

异步编程的两种模型,闭包回调,和Lua的coroutine,到底哪一种消耗更大的更多相关文章

  1. C#实现异步编程的两个简单机制(异步委托&定时器)及Thread实现多线程

    创建线程的常用方法:异步委托.定时器.Thread类 理解程序.进程.线程三者之间的区别:简而言之,一个程序至少有一个进程,一个进程至少有一个线程进程就是在内存中运行的程序(即运行着的程序):一个进程 ...

  2. 基于委托的C#异步编程的一个小例子 带有回调函数的例子

    我创建的是一个winform测试项目:界面如下: 设置: 下面是代码: using System; using System.Collections.Generic; using System.Com ...

  3. Func-Chain.js 另一种思路的javascript异步编程解决方案

    本文转载自:https://www.ctolib.com/panruiplay-func-chain.html Func-Chain.js 另一种思路的javascript异步编程,用于解决老式的回调 ...

  4. Java 异步编程的几种方式

    前言 异步编程是让程序并发运行的一种手段.它允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,当方法执行完成时通知给主线程根据需要获取其执行结果或者失 ...

  5. 你所必须掌握的三种异步编程方法callbacks,listeners,promise

    目录: 前言 Callbacks Listeners Promise 前言 coder都知道,javascript语言运行环境是单线程的,这意味着任何两行代码都不能同时运行.多任务同时进行时,实质上形 ...

  6. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  7. JavaScript异步编程原理

    众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...

  8. 深入理解 Python 异步编程(上)

    http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...

  9. angularjs系列之轻松使用$q进行异步编程

    第一部分关于js中的异步编程 异步编程简单的说就是你写了一段代码,但他不会按照你书写代码的顺序立即执行,而是等到程序中发生了某个事件(如用户点击了某个按钮,某个ajax请求得到了响应)才去执行这段代码 ...

随机推荐

  1. C# 数据类型 数据转换 自己的见解和方式

    数据类型分为:基本数据类和引用类型, 基本数据类型又分为整型,浮点型,字符型,布尔型. 引用类型又分为:字符串型,日期时间,枚举类型,结构类型. int long float = 10.5f; dou ...

  2. 支持缩放的fresco图片控件 —— fresco sample: ZoomableDraweeView

    最近在实现一个类似淘宝中的评论列表的功能,其中要在列表中显示评论图,点击图片后显示大图进行查看,各家app几乎都会有这样的功能. 可以看到,一个体验较好的查看大图的基本功能有, 第一,左右滑动时切换图 ...

  3. SaberRD之瞬态分析

    瞬态分析(Transient Analysis)也叫做暂态分析,一般用于分析含有储能器件的电路在换路后发生的过渡状态,比如分析RLC电路在接通.断开.改接以及参数发生改变和电源突变时,电路的电压电流从 ...

  4. 项目中通过Sorlj获取索引库中的数据

    在开发项目中通过使用Solr所提供的Solrj(java客户端)获取索引库中的数据,这才是真正对项目起实质性作用的功能,提升平台的检索性能及检索结果的精确性 第一步,引入相关依赖的jar包 第二步,根 ...

  5. [笔记]关于支持向量机(SVM)中 SMO算法的学习(一)理论总结

    1. 前言 最近又重新复习了一遍支持向量机(SVM).其实个人感觉SVM整体可以分成三个部分: 1. SVM理论本身:包括最大间隔超平面(Maximum Margin Classifier),拉格朗日 ...

  6. js方法提纲

    Math.random() 日期时间函数(需要用变量调用):var b = new Date(); //获取当前时间b.getTime() //获取时间戳b.getFullYear() //获取年份b ...

  7. 【2017-02-21】分支语句if...else...、分支嵌套、变量的作用域

    语句是指程序命令,都是按照顺序执行的. 语句又分为: 顺序语句:从上到下按顺序执行,挨个执行一遍. 分支语句:选择性执行语句,有的可能会执行,有的可能不执行.满足条件执行. 循环语句: 一.分支语句 ...

  8. 关于 myBatis 中的 jdbcType的细节问题

    前几天上线了一个 版本,第二天到公司的时候,在cat上发现了一个长sql查询非常耗时,几乎把线上的项目搞崩溃了.我马上开始排查问题.最终发现 球队的ID传过来的时候是String 类型的,但是在执行s ...

  9. 怎么用php语言来做文件缓存

    使用缓存能够让我们的程序访问起来更加快速,缓存可以减少对数据库的操作,体验起来更好一些,对服务器的压力也小一些,当然服务速度很快 php文件执行完之后产生的解析完的数据,保存成静态的网页,下次打开的这 ...

  10. Eclipse的Spring IDE插件的安装和使用

    Spring IDE是Spring官方网站推荐的Eclipse插件,可提供在研发Spring时对Bean定义文件进行验证并以可视化的方式查看各个Bean之间的依赖关系等. 安装 使用Eclipse M ...