今天和人讨论了一下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. IBM面试记

    link:http://kb.cnblogs.com/page/107213/ 话说其实我很久没有被正经面试过了.一开始去微软实习自然经过了经典的笔试和几轮面试,然后去了朋友的创业公司并立即被激动集团 ...

  2. Keepalived安装与配置

      下载并解压Keepalived安装包到两台nginx所在的服务器   192.168.200.1   192.168.200.2     执行编译安装(安装目录设置为 /usr/local/kee ...

  3. 【转】Hibernate和IBatis对比

    原文地址:http://blog.csdn.net/ya2dan/article/details/7396598 项目也做过几个, 使用IBatis就做一个项目, 基本上都是使用Hibernate, ...

  4. console深入理解

    ["$$", "$x", "dir", "dirxml", "keys", "values ...

  5. js中的面向对象入门

    什么是对象 我们先来看高程三中是如何对对象进行定义的 "无序属性的集合,其属性可以包括基本值.对象或者函数",对象是一组没有特定顺序的的值.对象的没个属性或方法都有一个俄名字,每个 ...

  6. C# TCP多线程服务器示例

    前言 之前一直很少接触多线程这块.这次项目中刚好用到了网络编程TCP这块,做一个服务端,需要使用到多线程,所以记录下过程.希望可以帮到自己的同时能给别人带来一点点收获- 关于TCP的介绍就不多讲,神马 ...

  7. 面向对象 "一"

    1:面向对象不是所有情况都适用. 2面向对象编程 a:定义类 calss Foo: 注意顶一个类的时候首字母必须是大写 def (方法一)(self,bb) pass b:根据创建对象,创建和Foo实 ...

  8. 《深入理解Java虚拟机》学习笔记之内存分配

    JVM在执行Java程序的过程中会把它所管理的内存划分若干个不同的数据区域,如下图: 大致可以分为两类:线程私有区域和线程共享区域. 线程私有区域 程序计数器(Program Counter Regi ...

  9. swiper遇到的问题及解决方法

    上一家公司主要是做些微信的场景页啥的,然后需要快速开发,所以第一时间选择了swiper,毕竟开发快速,比较方便然后api多(虽然并没有用很多),用这个的朋友.缺点的话有时候用了就晓得,下面开始讲讲,第 ...

  10. wemall app商城源码中实现带图片和checkbox的listview

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享其中实现带图片和checkbox的listvi ...