今天和人讨论了一下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. Codeforce 水题报告(2)

    又水了一发Codeforce ,这次继续发发题解顺便给自己PKUSC攒攒人品吧 CodeForces 438C:The Child and Polygon: 描述:给出一个多边形,求三角剖分的方案数( ...

  2. ios用户体验

    如果转载此文,请注明出处:http://blog.csdn.net/paulery2012/article/details/25157347,谢谢! 前言: 本文是在阅读<ios用户体验> ...

  3. Ionicons的使用

    安装 参考Ionicons npm install react-native-vector-icons --save 这时候可能会报错:npm WARN deprcated lodash@4.2.0: ...

  4. 原生js中slice()方法和splice()区别

    slice()方法和splice()方法都是原生js中对数组操作的方法. slice(),返回一个新的数组,该方法可从已有的数组中返回选定的元素.例如:arrObject(start,end),sta ...

  5. error LNK2001: unresolved external symbol __beginthreadex

    解决方法: project->settings->C++>category->code generation->Use runtime library选Debug Mul ...

  6. Eclipse-ee 启动Tomcat后浏览器无法访问Tomat,并且Web项目服务部署

    环境: Ubuntu 14.04 + Eclipse-ee +  Tomcat7 问题: 在Eclipse中建立Server时选择的Tomcat7,Server的运行时选择的时自己安装的Tomcat目 ...

  7. [LeetCode]Spiral Matrix 54

    54.Spiral Matrix Given a matrix of m x n elements (m rows, n columns), return all elements of the ma ...

  8. Java 注解 入门

    这几天在学习Spring3.x,发觉现在许多框架都用上了java注解功能,然后自己就对java注解这方面初步学习了一下. 首先,注解跟注释不是一个意思,也根本不是同一个事物. 注释就是我们平常平常中对 ...

  9. Java 数值类型以及计算

    前段时候写了一个对外提供的接口,其中有一个数值校验的计算.在测试的过程中发现5.6-1.6 != 4,在反复的测试过程中发现double类型的数值为有精度丢失的现象,看来还是基础知识不牢固,所以就在网 ...

  10. Codevs2018 反病毒软件

    2018 反病毒软件 时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond  查看运行结果   题目描述 Description 其实这个“反病毒软件”(Anti ...