今天和人讨论了一下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. [Kafka] - Kafka 安装介绍

    Kafka是由LinkedIn公司开发的,之后贡献给Apache基金会,成为Apache的一个顶级项目,开发语言为Scala.提供了各种不同语言的API,具体参考Kafka的cwiki页面: Kafk ...

  2. C++待解

    //[要求]按以下描述和要求建立一个含有对象成员的类TeleBook,用类Record定义的数组是TeleBook的数据成员. // 写出所有定义成员函数的代码.执行主函数对其测试. Record私有 ...

  3. java 计算源码的行数

    import java.io.BufferedReader;import java.io.File;import java.io.FileReader;import java.io.IOExcepti ...

  4. JAVA三大特性之二——继承

    很多人在学习了JAVA以后,都会了解这个概念,而且继承也会在以后的开发中经常用到,但对于JAVA的继承特性,很多人都了解的不够深入,不够完整,当然这其中包括我,所以我就想抽点时间来整理一下JAVA继承 ...

  5. linux 下apache2 安装

    Apache安装要求 安装APR.APR-Util.PCRE,gcc-c++等包 Apache HTTP Server  http://httpd.apache.org/download.cgi#ap ...

  6. Hibernate 迫切连接和普通连接的区别

    package com.baidu.test; import java.util.ArrayList;import java.util.LinkedHashSet;import java.util.L ...

  7. 关于Android App开发技术分类的一个小总结

     前言 本文从热更新.异步并发.性能优化.网络请求等多个方面对Android App开发的技术进行了一个分类总结.欢迎大家沟通交流. 热更新 [原]热更新开源项目Tinker源码解析之Dex热更新 [ ...

  8. linux 之程序管理

    一个程序的父进程可以用PPID来判断   命令ps -l 可以用来观察程序相关的输出信息   被关闭的程序又产生:crontab或者父进程产生的   我们将常驻在系统中的程序称为:服务(daemon) ...

  9. Java并发之任务的描述和执行

    简单概念 <Java编程思想>对并发概念的重要性阐述: Java是一种多线程语言,并且提出了并发问题,不管你是否意识到了.因此,有很多使用中的Java程序,要么只是偶尔工作,要么是在大多数 ...

  10. 当前最上层的视图控制器vc 和 当前最上层的导航控制器nav

    在处理 URL Router 跳转的时候,我们经常需要得到 当前最上层的视图控制器 和 当前最上层的导航控制器 来进行视图跳转或者方法调用.- (UIViewController *)currentV ...