LIBACO-快速且轻巧的c不对称的Coroutine库。
该项目的代码名称是Arkenstone?
非对称Coroutine&Arkenstone是其被命名为aco的原因。
目前支持Intel386和X86-64的SYS V ABI。
这是该项目的简要摘要:
上面的“最快”一词是指符合Intel386或AMD64的SYS VABI的最快上下文切换实现。
欢迎问题和公关???
注意:请使用版本而不是master构建最终二进制文件。
除了此读数外,您还可以从https://libaco.org/docs访问文档。如果有任何差异,请遵循此读书文件,因为网站上的文档可能会落后于此。
准备就绪。
#include "aco.h"
#include <stdio.h>
// this header would override the default C `assert`;
// you may refer the "API : MACROS" part for more details.
#include "aco_assert_override.h"
void foo ( int ct ) {
printf ( "co: %p: yield to main_co: %dn" , aco_get_co (), * (( int * )( aco_get_arg ())));
aco_yield ();
* (( int * )( aco_get_arg ())) = ct + 1 ;
}
void co_fp0 () {
printf ( "co: %p: entry: %dn" , aco_get_co (), * (( int * )( aco_get_arg ())));
int ct = 0 ;
while ( ct < 6 ){
foo ( ct );
ct ++ ;
}
printf ( "co: %p: exit to main_co: %dn" , aco_get_co (), * (( int * )( aco_get_arg ())));
aco_exit ();
}
int main () {
aco_thread_init ( NULL );
aco_t * main_co = aco_create ( NULL , NULL , 0 , NULL , NULL );
aco_share_stack_t * sstk = aco_share_stack_new ( 0 );
int co_ct_arg_point_to_me = 0 ;
aco_t * co = aco_create ( main_co , sstk , 0 , co_fp0 , & co_ct_arg_point_to_me );
int ct = 0 ;
while ( ct < 6 ){
assert ( co -> is_end == 0 );
printf ( "main_co: yield to co: %p: %dn" , co , ct );
aco_resume ( co );
assert ( co_ct_arg_point_to_me == ct );
ct ++ ;
}
printf ( "main_co: yield to co: %p: %dn" , co , ct );
aco_resume ( co );
assert ( co_ct_arg_point_to_me == ct );
assert ( co -> is_end );
printf ( "main_co: destroy and exitn" );
aco_destroy ( co );
co = NULL ;
aco_share_stack_destroy ( sstk );
sstk = NULL ;
aco_destroy ( main_co );
main_co = NULL ;
return 0 ;
} # default build
$ gcc -g -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
$ ./test_aco_synopsis
main_co: yield to co: 0x1887120: 0
co: 0x1887120: entry: 0
co: 0x1887120: yield to main_co: 0
main_co: yield to co: 0x1887120: 1
co: 0x1887120: yield to main_co: 1
main_co: yield to co: 0x1887120: 2
co: 0x1887120: yield to main_co: 2
main_co: yield to co: 0x1887120: 3
co: 0x1887120: yield to main_co: 3
main_co: yield to co: 0x1887120: 4
co: 0x1887120: yield to main_co: 4
main_co: yield to co: 0x1887120: 5
co: 0x1887120: yield to main_co: 5
main_co: yield to co: 0x1887120: 6
co: 0x1887120: exit to main_co: 6
main_co: destroy and exit
# i386
$ gcc -g -m32 -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
# share fpu and mxcsr env
$ gcc -g -D ACO_CONFIG_SHARE_FPU_MXCSR_ENV -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
# with valgrind friendly support
$ gcc -g -D ACO_USE_VALGRIND -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
$ valgrind --leak-check=full --tool=memcheck ./test_aco_synopsis有关更多信息,您可以参考“构建和测试”部分。

普通执行状态有4个基本元素: {cpu_registers, code, heap, stack} 。
由于代码信息由({E|R})?IP寄存器指示,并且从堆分配的内存的地址通常直接或间接存储在堆栈中,因此我们只能将4个元素简化为其中的2个: {cpu_registers, stack} 。

我们将main co定义为Coroutine,他垄断了当前线程的默认堆栈。而且,由于Main Co是此堆栈的唯一用户,因此我们只需要在从/恢复到to(switched out-out/switched-in)时保存/还原所需的CPU寄存器状态。
接下来, non-main co的定义是coroutine,其执行堆栈是一个堆栈,不是当前线程的默认堆栈,可以与其他非Main Co共享。因此,非MAIN CO必须具有一个private save stack存储器缓冲区,以保存/还原其执行堆栈时,当它被切换/切换时(因为成功的/先前的CO可能会/使用/使用/使用/使用共享堆栈作为其执行堆栈)。

有一个非梅因公司的特殊情况,那就是我们在libaco中所说的standalone non-main co :非梅因·科鲁特(Coroutine)的共享堆栈只有一个CO用户。因此,当没有其他CO会触摸独立的non-Main Co的执行堆栈,因此无需在切换/切换时保存/还原其私有保存堆栈的内容。

最后,我们得到了Libaco的全局。
如果您想潜入Libaco的内部或想实现自己的Coroutine库,您可能会发现“正确性证明”部分。
还强烈建议您接下来阅读教程和基准的源代码。基准结果非常令人印象深刻,也具有启发性。
-m32 GCC的-m32选项可以帮助您在X86_64机器上构建Libaco的i386应用。
ACO_CONFIG_SHARE_FPU_MXCSR_ENV您可以定义全局C ACO_CONFIG_SHARE_FPU_MXCSR_ENV如果您的代码都不会更改FPU和MXCSR的控制词,则可以稍微加快Coroutines之间上下文切换的性能。如果未定义宏,所有CO将保持其自己的FPU和MXCSR控制单词的副本。建议始终在全球上定义此宏,因为很少有人需要设置自己的FPU或MXCSR特殊ENV,而不是使用ISO C定义的默认设想。但是,如果您不确定,您可能不需要定义此宏。
ACO_USE_VALGRIND如果您想使用Valgrind的工具备忘录来测试应用程序,则可能需要定义全局C Macro ACO_USE_VALGRIND ,以启用Libaco中Valgrind的友好支持。但是,出于性能原因,不建议在最终版本中定义此宏。您可能还需要安装Valgrind标头(例如,包装名称是CentOS中的“ Valgrind-Devel”),以构建使用CACO ACO_USE_VALGRIND定义的Libaco应用程序。 (Valgrind的Memcheck仅与目前的独立CO合作。在多个非Main Co使用的共享堆栈中,Valgrind的Memcheck将产生许多误报报告。有关更多信息,您可能会参考“ test_aco_tutorial_6.c”。)。
ACO_USE_ASAN全球C型ACO_USE_ASAN将在Libaco中友好支持地址消毒剂(支持GCC和Clang)。
建造Libaco的测试套件:
$ mkdir output
$ bash make.shMake.sh中还有一些详细的选项:
$bash make.sh -h
Usage: make.sh [-o < no-m32 | no-valgrind > ] [-h]
Example:
# default build
bash make.sh
# build without the i386 binary output
bash make.sh -o no-m32
# build without the valgrind supported binary output
bash make.sh -o no-valgrind
# build without the valgrind supported and i386 binary output
bash make.sh -o no-valgrind -o no-m32简而言之,如果您没有安装-O valgrind,则使用-o no-valgrind ,如果您在AMD64主机上没有安装32位GCC开发工具, -o no-m32 。
在MacOS上,您需要用GNU sed和grep替换MacOS的默认sed和grep命令,以运行make.sh和test.sh (将来将删除此要求):
$ brew install grep --with-default-names
$ brew install gnu-sed --with-default-names$ cd output
$ bash ../test.sh该存储库中的test_aco_tutorial_0.c显示了libaco的基本用法。本教程中,只有一个主要CO和一个独立的非车手CO。源代码中的评论也非常有帮助。
test_aco_tutorial_1.c显示了某些统计数据的使用。 aco_t的数据结构非常清晰,并在aco.h中定义。
在test_aco_tutorial_2.c中,有一个主要CO,一个独立的非梅恩CO和两个非MAIN CO(指向相同的共享堆栈)。
test_aco_tutorial_3.c显示了如何在多线程过程中使用libaco。基本上,Libaco的一个实例仅是为了在一个特定线程中工作,以获得Coroutines之间上下文切换的最大性能。如果要在多线程环境中使用libaco,则只需在每个线程中创建一个libaco的实例即可。 Libaco内部的线程没有数据共享,您必须自己处理多个线程之间的数据竞争(例如gl_race_aco_yield_ct在本教程中所做的事情)。
Libaco中的规则之一是致电aco_exit()终止执行非默恩CO,而不是默认的直接C样式return ,否则Libaco将把这种行为视为非法的行为并触发默认保护程序,其作业是将有关犯罪行为的错误信息记录到STDERR并立即放弃该过程。 test_aco_tutorial_4.c显示了这种“令人讨厌的CO”情况。
您还可以定义自己的保护器来替换默认的保护器(做一些自定义的“最后单词”操作)。但是,无论在什么情况下,在执行保护者之后,该过程都将被中止。 test_aco_tutorial_5.c显示了如何定义自定义的最后一个单词函数。
最后一个示例是test_aco_tutorial_6.c中的简单Coroutine调度程序。
当您阅读LIBACO的以下API描述时,同时阅读源代码中的相应API实现将非常有帮助,因为源代码非常清晰且易于理解。并且还建议在阅读API文档之前阅读所有教程。
强烈建议在开始编写Libaco的真实应用之前阅读最佳实践部分(除了描述如何真正发布Libaco在您的应用程序中的极端性能外,还有有关Libaco编程的通知)。
注意:Libaco的版本控件遵循规格:语义版本控制2.0.0。因此,以下列表中的API具有兼容性保证。 (请注意,列表中的API没有任何保证。)
typedef void ( * aco_cofuncp_t )( void );
void aco_thread_init ( aco_cofuncp_t last_word_co_fp );初始化当前线程中的Libaco环境。
它将将FPU和MXCSR的当前控制词存储到线程 - 本地全局变量中。
ACO_CONFIG_SHARE_FPU_MXCSR_ENV ,则保存的控制词将用作建立新CO的FPU和MXCSR的控制单词的参考值,并且每个CO(在aco_create中)将维护FPU和MXCSR副本的副本,在以后的上下文中。ACO_CONFIG_SHARE_FPU_MXCSR_ENV ,则所有CO都共享FPU和MXCSR的相同控制单词。您可以将本文档的“构建和测试”一部分引用,以获取有关此文档的更多信息。正如它在“教程”部分的test_aco_tutorial_5.c中所说的那样,当第一个参数last_word_co_fp并非无效时,那么last_word_co_fp指向的函数将替换默认保护器来在此过程中删除该过程之前对犯罪问题进行一些“最后的单词”。在最后一个单词函数中,您可以使用aco_get_co获取有问题的CO的指针。有关更多信息,您可以阅读test_aco_tutorial_5.c 。
aco_share_stack_t * aco_share_stack_new ( size_t sz );等于aco_share_stack_new2(sz, 1) 。
aco_share_stack_t * aco_share_stack_new2 ( size_t sz , char guard_page_enabled );创建一个新的共享堆栈,其中具有字节中的sz的咨询内存大小,并且可能具有一个守卫页面(仅读取)以检测堆栈溢出,这取决于第二个参数guard_page_enabled 。
要使用默认的大小值(2MB),如果第一个参数sz等于0。在对齐和储备进行了一些计算之后,此功能将确保共享堆栈的最终有效长度为回报:
final_valid_sz >= 4096final_valid_sz >= szfinal_valid_sz % page_size == 0 if the guard_page_enabled != 0并尽可能接近sz的值。
当第二个参数的值guard_page_enabled的值为1时,返回的共享堆栈将具有一个仅读取的保护页面,用于检测堆栈溢出,而guard_page_enabled的值为0的值,则无需使用此类保护页面。
此功能将始终返回有效的共享堆栈。
void aco_share_stack_destroy ( aco_share_stack_t * sstk ); DESSORY share stack sstk 。
确保当您销毁sstk时,所有共享堆栈是sstk的CO已经被销毁。
typedef void ( * aco_cofuncp_t )( void );
aco_t * aco_create ( aco_t * main_co , aco_share_stack_t * share_stack ,
size_t save_stack_sz , aco_cofuncp_t co_fp , void * arg );创建一个新公司。
如果是您要创建的main_co,只需致电: aco_create(NULL, NULL, 0, NULL, NULL) 。 Main Co是一个特殊的独立Coroutine,其共享堆栈是默认线程堆栈。在线程中,Main Co是Coroutine,应该在所有其他非梅恩Coroutine之前就开始执行并开始执行。
否则,这是您要创建的非Main Co:
main_co是Main Co,CO将在将来的上下文切换中aco_yield 。 main_co不得无效;share_stack是您想要创建的非梅因公司将来将其用作其执行堆栈的共享堆栈的地址。 share_stack不得无效;save_stack_sz指定了此CO的私有保存堆栈的初始大小。该单元在字节中。值为0表示使用默认尺寸64字节。由于当私人保存堆栈不够大时,将发生自动调整大小,以至于在必须产生股份堆栈时将其执行的堆栈占用,因此您通常不必担心sz的价值。但是,当大量(例如10,000,000个CO)将其私有保存堆栈的大小调整大大的大小时,它将对内存分配给记忆分配带来一些影响,因此非常明智,强烈建议将save_stack_sz设置为等于co->save_stack.max_cpsz的最大值的值等于CO的最大值(您可以在CO上运行)(您可以在此文档中运行),以获取“最佳实践”部分的信息。co_fp是CO的输入函数指针。 co_fp不得为null;arg是指针值,并将设置为CO创建的co->arg 。它可以用作CO的输入参数。此功能将始终返回有效的CO。如果您是您要创建的非梅因公司,我们将CO的状态命名为“ Init”。
void aco_resume ( aco_t * co );从呼叫者主公司屈服,并开始或继续执行co 。
此功能的呼叫者必须是主要CO,并且必须是co->main_co 。第一个参数co必须是非梅因公司。
首次恢复co时,它开始运行由co->fp指向的功能。如果co已经产生, aco_resume重新启动并继续执行。
在aco_resume打电话后,我们将呼叫者的状态称为“屈服”。
void aco_yield ();产生co的执行,并简历co->main_co 。此功能的呼叫者必须是非墨包括co。和co->main_co不得无效。
在aco_yield打电话后,我们将co的状态称为“屈服”。
aco_t * aco_get_co ();返回当前非梅因公司的指针。此功能的呼叫者必须是非墨包括co。
void * aco_get_arg ();等于(aco_get_co()->arg) 。而且,此功能的呼叫者必须是非梅因公司。
void aco_exit ();另外,请与aco_yield()相同, aco_exit()还将co->is_end设置为1,以便以“ end”的状态标记co 。
void aco_destroy ( aco_t * co );摧毁co 。参数co不能无效。如果co是非梅因公司,私人保存堆栈也将被销毁。
#define ACO_VERSION_MAJOR 1
#define ACO_VERSION_MINOR 2
#define ACO_VERSION_PATCH 4这3个宏位于标题aco.h中,其值遵循规格:语义版本2.0.0。
// provide the compiler with branch prediction information
#define likely ( x ) aco_likely(x)
#define unlikely ( x ) aco_unlikely(x)
// override the default `assert` for convenience when coding
#define assert ( EX ) aco_assert(EX)
// equal to `assert((ptr) != NULL)`
#define assertptr ( ptr ) aco_assertptr(ptr)
// assert the successful return of memory allocation
#define assertalloc_bool ( b ) aco_assertalloc_bool(b)
#define assertalloc_ptr ( ptr ) aco_assertalloc_ptr(ptr)您可以选择包括标题"aco_assert_override.h"以在诸如test_aco_synopsis.c之类的libaco应用程序中覆盖默认的c“ servert”(此标头应该在源文件中的最后一个指令列表中,因为c“ assert也是c” sastert也是C MACRO定义),并在上面的5个MACR中定义了5个MACR。如果要使用默认的c“断言”,请不要将此标头包含在应用程序源文件中。
有关更多详细信息,您可以参考源文件ACO_ASSERT_OVERRIDE.H。
日期:6月30日星期六UTC 2018。
机器:c5d.large在AWS上。
OS:RHEL-7.5(Red Hat Enterprise Linux 7.5)。
这是基准部分的简短摘要:
$ LD_PRELOAD=/usr/lib64/libtcmalloc_minimal.so.4 ./test_aco_benchmark..no_valgrind.shareFPUenv
+build:x86_64
+build:-DACO_CONFIG_SHARE_FPU_MXCSR_ENV
+build:share fpu & mxcsr control words between coroutines
+build:undefined ACO_USE_VALGRIND
+build:without valgrind memcheck friendly support
sizeof(aco_t)=152:
comment task_amount all_time_cost ns_per_op speed
aco_create/init_save_stk_sz=64B 1 0.000 s 230.00 ns/op 4347824.79 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.412 s 20.59 ns/op 48576413.55 op/s
-> acosw 40000000 0.412 s 10.29 ns/op 97152827.10 op/s
aco_destroy 1 0.000 s 650.00 ns/op 1538461.66 op/s
aco_create/init_save_stk_sz=64B 1 0.000 s 200.00 ns/op 5000001.72 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.412 s 20.61 ns/op 48525164.25 op/s
-> acosw 40000000 0.412 s 10.30 ns/op 97050328.50 op/s
aco_destroy 1 0.000 s 666.00 ns/op 1501501.49 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.50 ns/op 15266771.53 op/s
aco_resume/co_amount=2000000/copy_stack_size=8B 20000000 0.666 s 33.29 ns/op 30043022.64 op/s
aco_destroy 2000000 0.066 s 32.87 ns/op 30425152.25 op/s
aco_create/init_save_stk_sz=64B 2000000 0.130 s 65.22 ns/op 15332218.24 op/s
aco_resume/co_amount=2000000/copy_stack_size=24B 20000000 0.675 s 33.75 ns/op 29630018.73 op/s
aco_destroy 2000000 0.067 s 33.45 ns/op 29898311.36 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.42 ns/op 15286937.97 op/s
aco_resume/co_amount=2000000/copy_stack_size=40B 20000000 0.669 s 33.45 ns/op 29891277.59 op/s
aco_destroy 2000000 0.080 s 39.87 ns/op 25084242.29 op/s
aco_create/init_save_stk_sz=64B 2000000 0.224 s 111.86 ns/op 8940010.49 op/s
aco_resume/co_amount=2000000/copy_stack_size=56B 20000000 0.678 s 33.88 ns/op 29515473.53 op/s
aco_destroy 2000000 0.067 s 33.42 ns/op 29922412.68 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.74 ns/op 15211896.70 op/s
aco_resume/co_amount=2000000/copy_stack_size=120B 20000000 0.769 s 38.45 ns/op 26010724.94 op/s
aco_destroy 2000000 0.088 s 44.11 ns/op 22669240.25 op/s
aco_create/init_save_stk_sz=64B 10000000 1.240 s 123.97 ns/op 8066542.54 op/s
aco_resume/co_amount=10000000/copy_stack_size=8B 40000000 1.327 s 33.17 ns/op 30143409.55 op/s
aco_destroy 10000000 0.328 s 32.82 ns/op 30467658.05 op/s
aco_create/init_save_stk_sz=64B 10000000 0.659 s 65.94 ns/op 15165717.02 op/s
aco_resume/co_amount=10000000/copy_stack_size=24B 40000000 1.345 s 33.63 ns/op 29737708.53 op/s
aco_destroy 10000000 0.337 s 33.71 ns/op 29666697.09 op/s
aco_create/init_save_stk_sz=64B 10000000 0.654 s 65.38 ns/op 15296191.35 op/s
aco_resume/co_amount=10000000/copy_stack_size=40B 40000000 1.348 s 33.71 ns/op 29663992.77 op/s
aco_destroy 10000000 0.336 s 33.56 ns/op 29794574.96 op/s
aco_create/init_save_stk_sz=64B 10000000 0.653 s 65.29 ns/op 15316087.09 op/s
aco_resume/co_amount=10000000/copy_stack_size=56B 40000000 1.384 s 34.60 ns/op 28902221.24 op/s
aco_destroy 10000000 0.337 s 33.73 ns/op 29643682.93 op/s
aco_create/init_save_stk_sz=64B 10000000 0.652 s 65.19 ns/op 15340872.40 op/s
aco_resume/co_amount=10000000/copy_stack_size=120B 40000000 1.565 s 39.11 ns/op 25566255.73 op/s
aco_destroy 10000000 0.443 s 44.30 ns/op 22574242.55 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.61 ns/op 15241722.94 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.947 s 47.36 ns/op 21114212.05 op/s
aco_destroy 2000000 0.125 s 62.35 ns/op 16039466.45 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.71 ns/op 15218784.72 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.948 s 47.39 ns/op 21101216.29 op/s
aco_destroy 2000000 0.125 s 62.73 ns/op 15941559.26 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.49 ns/op 15270258.18 op/s
aco_resume/co_amount=2000000/copy_stack_size=152B 20000000 1.069 s 53.44 ns/op 18714275.17 op/s
aco_destroy 2000000 0.122 s 61.05 ns/op 16378678.85 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 65.91 ns/op 15171336.62 op/s
aco_resume/co_amount=2000000/copy_stack_size=232B 20000000 1.190 s 59.48 ns/op 16813230.99 op/s
aco_destroy 2000000 0.123 s 61.26 ns/op 16324298.25 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.68 ns/op 15224361.30 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.828 s 91.40 ns/op 10941133.56 op/s
aco_destroy 2000000 0.145 s 72.56 ns/op 13781182.82 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 65.80 ns/op 15197461.34 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.829 s 91.47 ns/op 10932139.32 op/s
aco_destroy 2000000 0.149 s 74.70 ns/op 13387258.82 op/s
aco_create/init_save_stk_sz=64B 1000000 0.067 s 66.63 ns/op 15007426.35 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.224 s 211.20 ns/op 4734744.76 op/s
aco_destroy 1000000 0.093 s 93.36 ns/op 10711651.49 op/s
aco_create/init_save_stk_sz=64B 1000000 0.066 s 66.28 ns/op 15086953.73 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.222 s 211.12 ns/op 4736537.93 op/s
aco_destroy 1000000 0.094 s 94.09 ns/op 10627664.78 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 70.72 ns/op 14139923.59 op/s
aco_resume/co_amount=100000/copy_stack_size=1000B 20000000 4.191 s 209.56 ns/op 4771909.70 op/s
aco_destroy 100000 0.010 s 101.21 ns/op 9880747.28 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 66.62 ns/op 15010433.00 op/s
aco_resume/co_amount=100000/copy_stack_size=2024B 20000000 7.002 s 350.11 ns/op 2856228.03 op/s
aco_destroy 100000 0.016 s 159.69 ns/op 6262129.35 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 65.76 ns/op 15205994.08 op/s
aco_resume/co_amount=100000/copy_stack_size=4072B 20000000 11.918 s 595.90 ns/op 1678127.54 op/s
aco_destroy 100000 0.019 s 186.32 ns/op 5367189.85 op/s
aco_create/init_save_stk_sz=64B 100000 0.006 s 63.03 ns/op 15865531.37 op/s
aco_resume/co_amount=100000/copy_stack_size=7992B 20000000 21.808 s 1090.42 ns/op 917079.11 op/s
aco_destroy 100000 0.038 s 378.33 ns/op 2643225.42 op/s
$ LD_PRELOAD=/usr/lib64/libtcmalloc_minimal.so.4 ./test_aco_benchmark..no_valgrind.standaloneFPUenv
+build:x86_64
+build:undefined ACO_CONFIG_SHARE_FPU_MXCSR_ENV
+build:each coroutine maintain each own fpu & mxcsr control words
+build:undefined ACO_USE_VALGRIND
+build:without valgrind memcheck friendly support
sizeof(aco_t)=160:
comment task_amount all_time_cost ns_per_op speed
aco_create/init_save_stk_sz=64B 1 0.000 s 273.00 ns/op 3663004.27 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.415 s 20.76 ns/op 48173877.75 op/s
-> acosw 40000000 0.415 s 10.38 ns/op 96347755.51 op/s
aco_destroy 1 0.000 s 381.00 ns/op 2624672.26 op/s
aco_create/init_save_stk_sz=64B 1 0.000 s 212.00 ns/op 4716980.43 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.415 s 20.75 ns/op 48185455.26 op/s
-> acosw 40000000 0.415 s 10.38 ns/op 96370910.51 op/s
aco_destroy 1 0.000 s 174.00 ns/op 5747123.38 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.63 ns/op 15237386.02 op/s
aco_resume/co_amount=2000000/copy_stack_size=8B 20000000 0.664 s 33.20 ns/op 30119155.82 op/s
aco_destroy 2000000 0.065 s 32.67 ns/op 30604542.55 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.33 ns/op 15305975.29 op/s
aco_resume/co_amount=2000000/copy_stack_size=24B 20000000 0.675 s 33.74 ns/op 29638360.61 op/s
aco_destroy 2000000 0.067 s 33.31 ns/op 30016633.42 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.61 ns/op 15241767.78 op/s
aco_resume/co_amount=2000000/copy_stack_size=40B 20000000 0.678 s 33.88 ns/op 29518648.08 op/s
aco_destroy 2000000 0.079 s 39.74 ns/op 25163018.30 op/s
aco_create/init_save_stk_sz=64B 2000000 0.221 s 110.73 ns/op 9030660.30 op/s
aco_resume/co_amount=2000000/copy_stack_size=56B 20000000 0.684 s 34.18 ns/op 29253416.65 op/s
aco_destroy 2000000 0.067 s 33.40 ns/op 29938840.64 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.60 ns/op 15244077.65 op/s
aco_resume/co_amount=2000000/copy_stack_size=120B 20000000 0.769 s 38.43 ns/op 26021228.41 op/s
aco_destroy 2000000 0.087 s 43.74 ns/op 22863987.42 op/s
aco_create/init_save_stk_sz=64B 10000000 1.251 s 125.08 ns/op 7994958.59 op/s
aco_resume/co_amount=10000000/copy_stack_size=8B 40000000 1.327 s 33.19 ns/op 30133654.80 op/s
aco_destroy 10000000 0.329 s 32.85 ns/op 30439787.32 op/s
aco_create/init_save_stk_sz=64B 10000000 0.674 s 67.37 ns/op 14843796.57 op/s
aco_resume/co_amount=10000000/copy_stack_size=24B 40000000 1.354 s 33.84 ns/op 29548523.05 op/s
aco_destroy 10000000 0.339 s 33.90 ns/op 29494634.83 op/s
aco_create/init_save_stk_sz=64B 10000000 0.672 s 67.19 ns/op 14882262.88 op/s
aco_resume/co_amount=10000000/copy_stack_size=40B 40000000 1.361 s 34.02 ns/op 29393520.19 op/s
aco_destroy 10000000 0.338 s 33.77 ns/op 29609577.59 op/s
aco_create/init_save_stk_sz=64B 10000000 0.673 s 67.31 ns/op 14857716.02 op/s
aco_resume/co_amount=10000000/copy_stack_size=56B 40000000 1.371 s 34.27 ns/op 29181897.80 op/s
aco_destroy 10000000 0.339 s 33.85 ns/op 29540633.63 op/s
aco_create/init_save_stk_sz=64B 10000000 0.672 s 67.24 ns/op 14873017.10 op/s
aco_resume/co_amount=10000000/copy_stack_size=120B 40000000 1.548 s 38.71 ns/op 25835542.17 op/s
aco_destroy 10000000 0.446 s 44.61 ns/op 22415961.64 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 66.01 ns/op 15148290.52 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.944 s 47.22 ns/op 21177946.19 op/s
aco_destroy 2000000 0.124 s 61.99 ns/op 16132721.97 op/s
aco_create/init_save_stk_sz=64B 2000000 0.133 s 66.36 ns/op 15068860.85 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.944 s 47.20 ns/op 21187541.38 op/s
aco_destroy 2000000 0.124 s 62.21 ns/op 16073322.25 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.62 ns/op 15238955.93 op/s
aco_resume/co_amount=2000000/copy_stack_size=152B 20000000 1.072 s 53.61 ns/op 18652789.74 op/s
aco_destroy 2000000 0.121 s 60.42 ns/op 16551368.04 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 66.08 ns/op 15132547.65 op/s
aco_resume/co_amount=2000000/copy_stack_size=232B 20000000 1.198 s 59.88 ns/op 16699389.91 op/s
aco_destroy 2000000 0.121 s 60.71 ns/op 16471465.52 op/s
aco_create/init_save_stk_sz=64B 2000000 0.133 s 66.50 ns/op 15036985.95 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.853 s 92.63 ns/op 10796126.04 op/s
aco_destroy 2000000 0.146 s 72.87 ns/op 13723559.36 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 66.14 ns/op 15118324.13 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.855 s 92.75 ns/op 10781572.22 op/s
aco_destroy 2000000 0.152 s 75.79 ns/op 13194130.51 op/s
aco_create/init_save_stk_sz=64B 1000000 0.067 s 66.97 ns/op 14931921.56 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.218 s 210.90 ns/op 4741536.66 op/s
aco_destroy 1000000 0.093 s 93.16 ns/op 10734691.98 op/s
aco_create/init_save_stk_sz=64B 1000000 0.066 s 66.49 ns/op 15039274.31 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.216 s 210.81 ns/op 4743543.53 op/s
aco_destroy 1000000 0.094 s 93.97 ns/op 10641539.58 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 70.95 ns/op 14094724.73 op/s
aco_resume/co_amount=100000/copy_stack_size=1000B 20000000 4.190 s 209.52 ns/op 4772746.50 op/s
aco_destroy 100000 0.010 s 100.99 ns/op 9902271.51 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 66.49 ns/op 15040038.84 op/s
aco_resume/co_amount=100000/copy_stack_size=2024B 20000000 7.028 s 351.38 ns/op 2845942.55 op/s
aco_destroy 100000 0.016 s 159.15 ns/op 6283444.42 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 65.73 ns/op 15214482.36 op/s
aco_resume/co_amount=100000/copy_stack_size=4072B 20000000 11.879 s 593.95 ns/op 1683636.60 op/s
aco_destroy 100000 0.018 s 184.23 ns/op 5428119.00 op/s
aco_create/init_save_stk_sz=64B 100000 0.006 s 63.41 ns/op 15771072.16 op/s
aco_resume/co_amount=100000/copy_stack_size=7992B 20000000 21.808 s 1090.42 ns/op 917081.56 op/s
aco_destroy 100000 0.038 s 376.78 ns/op 2654073.13 op/s
在开始实施或证明Coroutine库之前,要非常熟悉Intel386和X86-64的SYS V ABI标准。
下面的证明没有有关IP(指令指针),SP(堆栈指针)以及私人保存堆栈和共享堆栈之间的保存/恢复的直接描述,因为这些东西在将其与ABI约束内容进行比较时易于理解。
在OS线程中,主要的Coroutine main_co是Coroutine,应该在所有其他非梅恩·克罗伊特(Coroutines)之前首先创建并开始执行。
下图是main_co和Co之间上下文切换的一个简单示例。
在此证明中,我们只是假设我们在Intel386的Sys v Abi下,因为Intel386和X86-64之间没有根本差异。我们还假设没有任何代码会更改FPU和MXCSR的控制词。

下一个图实际上是对称的Coroutine运行模型,该模型具有无限数量的非Main Co-S和一个主CO。这很好,因为不对称的Coroutine只是对称coroutine的特殊情况。证明对称的coroutine的正确性比不对称的Coroutine更具挑战性,因此会变得更加有趣。 (Libaco仅实施了非对称Coroutine的API,因为不对称Coroutine API的语义含义比对称的Coroutine更容易理解和使用。)

由于主要CO是第一个Coroutine开始运行,因此该操作系统线程中的第一个上下文切换必须以acosw(main_co, co)的形式进行,其中第二个参数co是一个非MAIN CO。
很容易证明上图中只有两种状态转移:
为了证明void* acosw(aco_t* from_co, aco_t* to_co)的正确性是等效的,以证明所有CO在呼叫acosw之前和之后都不断符合SYS VABI的约束。我们假设CO中的二进制代码的另一部分( acosw除外)已经符合ABI(通常由编译器正确生成)。
以下是登记册中登记册的限制的摘要。
Registers' usage in the calling convention of the Intel386 System V ABI:
caller saved (scratch) registers:
C1.0: EAX
At the entry of a function call:
could be any value
After the return of `acosw`:
hold the return value for `acosw`
C1.1: ECX,EDX
At the entry of a function call:
could be any value
After the return of `acosw`:
could be any value
C1.2: Arithmetic flags, x87 and mxcsr flags
At the entry of a function call:
could be any value
After the return of `acosw`:
could be any value
C1.3: ST(0-7)
At the entry of a function call:
the stack of FPU must be empty
After the return of `acosw`:
the stack of FPU must be empty
C1.4: Direction flag
At the entry of a function call:
DF must be 0
After the return of `acosw`:
DF must be 0
C1.5: others: xmm*,ymm*,mm*,k*...
At the entry of a function call:
could be any value
After the return of `acosw`:
could be any value
callee saved registers:
C2.0: EBX,ESI,EDI,EBP
At the entry of a function call:
could be any value
After the return of `acosw`:
must be the same as it is at the entry of `acosw`
C2.1: ESP
At the entry of a function call:
must be a valid stack pointer
(alignment of 16 bytes, retaddr and etc...)
After the return of `acosw`:
must be the same as it is before the call of `acosw`
C2.2: control word of FPU & mxcsr
At the entry of a function call:
could be any configuration
After the return of `acosw`:
must be the same as it is before the call of `acosw`
(unless the caller of `acosw` assume `acosw` may
change the control words of FPU or MXCSR on purpose
like `fesetenv`)
(对于Intel386,寄存器使用量在“ p13-表2.3:寄存器用法”中定义。
证明:

以上图是第一种情况:“产生状态CO-> Init State Co”。
约束:C 1.0、1.1、1.2、1.5(满意✓)
下面的划痕寄存器可以在函数输入时保持任何值:
EAX,ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
约束:C 1.3,1.4(满意✓)
由于FPU的堆栈必须已经空了,并且在调用acosw(co, to_co)之前,DF必须已经为0(CO的二进制代码已遵守ABI),因此约束1.3和1.4由acosw遵守。
约束:C 2.0、2.1、2.2(满意✓)
C 2.0和2.1已经满足。由于我们已经假设没有人会更改FPU和MXCSR的控制词,因此C 2.2也得到满足。

以上图是第二种情况:产生状态co->产生的状态co。
约束:C 1.0(满意✓)
当acosw返回到to_co(简历)时,EAX已经保持返回值。
约束:C 1.1、1.2、1.5(满意✓)
下面的划痕寄存器可以在函数输入和acosw返回之后保持任何值:
ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
约束:C 1.3,1.4(满意✓)
由于FPU的堆栈必须已经空了,并且在调用acosw(co, to_co)之前,DF必须已经为0(CO的二进制代码已遵守ABI),因此约束1.3和1.4由acosw遵守。
约束:C 2.0、2.1、2.2(满意✓)
C 2.0&2.1之所以满足,是因为当拨打/返回acosw时,Callee保存的寄存器保存和还原。由于我们已经假设没有人会更改FPU和MXCSR的控制词,因此C 2.2也得到满足。
线程中的第一个acosw必须是第一种情况:产生状态CO-> INT state Co,所有下一个acosw必须是上面的2个情况之一。顺便说一句,我们可以证明“所有CO都不断符合acosw呼叫之前和之后的SYS V ABI的约束”。因此,证明完成了。
系统v ABI x86-64中有一个叫做红色区域的新事物:
超出指向%RSP的位置的128字节区域被认为是保留的,不得通过信号或中断处理程序进行修改。因此,功能可以将此领域用于跨函数调用中不需要的临时数据。特别是,叶子功能可以将此区域用于整个堆栈框架,而不是调整序言和结语中的堆栈指针。该区域称为红色区域。
由于红色区域“不是由Callee保存”,因此在Coroutines之间的上下文切换中,我们根本不在乎它(因为acosw是叶子功能)。
输入参数区域的末端应在16(32或64)上对齐,如果__M256或__M512在堆栈上传递)字节边界。换句话说,当控件传输到功能入口点时,值(%ESP + 4)始终是16(32或64)的倍数。堆栈指针%ESP总是指向最新分配的堆栈框架的结束。
- intel386-psabi-1.1:2.2.2堆栈框架
堆栈指针%RSP总是指向最新分配的堆栈框架的末尾。
- SYS V ABI AMD64版本1.0:3.2.2堆栈框架
这是Tencent Libco中的一个错误示例。 ABI指出(E|R)SP应始终指向最新分配的堆栈框架的末尾。但是在libco的文件coctx_swap.s中, (E|R)SP已用于解决堆上的内存。
默认情况下,信号处理程序在正常过程堆栈上被调用。可以安排信号处理程序使用替代堆栈;有关如何执行此操作以及何时有用的讨论,请参见Sigalstack(2)。
- 男子7信号:信号处置
如果(E|R)SP指向信号时(堆)的数据结构,则可能会发生可怕的事情。 (使用GDB的breakpoint和signal命令可以方便地产生此类错误。尽管通过使用sigalstack更改默认信号堆栈可能会减轻问题,但是仍然存在(E|R)SP仍然违反ABI的使用情况。)
总而言之,如果您想获得Libaco的超级性能,只需在将aco_yield尽可能小的时候保留非标准元非MAIN CO的堆栈使用。如果要将局部变量的地址从一个CO传递到另一个CO,请非常小心,因为局部变量通常在共享堆栈上。从堆中分配这种变量始终是更明智的选择。
详细说明,有5个提示:
co_fp
/
/
f1 f2
/ /
/ f4
yield f3 f5
aco_yield将其寄回到Main Co)对Coroutines之间上下文切换的性能产生了很大的影响,如基准测试结果所示。在上图中,功能F2,F3,F4和F5的堆栈使用情况对上下文切换性能没有直接影响,因为它们执行时没有aco_yield ,而CO_FP和F1的堆栈使用占CO_FP和F1的堆栈使用占co->save_stack.max_cpsz的值,并且在上下文开关上具有很大的影响。保持尽可能低的功能堆栈使用的关键是在堆上分配局部变量(尤其是大变量)并手动管理其生命周期,而不是默认情况下在堆栈上分配它们。 GCC的-fstack-usage选项对此非常有帮助。
int * gl_ptr ;
void inc_p ( int * p ){ ( * p ) ++ ; }
void co_fp0 () {
int ct = 0 ;
gl_ptr = & ct ; // line 7
aco_yield ();
check ( ct );
int * ptr = & ct ;
inc_p ( ptr ); // line 11
aco_exit ();
}
void co_fp1 () {
do_sth ( gl_ptr ); // line 16
aco_exit ();
}gl_ptr中保存的地址(第16行)与CO_FP0第7行中的gl_ptr具有完全不同的语义,并且这种代码可能会损坏CO_FP1的执行堆栈。但是第11行是可以的,因为可变ct和FUNCTION inc_p处于相同的Coroutine上下文中。在堆上分配这种变量(需要与其他协调分享)只会解决此类问题: int * gl_ptr ;
void inc_p ( int * p ){ ( * p ) ++ ; }
void co_fp0 () {
int * ct_ptr = malloc ( sizeof ( int ));
assert ( ct_ptr != NULL );
* ct_ptr = 0 ;
gl_ptr = ct_ptr ;
aco_yield ();
check ( * ct_ptr );
int * ptr = ct_ptr ;
inc_p ( ptr );
free ( ct_ptr );
gl_ptr = NULL ;
aco_exit ();
}
void co_fp1 () {
do_sth ( gl_ptr );
aco_exit ();
}欢迎新想法!
添加一个宏,例如aco_mem_new ,它是p = malloc(sz); assertalloc_ptr(p) 。
添加新的API aco_reset来支持Coroutine对象的可重复使用性。
支持其他平台(尤其是ARM和ARM64)。
v1.2.4 Sun Jul 29 2018
Changed `asm` to `__asm__` in aco.h to support compiler's `--std=c99`
flag (Issue #16, proposed by Theo Schlossnagle @postwait).
v1.2.3 Thu Jul 26 2018
Added support for MacOS;
Added support for shared library build of libaco (PR #10, proposed
by Theo Schlossnagle @postwait);
Added C macro ACO_REG_IDX_BP in aco.h (PR #15, proposed by
Theo Schlossnagle @postwait);
Added global C config macro ACO_USE_ASAN which could enable the
friendly support of address sanitizer (both gcc and clang) (PR #14,
proposed by Theo Schlossnagle @postwait);
Added README_zh.md.
v1.2.2 Mon Jul 9 2018
Added a new option `-o <no-m32|no-valgrind>` to make.sh;
Correction about the value of macro ACO_VERSION_PATCH (issue #1
kindly reported by Markus Elfring @elfring);
Adjusted some noncompliant naming of identifiers (double underscore
`__`) (issue #1, kindly proposed by Markus Elfring @elfring);
Supported the header file including by C++ (issue #4, kindly
proposed by Markus Elfring @elfring).
v1.2.1 Sat Jul 7 2018
Fixed some noncompliant include guards in two C header files (
issue #1 kindly reported by Markus Elfring @elfring);
Removed the "pure" word from "pure C" statement since it is
containing assembly codes (kindly reported by Peter Cawley
@corsix);
Many updates in the README.md document.
v1.2.0 Tue Jul 3 2018
Provided another header named `aco_assert_override.h` so user
could choose to override the default `assert` or not;
Added some macros about the version information.
v1.1 Mon Jul 2 2018
Removed the requirement on the GCC version (>= 5.0).
v1.0 Sun Jul 1 2018
The v1.0 release of libaco, cheers ???
我是专职开源开发人员。任何数量的捐款都将受到高度赞赏,并可能给我带来极大的鼓励。
贝宝
paypal.me链接
apay(支付(宝|宝))


Libaco的徽标由Peter Bech(Peteck)慷慨捐赠。该徽标是根据CC BY-ND 4.0许可的。 libaco.org的网站也由彼得·贝克(Peter Bech)(Peteck)撰写。
版权所有(C)2018,由Sen Han [email protected]。
在Apache许可证下,版本2.0。
有关详细信息,请参见许可证文件。