Libaco: una biblioteca de coroutina asimétrica C rápida y liviana.
¿El nombre en código de este proyecto es Arkenstone?
Coroutine y Arkenstone asimétrico es la razón por la cual se ha llamado aco .
Actualmente admite Sys V ABI de Intel386 y X86-64.
Aquí hay un breve resumen de este proyecto:
La frase " más rápida " en arriba significa la implementación de cambio de contexto más rápida que cumple con el SYS v ABI de Intel386 o AMD64.
¿Los problemas y los PR son bienvenidos?
Nota: use los lanzamientos en lugar del master para construir el binario final.
Además de este readme, también puede visitar la documentación de https://libaco.org/docs. Siga este readme si hay alguna diferencia porque la documentación en el sitio web puede estar retrasándose de este readme.
Producción lista.
#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_synopsisPara obtener más información, puede consultar la parte de "construir y probar".

Hay 4 elementos básicos de un estado de ejecución ordinario: {cpu_registers, code, heap, stack} .
Dado que la información del código se indica mediante el registro ({E|R})?IP , y la dirección de la memoria asignada desde el montón se almacena normalmente en la pila directa o indirectamente, por lo tanto, podríamos simplificar los 4 elementos en solo 2 de ellos: {cpu_registers, stack} .

Definimos el main co como la coroutina que monopoliza la pila predeterminada del hilo actual. Y dado que el CO principal es el único usuario de esta pila, solo necesitamos guardar/restaurar el estado de registros de CPU necesarios del CO principal cuando se ha cedido de/reanudado (conmutado/conmutado).
A continuación, la definición del non-main co es la coroutina cuya pila de ejecución es una pila que no es la pila predeterminada del hilo actual y puede compartirse con el otro CO no principal. Por lo tanto, el CO no principal debe tener un búfer de memoria de private save stack para guardar/restaurar su pila de ejecución cuando se ha interrumpido/conmutado (porque el CO de éxito/precedente puede usar/usar/utilizar la pila de acciones como su pila de ejecución).

Existe un caso especial de no principal, que es standalone non-main co lo que llamamos en Libaco: la pila de acciones de la Coroutina no principal tiene solo un usuario de CO. Por lo tanto, no hay necesidad de guardar/restaurar cosas de su pila de guardado privado cuando se ha conmutado/conmutado, ya que no hay otro CO tocará la pila de ejecución del co-no mayor co excepto en sí mismo.

Finalmente, obtenemos el panorama general de Libaco.
Hay una parte de "prueba de corrección" que puede encontrar realmente útil si desea sumergirse en la interna de Libaco o desea implementar su propia biblioteca de Coroutine.
También se recomienda leer el código fuente de los tutoriales y el punto de referencia a continuación. El resultado de referencia también es muy impresionante e esclarecedor.
-m32 La opción -m32 de GCC podría ayudarlo a construir la aplicación i386 de libaco en una máquina x86_64.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV Puede definir el ACO_CONFIG_SHARE_FPU_MXCSR_ENV global C para acelerar ligeramente el rendimiento de la conmutación de contexto entre las coroutinas si ninguno de su código cambiaría las palabras de control de FPU y MXCSR. Si la macro no está definida, todo el CO mantendría su propia copia de las palabras de control FPU y MXCSR. Se recomienda definir siempre esta macro a nivel mundial, ya que es muy raro que una función necesite establecer su propio ENV especial de FPU o MXCSR en lugar de usar el ENV predeterminado definido por el ISO C., pero es posible que no necesite definir esta macro si no está seguro de ella.
ACO_USE_VALGRIND Si desea utilizar la herramienta MemCheck de Valgrind para probar la aplicación, es posible que deba definir el Macro ACO_USE_VALGRIND global C Global para habilitar el apoyo amigable de Valgrind en Libaco. Pero no se recomienda definir esta macro en la compilación de la versión final por el motivo de rendimiento. También es posible que deba instalar los encabezados Valgrind (el nombre del paquete es "Valgrind-Devel" en CentOS, por ejemplo) para construir la aplicación Libaco con C Macro ACO_USE_VALGRIND definida. (La Memcheck de Valgrind solo funciona bien con el CO independiente actualmente. En el caso de la pila compartida utilizada por más de un CO no principal, la Memcheck de Valgrind generaría muchos informes falsos positivos. Para obtener más información, puede consultar "Test_ACo_tutorial_6.C".)
ACO_USE_ASAN El Macro ACO_USE_ASAN global permitiría el apoyo amistoso del desinfectante de direcciones en Libaco (soporte tanto GCC como CLANG).
Para construir las suites de prueba de Libaco:
$ mkdir output
$ bash make.shTambién hay algunas opciones detalladas en Make.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 En resumen, usando -o no-valgrind si no tiene encabezados Valgrind instalados, -o no-m32 Si no tiene herramientas de desarrollo GCC de 32 bits instaladas en un host AMD64.
En MacOS, debe reemplazar los comandos sed y grep predeterminados de MacOS con el GNU sed y grep para ejecutar make.sh y test.sh (dicho requisito se eliminaría en el futuro):
$ brew install grep --with-default-names
$ brew install gnu-sed --with-default-names$ cd output
$ bash ../test.sh El test_aco_tutorial_0.c en este repositorio muestra el uso básico de libaco. Solo hay un CO principal y un coe no principal independiente en este tutorial. Los comentarios en el código fuente también son muy útiles.
El test_aco_tutorial_1.c muestra el uso de algunas estadísticas de CO no principal. La estructura de datos de aco_t es muy clara y se define en aco.h
Hay un CO principal, un CO no principal independiente y dos CO no principales (apuntando a la misma pila de acciones) en test_aco_tutorial_2.c .
El test_aco_tutorial_3.c muestra cómo usar libaco en un proceso multiproceso. Básicamente, una instancia de libaco está diseñada solo para trabajar dentro de un cierto hilo para obtener el máximo rendimiento del cambio de contexto entre las corutinas. Si desea usar libaco en un entorno multiproceso, simplemente para crear una instancia de libaco en cada uno de los hilos. No hay datos de datos entre hilos dentro del libaco, y usted mismo debe lidiar con la competencia de datos entre múltiples hilos (como lo que hace gl_race_aco_yield_ct en este tutorial).
Una de las reglas en Libaco es llamar aco_exit() para finalizar la ejecución del CO no principal en lugar del return de estilo C directo predeterminado, de lo contrario, Libaco tratará dicho comportamiento como ilegal y activará el protector predeterminado cuyo trabajo es registrar la información de error sobre el CO ofensivo a STDERR y abortar el proceso de inmediato. El test_aco_tutorial_4.c muestra tal situación de "CO ofensiva".
También puede definir su propio protector para sustituir el predeterminado (para hacer algunas cosas personalizadas de "Últimas palabras"). Pero no importa en qué caso, el proceso se abortará después de que se ejecutara el protector. El test_aco_tutorial_5.c muestra cómo definir la función de última palabra personalizada.
El último ejemplo es un simple planificador de coroutine en test_aco_tutorial_6.c .
Sería muy útil leer la implementación de API correspondiente en el código fuente simultáneamente cuando está leyendo la siguiente descripción de la API de Libaco, ya que el código fuente es bastante claro y fácil de entender. Y también se recomienda leer todos los tutoriales antes de leer el documento API.
Se recomienda leer la parte de las mejores prácticas antes de comenzar a escribir la aplicación real de Libaco (además de describir cómo liberar realmente el rendimiento extremo de Libaco en su aplicación, también hay un aviso sobre la programación de Libaco).
Nota: El control de la versión de Libaco sigue la especificación: Versión semántica 2.0.0. Entonces, la API en la siguiente lista tiene la garantía de compatibilidad. (Tenga en cuenta que no hay tal garantía para la API no en la lista).
typedef void ( * aco_cofuncp_t )( void );
void aco_thread_init ( aco_cofuncp_t last_word_co_fp );Inicializa el entorno libaco en el hilo actual.
Almacenará las palabras de control actuales de FPU y MXCSR en una variable global local de hilo.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV Global, las palabras de control guardadas se utilizarían como un valor de referencia para configurar las palabras de control de la nueva FPU de CO y MXCSR (en aco_create ) y cada CO mantendría su propia copia de las palabras de control de FPU y MXCSR durante el cambio de contexto posterior.ACO_CONFIG_SHARE_FPU_MXCSR_ENV Global, todas las CO comparten las mismas palabras de control de FPU y MXCSR. Puede consultar la parte de "construir y probar" de este documento para obtener más información sobre esto. Y como dijo en test_aco_tutorial_5.c de la parte "tutoriales", cuando el primer argumento last_word_co_fp no es nula, entonces la función apuntada por last_word_co_fp sustituirá al protector predeterminado para hacer algunas cosas de "Últimas palabras" sobre el CO ofensivo antes del proceso. En tal función de la última palabra, puede usar aco_get_co para obtener el puntero del CO ofensivo. Para obtener más información, puede leer test_aco_tutorial_5.c .
aco_share_stack_t * aco_share_stack_new ( size_t sz ); Igual a aco_share_stack_new2(sz, 1) .
aco_share_stack_t * aco_share_stack_new2 ( size_t sz , char guard_page_enabled ); Crea una nueva pila de acciones con un tamaño de memoria de asesoramiento de sz en bytes y puede tener una página de guardia (de solo lectura) para la detección de desbordamiento de pila que depende del segundo argumento guard_page_enabled .
Para usar el valor de tamaño predeterminado (2MB) si el primer argumento sz es igual a 0. Después de algún cálculo de alineación y reserva, esta función garantizará la longitud válida final de la pila de acciones a cambio:
final_valid_sz >= 4096final_valid_sz >= szfinal_valid_sz % page_size == 0 if the guard_page_enabled != 0 Y lo más cerca posible del valor de sz .
Cuando el valor del 2º argumento guard_page_enabled es 1, la pila de acciones a cambio tendría una página de guardia de solo lectura para la detección del desbordamiento de la pila, mientras que un valor 0 de guard_page_enabled significa sin dicha página de guardia.
Esta función siempre devolverá una pila de acciones válida.
void aco_share_stack_destroy ( aco_share_stack_t * sstk ); Destory the Share Stack sstk .
Asegúrese de que todo el CO cuya pila de acciones sea sstk ya esté destruida cuando destruyes el sstk .
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 );Crea una nueva co.
Si es un Main_CO que desea crear, simplemente llame: aco_create(NULL, NULL, 0, NULL, NULL) . Main Co es una coroutina independiente especial cuya pila de acciones es la pila de subprocesos predeterminada. En el hilo, Main Co es la coroutina que debe ser creada y comenzó a ejecutarse antes de que toda la otra coroutina no principal lo haga.
De lo contrario, es una CO no principal que desea crear:
main_co es el CO principal a los que el CO aco_yield en el cambio de contexto futuro. main_co no debe ser nulo;share_stack es la dirección de una pila de acciones que el CO no principal que desea crear utilizará como su pila de ejecución en el futuro. share_stack no debe ser nulo;save_stack_sz especifica el tamaño de inicio de la pila de guardado privado de este co. La unidad está en bytes. Un valor de 0 significa usar el tamaño predeterminado 64 bytes. Dado que el cambio de tamaño automático ocurriría cuando la pila de salvación privada no es lo suficientemente grande como para mantener la pila de ejecución de la CO cuando tiene que producir la pila de acciones que está ocupando a otro CO, generalmente no debe preocuparse por el valor de sz en absoluto. Pero traerá un impacto de rendimiento al asignador de memoria cuando una gran cantidad (por ejemplo, 10,000,000) del CO cambia de tamaño su pila de guardado privado continuamente, por lo que es muy sabio y muy recomendable establecer el save_stack_sz con un valor igual al valor máximo de co->save_stack.max_cpsz cuando el CO se está ejecutando (puede referirse a la parte "mejor práctica" de esta documento para más información sobre la información más información sobre la información más información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información sobre la información más información sobre la información sobre la información sobre la información sobre más información sobre la información más información sobre la información más información sobre la información más información sobre la información sobre la mayor información);co_fp es el puntero de la función de entrada del CO. co_fp no debe ser nulo;arg es un valor de puntero y se establecerá para co->arg del CO para crear. Podría usarse como un argumento de entrada para el CO.Esta función siempre devolverá un co. Y nombramos el estado del CO a cambio como "init" si es un co no principal que desea crear.
void aco_resume ( aco_t * co ); Reduce de la persona que llama Main Co y para comenzar o continuar la ejecución de co .
La persona que llama de esta función debe ser un CO principal y debe ser co->main_co . Y el primer argumento co debe ser un co.
La primera vez que reanuda un co , comienza a ejecutar la función que apunta por co->fp . Si co ya ha sido producido, aco_resume lo reinicia y continúa la ejecución.
Después de la llamada de aco_resume , nombramos el estado de la persona que llama - Main Co como "cedido".
void aco_yield (); Reduzca la ejecución de co y reanude co->main_co . La persona que llama de esta función debe ser un co no principal. Y co->main_co no debe ser nulo.
Después de la llamada de aco_yield , nombramos el estado de la persona que llama - co como "cedido".
aco_t * aco_get_co ();Devuelve el puntero del actual CO no principal. La persona que llama de esta función debe ser un co no principal.
void * aco_get_arg (); Igual a (aco_get_co()->arg) . Y también, la persona que llama de esta función debe ser un co no principal.
void aco_exit (); Además, haga lo mismo que aco_yield() , aco_exit() también establece co->is_end en 1 para marcar el co en el estado de "final".
void aco_destroy ( aco_t * co ); Destruye el co . El argumento co no debe ser nulo. La pila de salvamento privado también se destruiría si el co no es un co.
#define ACO_VERSION_MAJOR 1
#define ACO_VERSION_MINOR 2
#define ACO_VERSION_PATCH 4 Estas 3 macros se definen en el encabezado aco.h y el valor de ellas sigue la especificación: Versión semántica 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) Puede optar por incluir el encabezado "aco_assert_override.h" para anular el "afirmar" predeterminado en la aplicación Libaco como test_aco_synopsis.c (este encabezado incluye en la última lista de directivas de inclusión en el archivo fuente porque la "afirmación" de C es una definición de Cacro C) y definir el también otras 5 macros en la anterior. No incluya este encabezado en el archivo fuente de la aplicación si desea usar el "Afirmar" predeterminado C.
Para obtener más detalles, puede consultar el archivo de origen aco_assert_override.h.
Fecha: sábado 30 de junio UTC 2018.
Máquina: C5D.Large en AWS.
OS: RHEL-7.5 (Red Hat Enterprise Linux 7.5).
Aquí hay un breve resumen de la parte de referencia:
$ 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
Es esencial estar muy familiarizado con el estándar de Sys V ABI de Intel386 y X86-64 antes de comenzar a implementar o probar una biblioteca de Coroutine.
La prueba a continuación no tiene una descripción directa sobre la IP (puntero de instrucciones), SP (puntero de pila) y el ahorro/restauración entre la pila de guardado privado y la pila de acciones, ya que estas cosas son bastante triviales y fáciles de entender cuando se comparan con las restricciones ABI.
En el hilo del sistema operativo, el principal Coroutine main_co es la coroutina que debe ser creada y comenzó a ejecutarse primero, antes de que todas las otras coroutinas no principales lo hagan.
El siguiente diagrama es un ejemplo simple del cambio de contexto entre Main_CO y Co.
En esta prueba, suponemos que estamos bajo SYS V ABI de Intel386, ya que no hay diferencias fundamentales entre el Sys V ABI de Intel386 y X86-64. También suponemos que ninguno de los códigos cambiaría las palabras de control de FPU y MXCSR.

El siguiente diagrama es en realidad un modelo de ejecución de Coroutine simétrico que tiene un número ilimitado de Co-S no main y una CO principal. Esto está bien porque la coroutina asimétrica es solo un caso especial de la coroutina simétrica. Probar la corrección de la coroutina simétrica es un poco más desafiante que de la coroutina asimétrica y, por lo tanto, se volvería más divertido. (Libaco solo implementó la API de la coroutina asimétrica actualmente porque el significado semántico de la API de coroutina asimétrica es mucho más fácil de entender y usar que la coroutina simétrica).

Dado que el CO principal es la primera coroutina que comienza a ejecutarse, el primer contexto que cambia en este hilo del sistema operativo debe estar en forma de acosw(main_co, co) donde el segundo argumento co es un CO no principal.
Es fácil demostrar que solo existe dos tipos de transferencia de estado en el diagrama anterior:
Para demostrar la corrección de la implementación void* acosw(aco_t* from_co, aco_t* to_co) es equivalente para demostrar que todo el CO cumple constantemente con las restricciones de Sys v Abi antes y después de la llamada de acosw . Suponemos que la otra parte del código binario (excepto acosw ) en el CO ya había cumplido con el ABI (el compilador normalmente los generan correctamente).
Aquí hay un resumen de las limitaciones de los registros en la Convención de Llamadas de la función de Intel386 Sys V 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`)
(Para Intel386, el uso del registro se define en el "P13 - Tabla 2.3: Uso de registro" de Sys v ABI Intel386 V1.1, y para AMD64 está en "P23 - Figura 3.4: Uso de registro" de Sys V ABI AMD64 V1.0.)
Prueba:

El diagrama anterior es para el primer caso: "Produjo el estado CO -> init State Co".
Restricciones: C 1.0, 1.1, 1.2, 1.5 ( satisfecho ✓)
Los registros de scratch a continuación pueden contener cualquier valor en la entrada de una función:
EAX,ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Restricciones: C 1.3, 1.4 ( satisfecho ✓)
Dado que la pila de FPU ya debe estar vacía y el DF ya debe ser 0 antes de que acosw(co, to_co) se llamara (el Código Binario de CO ya se cumplió con el ABI), la restricción 1.3 y 1.4 cumplen con acosw .
Restricciones: C 2.0, 2.1, 2.2 ( satisfecho ✓)
C 2.0 y 2.1 ya está satisfecho. Como ya asumimos que nadie cambiará las palabras de control de FPU y MXCSR, C 2.2 también está satisfecho.

El diagrama anterior es para el segundo caso: rindió el estado de estado cogido.
Restricciones: C 1.0 ( satisfecho ✓)
EAX ya contiene el valor de retorno cuando acosw regresa a TO_CO (currículum).
Restricciones: C 1.1, 1.2, 1.5 ( satisfecho ✓)
Los registros de scratch a continuación pueden contener cualquier valor en la entrada de una función y después de la devolución de acosw :
ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Restricciones: C 1.3, 1.4 ( satisfecho ✓)
Dado que la pila de FPU ya debe estar vacía y el DF ya debe ser 0 antes de que acosw(co, to_co) se llamara (el Código Binario de CO ya se cumplió con el ABI), la restricción 1.3 y 1.4 cumplen con acosw .
Restricciones: C 2.0, 2.1, 2.2 ( satisfecho ✓)
C 2.0 y 2.1 está satisfecho porque hay guardado y restauración de los registros guardados de la Callee cuando acosw ha sido llamado/devuelto. Como ya asumimos que nadie cambiará las palabras de control de FPU y MXCSR, C 2.2 también está satisfecho.
El 1er acosw en el hilo debe ser el primer caso: Produjo el estado de estado de estado, y todo el siguiente acosw debe ser uno de los 2 casos anteriores. Securialmente, podríamos demostrar que "todo el CO constantemente cumple con las restricciones de Sys V ABI antes y después de la llamada de acosw ". Por lo tanto, la prueba está terminada.
Hay una nueva cosa llamada Red Zone in System v ABI X86-64:
El área de 128 bytes más allá de la ubicación apuntada por %RSP se considera reservada y no se modificará por señal o controladores de interrupción. Por lo tanto, las funciones pueden usar esta área para datos temporales que no se necesitan en las llamadas de función. En particular, las funciones de hoja pueden usar esta área para todo su marco de pila, en lugar de ajustar el puntero de la pila en el prólogo y el epílogo. Esta área se conoce como la zona roja.
Dado que la zona roja "no está conservada por la calle", simplemente no nos importa en absoluto en el contexto que cambia entre las coroutinas (porque el acosw es una función de hoja).
El final del área de argumento de entrada se alineará en un límite de byte 16 (32 o 64, si __m256 o __m512. En otras palabras, el valor (%esp + 4) es siempre un múltiplo de 16 (32 o 64) cuando el control se transfiere al punto de entrada de la función. El puntero de la pila, %ESP, siempre apunta al final del último marco de pila asignado.
-Intel386-Psabi-1.1: 2.2.2 El marco de la pila
El puntero de la pila, %RSP, siempre apunta al final del último marco de pila asignado.
- SYS V ABI AMD64 Versión 1.0: 3.2.2 El marco de la pila
Aquí hay un ejemplo de error en Libco de Tencent. El ABI afirma que el (E|R)SP siempre debe apuntar al final del último marco de pila asignado. Pero en el archivo COCTX_SWAP.S de Libco, el (E|R)SP se había utilizado para abordar la memoria en el montón.
Por defecto, el controlador de señal se invoca en la pila de proceso normal. Es posible organizar que el controlador de señal use una pila alternativa; Vea SigalStack (2) para una discusión sobre cómo hacer esto y cuándo podría ser útil.
- Man 7 señal: disposiciones de señal
Pueden suceder cosas terribles si el (E|R)SP apunta a la estructura de datos en el montón cuando llega la señal. (Usar el breakpoint y los comandos signal de GDB podrían producir dicho error convenientemente. Aunque mediante el uso de sigalstack para cambiar la pila de señal predeterminada podría aliviar el problema, pero aún así, ese tipo de uso de (E|R)SP todavía viola el ABI).
En resumen, si desea obtener el rendimiento ultra de Libaco, solo mantenga el uso de la pila de la CO no principal no estandalona en el punto de llamar aco_yield lo más pequeño posible. Y tenga mucho cuidado si desea pasar la dirección de una variable local de una CO a otro CO, ya que la variable local generalmente está en la pila de acciones . Asignar este tipo de variables desde el montón es siempre la elección más sabia.
En detalle, hay 5 consejos:
co_fp
/
/
f1 f2
/ /
/ f4
yield f3 f5
aco_yield para volver a la CO principal) tiene un gran impacto en el rendimiento del cambio de contexto entre las coroutinas, como ya se indican los resultados de referencia. En el diagrama anterior, el uso de la pila de la función F2, F3, F4 y F5 no tiene una influencia directa sobre el rendimiento del cambio de contexto, ya que no hay aco_yield cuando se ejecutan, mientras que el uso de la pila de CO_FP y F1 domina el valor de co->save_stack.max_cpsz y tiene una gran influencia sobre el rendimiento de cambio de contexto. La clave para mantener el uso de la pila de una función lo más baja posible es asignar las variables locales (especialmente las grandes) en el montón y administrar su ciclo de vida manualmente en lugar de asignarlas en la pila de forma predeterminada. La opción -fstack-usage de GCC es muy útil al respecto.
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 en CO_FP1 (línea 16) tiene una semántica totalmente diferente con el gl_ptr en la línea 7 de CO_FP0, y ese tipo de código probablemente corrompería la pila de ejecución de CO_FP1. Pero la línea 11 está bien porque las variables ct y la función inc_p están en el mismo contexto de Coroutina. Asignar ese tipo de variables (necesita compartir con otras coroutinas) en el montón simplemente resolvería tales problemas: 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 ();
}¡Las nuevas ideas son bienvenidas!
Agregue una macro como aco_mem_new , que es la combinación de algo como p = malloc(sz); assertalloc_ptr(p) .
Agregue una nueva API aco_reset para respaldar la reutilización de los objetos de Coroutine.
Admite otras plataformas (especialmente ARM y 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 ???
Soy un desarrollador de código abierto a tiempo completo. Cualquier cantidad de donaciones será muy apreciada y podría traerme un gran aliento.
Paypal
enlace de paypal.me
Alipay (支付 (宝 | 寶))


El logotipo de Libaco es generosamente donado por Peter Bech (Peteck). El logotipo tiene licencia bajo CC BY-ND 4.0. El sitio web de libaco.org también es amablemente contribuido por Peter Bech (Peteck).
Copyright (c) 2018, por Sen Han [email protected].
Bajo la licencia Apache, versión 2.0.
Consulte el archivo de licencia para obtener más detalles.