今天和人讨论了一下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++指向常量的指针和常指针

    C++指向常量的指针和常指针 指向常量的指针 通常情况下,可以通过指针去修改指针指向的内容.但是在某些情况下,只希望通过指针去访问指针指向的内容,不想修改.比如只想通过树根结点的指针去遍历输出树中所有 ...

  2. Windows Opengl ES 环境搭建

    环境 OS:win7 专业版SP1 64位 编译器: VS 2013 express 的cl 软件 windows上运行OpenGL ES要用到第三方的头文件,库文件和dll.下载地址 http:// ...

  3. php分页查询

    1.先把数据库里所有的数据分页显示在页面,并在显示数据的表格上方加上查询表单. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transit ...

  4. react搭配amazeui环境搭建

    1.安装node https://nodejs.org/en/download/ 查看node   npm版本 2.安装react 创建新的react单页面应用 npm install  -g cre ...

  5. python爬虫利器Selenium使用详解

    简介: 用pyhon爬取动态页面时普通的urllib2无法实现,例如下面的京东首页,随着滚动条的下拉会加载新的内容,而urllib2就无法抓取这些内容,此时就需要今天的主角selenium. Sele ...

  6. git全部使用步骤

    今天要讲的内容:项目管理和工具 Git:版本控制系统 Less:动态的css语言,提高编写CSS的效率 Gulp:项目自动构建工具,对html,css,js,image进行压缩,合并等操作. 一.什么 ...

  7. HTML文档及标签介绍

    HTML标签 HTML 标记标签通常被称为 HTML 标签 (HTML tag). HTML标签是由尖括号包含的关键词,比如<html> HTML标签通常是成对出现的,比如<body ...

  8. python之数据库(mysql)操作

    前言: 最近开始学django了,学了下web框架,顿时感觉又会了好多知识.happy~~ 这篇博客整理写下数据库基本操作,内容挺少.明天写SQLAlchemy. 一.数据库基本操作 1. 想允许在数 ...

  9. Java设计模式之《外观模式》及应用场景

    原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/6484128.html 1.外观模式简介 外观模式,一般用在子系统与访问之间,用于对访问屏蔽复 ...

  10. MES工具机器列表功能操作

    概述 在机器权限组.限制工序变更等其他情况下,有时候需要重启中间件才能起作用,这样会直接影响其他人员的加工情况.为了改善这种情况,现在MES工具中新加了一个功能,可以单独重启某一个机器,其它机器不受影 ...