Libaco - Une bibliothèque coroutine asymétrique en C rapide et légère et légère.
Le nom de code de ce projet est Arkenstone?
La coroutine asymétrique et Arkenstone sont la raison pour laquelle il a été nommé aco .
Soutient actuellement Sys v ABI de Intel386 et X86-64.
Voici un bref résumé de ce projet:
L'expression « la plus rapide » ci-dessus signifie la mise en œuvre de commutation de contexte la plus rapide qui est conforme au Sys v ABI de Intel386 ou AMD64.
Les problèmes et les PR sont les bienvenus ???
Remarque: veuillez utiliser des versions au lieu du master pour construire le binaire final.
Outre cette lecture, vous pouvez également visiter la documentation depuis https://libaco.org/docs. Veuillez suivre cette lecture s'il y a des différences car la documentation sur le site Web peut être en retard à partir de cette lecture.
Production prête.
#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_synopsisPour plus d'informations, vous pouvez vous référer à la pièce "Build and Tester".

Il y a 4 éléments de base d'un état d'exécution ordinaire: {cpu_registers, code, heap, stack} .
Étant donné que les informations de code sont indiquées par ({E|R})?IP Register, et que l'adresse de la mémoire allouée à partir de Heap est normalement stockée dans la pile directement ou indirectement, nous pourrions donc simplifier les 4 éléments en seulement 2: {cpu_registers, stack} .

Nous définissons le main co comme la coroutine qui monopolise la pile par défaut du thread actuel. Et puisque le CO principal est le seul utilisateur de cette pile, nous devons seulement enregistrer / restaurer l'état des registres du processeur nécessaire du CO principal lorsqu'il a été donné à partir / repris à (commuté / interrompu).
Ensuite, la définition du non-main co est la coroutine dont la pile d'exécution est une pile qui n'est pas la pile par défaut du thread actuel et peut être partagée avec l'autre non-main CO. Ainsi, le CO non-principal doit avoir un tampon de mémoire de private save stack pour enregistrer / restaurer sa pile d'exécution lorsqu'il a été commuté / commandé (car le CO suivant / précédent peut / avait utilisé / utilisé la pile de partage comme pile d'exécution).

Il y a un cas particulier de CO non-main, c'est-à-dire standalone non-main co ce que nous avons appelé dans Libaco: la pile de partage de la coroutine non-principale n'a qu'un seul utilisateur de CO. Ainsi, il n'est pas nécessaire de réaliser / restaurer des trucs de sa pile de sauvegarde privée lorsqu'elle est commutée / interrompue car il n'y a pas d'autre CO touchera la pile d'exécution du CO autonome non-principal sauf lui-même.

Enfin, nous avons la vue d'ensemble de Libaco.
Il y a une partie de "preuve de correction" que vous pouvez trouver vraiment utile si vous souhaitez plonger dans l'interne de Libaco ou si vous souhaitez implémenter votre propre bibliothèque Coroutine.
Il est également fortement recommandé de lire le code source des tutoriels et de référence ensuite. Le résultat de référence est également très impressionnant et éclairant.
-m32 L'option -m32 de GCC pourrait vous aider à construire l'application i386 de Libaco sur une machine x86_64.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV Vous pouvez définir la macro C macro Global ACO_CONFIG_SHARE_FPU_MXCSR_ENV pour accélérer légèrement les performances de la commutation de contexte entre les coroutines si aucun de votre code ne modifierait les mots de contrôle de FPU et MXCSR. Si la macro n'est pas définie, tous les CO conserveraient sa propre copie des mots de contrôle FPU et MXCSR. Il est recommandé de toujours définir cette macro à l'échelle mondiale car il est très rare qu'une fonction doit définir son propre ENV spécial de FPU ou MXCSR au lieu d'utiliser l'env par défaut défini par l'ISO C. Mais vous n'aurez peut-être pas besoin de définir ce macro si vous n'en êtes pas sûr.
ACO_USE_VALGRIND Si vous souhaitez utiliser l'outil Memcheck de Valgrind pour tester l'application, vous devrez peut-être définir le C macro Cacro Global ACO_USE_VALGRIND pour permettre le soutien amical de Valgrind à Libaco. Mais il n'est pas recommandé de définir cette macro dans la version de version finale pour la raison. Vous devrez peut-être également installer les en-têtes Valgrind (le nom du package est "Valgrind-Devel" dans CentOS par exemple) pour créer une application Libaco avec C macro ACO_USE_VALGRIND défini. (Le Memcheck de Valgrind ne fonctionne bien avec le CO autonome actuellement. Dans le cas de la pile partagée utilisée par plus d'un CO non-main, le memcheck de Valgrind générerait de nombreux rapports faux positifs. Pour plus d'informations, vous pouvez vous référer à "test_aco_tutorial_6.c".).
ACO_USE_ASAN Le Cacro Global Cacro ACO_USE_ASAN permettrait le soutien amical du désinfectant d'adresse dans Libaco (support à la fois GCC et Clang).
Pour construire les suites de test de Libaco:
$ mkdir output
$ bash make.shIl existe également des options détaillées dans 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 bref, en utilisant -o no-valgrind si vous n'avez pas d'installation de Valgrind, -o no-m32 Si vous n'avez pas d'outils de développement GCC 32 bits installés sur un hôte AMD64.
Sur MacOS, vous devez remplacer les commandes sed et grep par défaut de MacOS par le GNU sed et grep pour exécuter make.sh et test.sh (une telle exigence serait supprimée à l'avenir):
$ brew install grep --with-default-names
$ brew install gnu-sed --with-default-names$ cd output
$ bash ../test.sh Le test_aco_tutorial_0.c dans ce référentiel montre l'utilisation de base de Libaco. Il n'y a qu'un seul CO principal et un non autonome non principal dans ce tutoriel. Les commentaires dans le code source sont également très utiles.
Le test_aco_tutorial_1.c montre l'utilisation de certaines statistiques de non-main co. La structure des données d' aco_t est très claire et est définie dans aco.h
Il y a un CO principal, un CO autonome non-principal et deux non-main CO (pointant vers la même pile de partage) dans test_aco_tutorial_2.c .
Le test_aco_tutorial_3.c montre comment utiliser libaco dans un processus multithread. Fondamentalement, une instance de Libaco est conçue uniquement pour fonctionner à l'intérieur d'un certain fil pour gagner les performances maximales de la commutation de contexte entre les coroutines. Si vous souhaitez utiliser Libaco dans un environnement multithread, simplement pour créer une instance de Libaco dans chacun des fils. Il n'y a pas de partage de données sur les fils à l'intérieur du Libaco, et vous devez faire face à la compétition de données entre plusieurs threads vous-même (comme ce que fait gl_race_aco_yield_ct dans ce tutoriel).
L'une des règles de Libaco est d'appeler aco_exit() pour mettre fin à l'exécution du CO non-main au lieu du return du style C de Carel C, sinon Libaco traitera ce comportement comme illégal et déclenchera le protecteur par défaut dont le travail consiste à enregistrer les informations d'erreur sur le cofreinde Co à Stderr et à abandonner le processus immédiatement. Le test_aco_tutorial_4.c affiche une telle situation "Offending Co".
Vous pouvez également définir votre propre protecteur pour remplacer celui par défaut (pour faire des trucs de "derniers mots" personnalisés). Mais peu importe dans quel cas, le processus sera interdit après l'exécution du protecteur. Le test_aco_tutorial_5.c montre comment définir la dernière fonction de mot personnalisée.
Le dernier exemple est un simple planificateur Coroutine dans test_aco_tutorial_6.c .
Il serait très utile de lire simultanément l'implémentation de l'API correspondante dans le code source lorsque vous lisez la description de l'API suivante de Libaco, car le code source est assez clair et facile à comprendre. Et il est également recommandé de lire tous les tutoriels avant de lire le document API.
Il est fortement recommandé de lire la partie meilleure pratique avant de commencer à écrire la véritable application de Libaco (en plus de décrire comment libérer vraiment les performances extrêmes de Libaco dans votre application, il existe également un avis sur la programmation de Libaco).
Remarque: Le contrôle de la version de Libaco suit la spécification: Versioning sémantique 2.0.0. Ainsi, l'API de la liste suivante a la garantie de compatibilité. (Veuillez noter qu'il n'y a pas une telle garantie pour l'API no dans la liste.)
typedef void ( * aco_cofuncp_t )( void );
void aco_thread_init ( aco_cofuncp_t last_word_co_fp );Initialise l'environnement Libaco dans le thread actuel.
Il stockera les mots de contrôle actuels de FPU et MXCSR dans une variable globale de thread-local.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV n'est pas définie, les mots de contrôle enregistrés seraient utilisés comme valeur de référence pour configurer les mots de contrôle du nouveau CO FPU et MXCSR (dans aco_create ) et que chaque CO maintiendrait sa propre copie de FPU et MXCSR des mots de contrôle lors de la commutation de contexte.ACO_CONFIG_SHARE_FPU_MXCSR_ENV est définie, tout le CO partage les mêmes mots de contrôle de FPU et MXCSR. Vous pouvez référer la partie "Build and Tester" de ce document pour plus d'informations à ce sujet. Et comme il l'a dit dans la pièce test_aco_tutorial_5.c de la pièce "Tutorials", lorsque le 1er argument last_word_co_fp n'est pas nul, alors la fonction pointée par last_word_co_fp remplacera le protecteur par défaut pour faire des "derniers mots" sur le CO offensant avant que le processus ne soit abandonné. Dans une telle dernière fonction de mot, vous pouvez utiliser aco_get_co pour obtenir le pointeur de l'offense co. Pour plus d'informations, vous pouvez lire test_aco_tutorial_5.c .
aco_share_stack_t * aco_share_stack_new ( size_t sz ); Égal à aco_share_stack_new2(sz, 1) .
aco_share_stack_t * aco_share_stack_new2 ( size_t sz , char guard_page_enabled ); Crée une nouvelle pile de partage avec une taille de mémoire consultative de sz dans les octets et peut avoir une page de garde (en lecture seule) pour la détection de Stack Overflow qui dépend du 2ème argument guard_page_enabled .
Pour utiliser la valeur de taille par défaut (2 Mo) Si le 1er argument sz est égal à 0. Après un calcul de l'alignement et de la réserve, cette fonction garantira la longueur valide finale de la pile de partage en retour:
final_valid_sz >= 4096final_valid_sz >= szfinal_valid_sz % page_size == 0 if the guard_page_enabled != 0 Et aussi proche de la valeur de sz que possible.
Lorsque la valeur du 2ème argument guard_page_enabled est 1, la pile de partage en retour aurait une page de garde en lecture seule pour la détection de la pile déborde tandis qu'une valeur 0 de guard_page_enabled signifie sans une telle page de garde.
Cette fonction renverra toujours une pile de partage valide.
void aco_share_stack_destroy ( aco_share_stack_t * sstk ); Destory the Share Stack sstk .
Assurez-vous que tous les CO dont la pile de partage est sstk est déjà détruit lorsque vous détruisez le 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 );Créez un nouveau CO.
S'il s'agit d'un main_co que vous souhaitez créer, appelez simplement: aco_create(NULL, NULL, 0, NULL, NULL) . Main Co est une coroutine autonome spéciale dont la pile de partage est la pile de thread par défaut. Dans le fil, Main Co est la coroutine qui devrait être créée et a commencé à s'exécuter avant que la coroutine non-main ne fasse.
Sinon, c'est un CO non-main que vous souhaitez créer:
main_co est le principal CO à laquelle le CO aco_yield dans le futur commutateur de contexte. main_co ne doit pas être nul;share_stack est l'adresse d'une pile de partage que le CO non-main que vous souhaitez créer utilisera comme pile d'exécution à l'avenir. share_stack ne doit pas être nul;save_stack_sz spécifie la taille d'initiation de la pile de sauvegarde privée de ce co. L'unité est en octets. Une valeur de 0 signifie utiliser la taille par défaut de 64 octets. Étant donné que le redimensionnement automatique se produirait lorsque la pile de sauvegarde privée n'est pas assez grande pour maintenir la pile d'exécution du CO lorsqu'elle doit donner la pile de partage qu'il occupe à un autre CO, vous ne devez généralement pas vous soucier de la valeur de sz . Mais cela apportera un impact sur les performances à l'allocateur de mémoire en cas de énorme quantité (disons 10 000 000) du CO redimensionne leur pile de sauvegarde privée en continu, il est donc très sage et fortement recommandé pour définir le save_stack_sz avec une valeur égale à la valeur maximale de co->save_stack.max_cpsz lorsque le CO est en cours d'exécution (vous pouvez référer à la "meilleure pratique" de ce document pour plus d'informations sur plus d'informations sur la plus grande information);co_fp est le pointeur de fonction d'entrée du co. co_fp ne doit pas être nul;arg est une valeur de pointeur et sera défini sur co->arg du CO pour créer. Il pourrait être utilisé comme argument d'entrée pour le CO.Cette fonction renverra toujours un co. Et nous nommons l'état du CO en retour comme "init" s'il s'agit d'un CO non-principal que vous souhaitez créer.
void aco_resume ( aco_t * co ); Rendement de l'appelant Main Co et pour démarrer ou continuer l'exécution de co .
L'appelant de cette fonction doit être un CO principal et doit être co->main_co . Et le 1er argument co doit être un non-main co.
La première fois que vous reprenez un co , il commence à exécuter la fonction pointant en pointant par co->fp . Si co a déjà été cédé, aco_resume le redémarre et continue l'exécution.
Après l'appel de aco_resume , nous nommons l'état de l'appelant - le CO principal comme "cédé".
void aco_yield (); Rendez l'exécution de co et CV co->main_co . L'appelant de cette fonction doit être un non-main co. Et co->main_co ne doit pas être nul.
Après l'appel d' aco_yield , nous nommons l'état de l'appelant - co comme "cédé".
aco_t * aco_get_co ();Renvoie le pointeur du non-main co. L'appelant de cette fonction doit être un non-main co.
void * aco_get_arg (); Égal à (aco_get_co()->arg) . Et aussi, l'appelant de cette fonction doit être un non-principal co.
void aco_exit (); En outre, faites de même que aco_yield() , aco_exit() a également défini co->is_end sur 1 pour marquer le co à l'état de "end".
void aco_destroy ( aco_t * co ); Détruisez le co . L'argument co ne doit pas être nul. La pile de sauvegarde privée aurait également été détruite si le co est un CO non-principal.
#define ACO_VERSION_MAJOR 1
#define ACO_VERSION_MINOR 2
#define ACO_VERSION_PATCH 4 Ces 3 macros sont définies dans l'en-tête aco.h et leur valeur suit les spécifications: Versioning sémantique 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) Vous pouvez choisir d'inclure l'en-tête "aco_assert_override.h" pour remplacer le C "Assert" par défaut dans l'application Libaco comme TEST_ACO_SYNOPSIS.C (cet en-tête, y compris, devrait également être dans la dernière liste des directives d'inclusion dans le fichier source parce que C "Assert" est également une définition de macro C) et définir les 5 autres macros ci-dessus. Veuillez ne pas inclure cet en-tête dans le fichier source de l'application si vous souhaitez utiliser le "affirmation" par défaut.
Pour plus de détails, vous pouvez vous référer au fichier source aco_assert_override.h.
Date: sam juin 30 UTC 2018.
Machine: C5d.large sur AWS.
OS: RHEL-7.5 (Red Hat Enterprise Linux 7.5).
Voici un bref résumé de la partie de référence:
$ 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
Il est essentiel de être très familier avec la norme de SYS V ABI de Intel386 et X86-64 avant de commencer à implémenter ou à prouver une bibliothèque Coroutine.
La preuve ci-dessous n'a aucune description directe de l'IP (pointeur d'instructions), SP (pointeur de pile) et de la sauvegarde / restauration entre la pile de sauvegarde privée et la pile de partage, car ces choses sont assez triviales et faciles à comprendre lorsqu'elles sont comparées avec les contraintes ABI.
Dans le fil du système d'exploitation, la principale coroutine main_co est la coroutine qui devrait être créée et a commencé à exécuter en premier, avant que toutes les autres coroutines non principales ne le fassent.
Le diagramme suivant est un exemple simple du contexte de contexte entre main_co et co.
Dans cette preuve, nous supposons simplement que nous sommes sous Sys v Abi de Intel386 car il n'y a pas de différences fondamentales entre le SYS V ABI de Intel386 et X86-64. Nous supposons également qu'aucun du code ne modifierait les mots de contrôle de FPU et MXCSR.

Le prochain diagramme est en fait un modèle de course de coroutine symétrique qui a un nombre illimité de co-co-non-main et un principal co-co. C'est bien car la coroutine asymétrique n'est qu'un cas particulier de la coroutine symétrique. Prouver que l'exactitude de la coroutine symétrique est un peu plus difficile que de la coroutine asymétrique et donc plus amusante qu'elle deviendrait. (Libaco n'a mis en œuvre que l'API de la coroutine asymétrique actuellement parce que la signification sémantique de l'API de coroutine asymétrique est beaucoup plus facile à comprendre et à utiliser que la coroutine symétrique.)

Étant donné que le CO principal est la 1ère coroutine commence à s'exécuter, le 1er commutateur de contexte dans ce thread de système d'exploitation doit se présenter sous la forme d' acosw(main_co, co) où le 2ème argument co est un non-main Co.
Il est facile de prouver qu'il n'existe que deux types de transfert d'état dans le diagramme ci-dessus:
Pour prouver l'exactitude de void* acosw(aco_t* from_co, aco_t* to_co) est équivalente pour prouver que tous les CO se conforment constamment aux contraintes de SYS V ABI avant et après l'appel d' acosw . Nous supposons que l'autre partie du code binaire (sauf acosw ) dans le CO était déjà conforme à l'ABI (elles sont normalement générées correctement par le compilateur).
Voici un résumé des contraintes des registres dans la convention d'appel de la fonction d'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`)
(Pour Intel386, l'utilisation du registre est définie dans le "P13 - Tableau 2.3: Utilisation du registre" de SYS V ABI Intel386 v1.1, et pour AMD64 est dans "P23 - Figure 3.4: Utilisation du registre" de SYS V ABI AMD64 V1.0.)
Preuve:

Le diagramme ci-dessus est pour le 1er cas: "Cendu State Co -> init State Co".
Contraintes: C 1.0, 1.1, 1.2, 1.5 ( satisfait ✓)
Les registres à gratter ci-dessous peuvent maintenir n'importe quelle valeur à l'entrée d'une fonction:
EAX,ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Contraintes: C 1.3, 1.4 ( satisfait ✓)
Étant donné que la pile de FPU doit déjà être vide et que le DF doit déjà être 0 avant que acosw(co, to_co) ne soit appelé (le code binaire de CO est déjà respecté à l'ABI), la contrainte 1.3 et 1.4 est respectée par acosw .
Contraintes: C 2.0, 2.1, 2.2 ( satisfait ✓)
C 2.0 et 2.1 est déjà satisfait. Comme nous avons déjà supposé que personne ne changera les mots de contrôle de FPU et MXCSR, C 2.2 est également satisfait.

Le diagramme ci-dessus est pour le 2ème cas: un état cédé Co -> STATE CODED CO.
Contraintes: C 1.0 ( satisfait ✓)
Eax tenant déjà la valeur de retour lorsque acosw revient à TO_CO (CV).
Contraintes: C 1.1, 1.2, 1.5 ( satisfait ✓)
Les registres à gratter ci-dessous peuvent maintenir n'importe quelle valeur à l'entrée d'une fonction et après le retour d' acosw :
ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Contraintes: C 1.3, 1.4 ( satisfait ✓)
Étant donné que la pile de FPU doit déjà être vide et que le DF doit déjà être 0 avant que acosw(co, to_co) ne soit appelé (le code binaire de CO est déjà respecté à l'ABI), la contrainte 1.3 et 1.4 est respectée par acosw .
Contraintes: C 2.0, 2.1, 2.2 ( satisfait ✓)
C 2.0 & 2.1 est satisfait car il y a une économie et une restauration des registres enregistrés Callee lorsque acosw a été appelé / retourné. Comme nous avons déjà supposé que personne ne changera les mots de contrôle de FPU et MXCSR, C 2.2 est également satisfait.
Le 1er acosw dans le thread doit être le 1er cas: CODED CO -> Init State Co, et toute la prochaine acosw doit être l'un des 2 cas ci-dessus. Séquentiellement, nous pourrions prouver que "tous les CO se conforment constamment aux contraintes de SYS V ABI avant et après l'appel de acosw ". Ainsi, la preuve est terminée.
Il y a une nouvelle chose appelée Rouge Zone dans System V ABI x86-64:
La zone de 128 octets au-delà de l'emplacement indiqué par% RSP est considérée comme réservée et ne doit pas être modifiée par des gestionnaires de signaux ou d'interruption. Par conséquent, les fonctions peuvent utiliser ce domaine pour des données temporaires qui ne sont pas nécessaires sur les appels de fonction. En particulier, les fonctions des feuilles peuvent utiliser cette zone pour toute leur cadre de pile, plutôt que d'ajuster le pointeur de pile dans le prologue et l'épilogue. Cette zone est connue sous le nom de zone rouge.
Étant donné que la zone rouge n'est "pas conservée par la Callee", nous ne nous en soucions tout simplement pas du tout dans le contexte de commutation entre les coroutines (car l' acosw est une fonction foliaire).
La fin de la zone d'argument d'entrée doit être alignée sur un 16 (32 ou 64, si __M256 ou __M512 est transmis sur la frontière d'octet. En d'autres termes, la valeur (% ESP + 4) est toujours un multiple de 16 (32 ou 64) lorsque le contrôle est transféré au point d'entrée de la fonction. Le pointeur de pile,% ESP, pointe toujours vers la fin du dernier cadre de pile alloué.
- Intel386-psabi-1.1: 2.2.2 Le cadre de pile
Le pointeur de pile,% RSP, pointe toujours vers la fin du dernier cadre de pile alloué.
- SYS V ABI AMD64 Version 1.0: 3.2.2 Le cadre de pile
Voici un exemple de bogue dans Libco de Tencent. L'ABI indique que le (E|R)SP doit toujours pointer vers la fin du dernier cadre de pile alloué. Mais dans le fichier coctx_swap.s de Libco, le (E|R)SP avait été utilisé pour aborder la mémoire sur le tas.
Par défaut, le gestionnaire de signaux est invoqué sur la pile de processus normale. Il est possible d'organiser que le gestionnaire de signaux utilise une pile alternative; Voir Sigalstack (2) pour une discussion sur la façon de procéder et quand cela pourrait être utile.
- Homme 7 Signal: Dispositions du signal
Des choses terribles peuvent se produire si le (E|R)SP pointe vers la structure des données sur le tas lorsque le signal arrive. (L'utilisation des commandes breakpoint et signal de GDB pourrait produire un tel bug de manière pratique. Bien qu'en utilisant sigalstack pour modifier la pile de signal par défaut puisse atténuer le problème, mais toujours, ce type d'utilisation de (E|R)SP viole toujours l'Abi.)
En résumé, si vous souhaitez obtenir les performances ultra de Libaco, gardez simplement l'utilisation de la pile du CO non-standalone non-principal au point d'appeler aco_yield aussi petit que possible. Et soyez très prudent si vous souhaitez passer l'adresse d'une variable locale d'un CO à un autre CO, car la variable locale est généralement sur la pile de partage . La répartition de ce type de variables du tas est toujours le choix plus sage.
En détail, il y a 5 conseils:
co_fp
/
/
f1 f2
/ /
/ f4
yield f3 f5
aco_yield pour redonner à Main Co) a un impact important sur les performances de la commutation de contexte entre les coroutines, comme déjà indiqué par les résultats de référence. Dans le diagramme ci-dessus, l'utilisation de la pile de la fonction F2, F3, F4 et F5 n'a aucune influence directe sur les performances de commutation de contexte car il n'y a pas aco_yield lorsqu'ils s'exécutent, tandis que l'utilisation de la pile de Co_FP et F1 domine la valeur de la valeur de co->save_stack.max_cpsz et a une grande influence sur la performance de commutation de contexte. La clé pour maintenir l'utilisation de la pile d'une fonction aussi faible que possible est d'allouer les variables locales (en particulier les grandes) sur le tas et de gérer leur cycle de vie manuellement au lieu de les allouer par défaut sur la pile. L'option -fstack-usage de GCC est très utile à ce sujet.
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 dans CO_FP1 (ligne 16) a une sémantique totalement différente avec le gl_ptr dans la ligne 7 de Co_FP0, et ce type de code corromprait probablement la pile d'exécution de Co_FP1. Mais la ligne 11 est très bien car la variable ct et la fonction inc_p sont dans le même contexte coroutine. La répartition de ce type de variables (besoin de partager avec d'autres coroutines) sur le tas résoudrait simplement de tels problèmes: 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 ();
}Les nouvelles idées sont les bienvenues!
Ajoutez une macro comme aco_mem_new qui est la combinaison de quelque chose comme p = malloc(sz); assertalloc_ptr(p) .
Ajoutez une nouvelle API aco_reset pour soutenir la réutilisabilité des objets Coroutine.
Prise en charge d'autres plateformes (en particulier ARM et 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 ???
Je suis un développeur open source à temps plein. Tout montant des dons sera très apprécié et pourrait m'apporter de grands encouragements.
Paypal
lien paypal.me
Alipay (支付 (宝 | 寶))


Le logo de Libaco est généreusement donné par Peter Bech (Peteck). Le logo est licencié sous CC BY-ND 4.0. Le site Web de Libaco.org est également aimablement apporté par Peter Bech (Peteck).
Copyright (C) 2018, par Sen Han [email protected].
Sous la licence Apache, version 2.0.
Voir le fichier de licence pour plus de détails.