LIBACO - Uma biblioteca de coroutina assimétrica rápida e leve e leve.
O nome do código deste projeto é Arkenstone?
A Coroutine & Arkenstone assimétrica é a razão pela qual foi nomeada aco .
Atualmente, suporta SYS V ABI dos Intel386 e X86-64.
Aqui está um breve resumo deste projeto:
A frase " mais rápida " acima significa a implementação de comutação de contexto mais rápida que está em conformidade com o SYS V ABI do Intel386 ou AMD64.
Problemas e PRs são bem -vindos ???
NOTA: Por favor, use lançamentos em vez do master para construir o binário final.
Além deste readme, você também pode visitar a documentação de https://libaco.org/docs. Siga este ReadMe se houver alguma diferença, porque a documentação no site pode estar atrasada neste Readme.
Produção pronta.
#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 obter mais informações, você pode consultar a parte "Build and Test".

Existem 4 elementos básicos de um estado de execução comum: {cpu_registers, code, heap, stack} .
Como as informações do código são indicadas por ({E|R})?IP , e o endereço da memória alocado da heap é normalmente armazenado na pilha direta ou indiretamente, assim podemos simplificar os 4 elementos em apenas 2 deles: {cpu_registers, stack} .

Definimos o main co como a coroutina que monopoliza a pilha padrão do thread atual. E como o CO principal é o único usuário desta pilha, precisamos salvar/restaurar/restaurar o estado dos registros da CPU necessário quando ele foi rendido/retomado-to (comutado/comutado).
Em seguida, a definição do non-main co é a coroutina cuja pilha de execução é uma pilha que não é a pilha padrão do thread atual e pode ser compartilhada com o outro CO. Assim, o Co-Main Co deve ter um buffer de memória de private save stack para salvar/restaurar sua pilha de execução quando for alterada/desligada (porque o CO de sucesso/a preceding pode/se usou/usou a pilha de compartilhamento como sua pilha de execução).

Existe um caso especial de Co-Main Co, que é standalone non-main co que chamamos de Libaco: a pilha de compartilhamentos da coroutina não-main tem apenas um usuário de CO. Portanto, não há necessidade de economizar/restaurar coisas de sua pilha de salvamento privada quando ela for alterada/alternada, pois não há outro CO tocará a pilha de execução do coonete não-main independente, exceto por si mesmo.

Finalmente, temos o quadro geral de Libaco.
Existe uma peça de "prova de correção" que você pode achar realmente útil se desejar mergulhar no interno da libaco ou desejar implementar sua própria biblioteca coroutine.
Também é altamente recomendável ler o código -fonte dos tutoriais e da referência em seguida. O resultado da referência também é muito impressionante e esclarecedor.
-m32 A opção -m32 do GCC pode ajudá -lo a criar o aplicativo i386 do libaco em uma máquina x86_64.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV Você pode definir a macro C global ACO_CONFIG_SHARE_FPU_MXCSR_ENV para acelerar o desempenho da alternância de contexto entre os coroutinas um pouco se nenhum do seu código alteraria as palavras de controle da FPU e MXCSR. Se a macro não for definida, todo o CO manteria sua própria cópia das palavras de controle da FPU e MXCSR. Recomenda -se sempre definir essa macro globalmente, pois é muito raro que uma função precise definir seu próprio Env especial da FPU ou MXCSR em vez de usar o inadimplente definido pela ISO C. Mas você pode não precisar definir essa macro se não tiver certeza disso.
ACO_USE_VALGRIND Se você deseja usar o Memcheck de Valgrind para testar o aplicativo, pode ser necessário definir a macro C global C ACO_USE_VALGRIND para permitir o apoio amigável de Valgrind em Libaco. Mas não é recomendável definir essa macro na versão final da versão para o motivo do desempenho. Você também pode precisar instalar os cabeçalhos Valgrind (o nome do pacote é "Valgrind-devel" no CentOS, por exemplo) para criar o aplicativo Libaco com M macro ACO_USE_VALGRIND definido. (O Memcheck de Valgrind só funciona bem com o Co-Standalone Co. No caso da pilha compartilhada usada por mais de um co-main Co, o Memcheck of Valgrind geraria muitos relatórios falsos positivos. Para obter mais informações que você pode consultar "test_aco_tutorial_6.c".)
ACO_USE_ASAN A macro C global ACO_USE_ASAN permitiria o apoio amigável do desinfetante de endereço em Libaco (apoiar o GCC e o CLANG).
Para construir as suítes de teste de Libaco:
$ mkdir output
$ bash make.shHá também algumas opções detalhadas em 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 Em suma, usando -o no-valgrind se você não tiver cabeçalhos Valgrind instalados, -o no-m32 se você não tiver ferramentas de desenvolvimento GCC de 32 bits instaladas em um host AMD64.
No MacOS, você precisa substituir os comandos de sed e grep padrão do macOS pelo GNU sed e grep para executar make.sh e test.sh (esse requisito seria removido no futuro):
$ brew install grep --with-default-names
$ brew install gnu-sed --with-default-names$ cd output
$ bash ../test.sh O test_aco_tutorial_0.c neste repositório mostra o uso básico do libaco. Existe apenas um CO principal e um CO não-especializado neste tutorial. Os comentários no código -fonte também são muito úteis.
O test_aco_tutorial_1.c mostra o uso de algumas estatísticas do co. A estrutura de dados do aco_t é muito clara e é definida no aco.h
Há um CO principal, um Co-Main Co-Main e dois Co-Main Co (apontando para a mesma pilha de compartilhamento) em test_aco_tutorial_2.c .
O test_aco_tutorial_3.c mostra como usar o libaco em um processo multithread. Basicamente, uma instância de libaco foi projetada apenas para funcionar dentro de um determinado thread para obter o desempenho máximo da troca de contexto entre coroutinas. Se você deseja usar o Libaco em ambiente multithread, basta criar uma instância de libaco em cada um dos threads. Não há compartilhamento de dados nos threads dentro do Libaco e você deve lidar com a competição de dados entre vários threads (como o que gl_race_aco_yield_ct faz neste tutorial).
Uma das regras do Libaco é ligar para aco_exit() para encerrar a execução do COM não-gerente, em vez do return padrão do estilo C direto, caso contrário, a libaco tratará o comportamento como ilegal e acionará o protetor padrão cujo trabalho é registrar as informações de erro sobre o co ofensivo e abortar o processo imediatamente. O test_aco_tutorial_4.c mostra essa situação de "co ofensiva".
Você também pode definir seu próprio protetor para substituir o padrão (para fazer algumas coisas personalizadas de "últimas palavras"). Mas não importa em que caso, o processo será abortado após a execução do protetor. O test_aco_tutorial_5.c mostra como definir a função de última palavra personalizada.
O último exemplo é um agendador de coroutina simples em test_aco_tutorial_6.c .
Seria muito útil ler a implementação da API correspondente no código -fonte simultaneamente quando você estiver lendo a seguinte descrição da API do Libaco, pois o código -fonte é bastante claro e fácil de entender. E também é recomendável ler todos os tutoriais antes de ler o documento da API.
É fortemente recomendado ler a parte da melhor prática antes de começar a escrever a aplicação real do Libaco (além de descrever como liberar realmente o desempenho extremo de Libaco em seu aplicativo, também há um aviso sobre a programação do Libaco).
Nota: O controle da versão do libaco segue a especificação: Versão Semântica 2.0.0. Portanto, a API na lista a seguir tem a garantia de compatibilidade. (Observe que não existe essa garantia para a API NO na lista.)
typedef void ( * aco_cofuncp_t )( void );
void aco_thread_init ( aco_cofuncp_t last_word_co_fp );Inicializa o ambiente Libaco no encadeamento atual.
Ele armazenará as palavras de controle atuais da FPU e MXCSR em uma variável global local de Thread.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV não for definida, as palavras de controle salvas seriam usadas como um valor de referência para configurar as palavras de controle da nova FPU e MXCSR (em aco_create ) e cada um CO manteria sua própria cópia da FPU e MXCSR Control Words durante durante o Control de Controle durante a FPU.ACO_CONFIG_SHARE_FPU_MXCSR_ENV for definida, todos os CO compartilham as mesmas palavras de controle da FPU e MXCSR. Você pode consultar a parte "Build and Test" deste documento para obter mais informações sobre isso. E como disse na parte test_aco_tutorial_5.c da parte "tutoriais", quando o 1º argumento last_word_co_fp não é nulo, a função apontada por last_word_co_fp substituirá o protetor padrão para fazer algumas "últimas palavras" sobre o CO ofensivo antes do processo. Nessa função da última palavra, você pode usar aco_get_co para obter o ponteiro do co ofensivo. Para mais informações, você pode ler 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 ); Cria uma nova pilha de compartilhamento com um tamanho de memória consultiva de sz em bytes e pode ter uma página de guarda (somente leitura) para a detecção do excesso de pilha, que depende do segundo argumento guard_page_enabled .
Para usar o valor do tamanho padrão (2MB) Se o 1º argumento sz for igual a 0. Após algum cálculo de alinhamento e reserva, essa função garantirá a duração final válida da pilha de compartilhamento em troca:
final_valid_sz >= 4096final_valid_sz >= szfinal_valid_sz % page_size == 0 if the guard_page_enabled != 0 E o mais próximo possível do valor do sz .
Quando o valor do segundo argumento guard_page_enabled é 1, a pilha de compartilhamentos em troca teria uma página de guarda somente leitura para a detecção do transbordamento da pilha, enquanto um valor 0 de guard_page_enabled significa sem essa página de guarda.
Esta função sempre retornará uma pilha de compartilhamento válida.
void aco_share_stack_destroy ( aco_share_stack_t * sstk ); Destory the share pilha sstk .
Certifique -se de que todo o CO cuja pilha de compartilhamento seja sstk já esteja destruída quando você destruir o 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 );Crie um novo CO.
Se for um main_co que você deseja criar, basta chamar: aco_create(NULL, NULL, 0, NULL, NULL) . O principal CO é uma corota especial em uma pilha de compartilhamentos é a pilha de threads padrão. No segmento, o principal CO é a coroutina que deve ser criada e começou a executar antes que todas as outras corotas não main-main.
Caso contrário, é um CO não-especial que você deseja criar:
main_co é o principal CO do que o CO aco_yield no futuro comutação de contexto. main_co não deve ser nulo;share_stack é o endereço de uma pilha de ações que o Co-Main Co que você deseja criar será usado como pilha de execução no futuro. share_stack não deve ser nulo;save_stack_sz especifica o tamanho inicial da pilha de salvamento privada deste co. A unidade está em bytes. Um valor de 0 significa usar o tamanho padrão 64 bytes. Como o redimensionamento automático aconteceria quando a pilha de salvamento privada não for grande o suficiente para manter a pilha de execução do CO quando precisar produzir a pilha de ações que está ocupando para outro CO, você geralmente não deve se preocupar com o valor da sz . Mas trará algum impacto de desempenho para o alocador de memória quando uma quantidade enorme (digamos, 10.000.000) do CO, redimensiona sua pilha de salvamento privada continuamente, por co->save_stack.max_cpsz é muito sábio e altamente recomendado para definir o save_stack_sz com um valor igual ao melhor valor;co_fp é o ponteiro da função de entrada do CO. co_fp não deve ser nulo;arg é um valor de ponteiro e será definido como co->arg do CO para criar. Pode ser usado como um argumento de entrada para o CO.Esta função sempre retornará um CO válido. E nomeamos o estado do CO em troca como "init", se for um co não-especial que você deseja criar.
void aco_resume ( aco_t * co ); Rendimento do Caller Main Co e para iniciar ou continuar a execução do co .
O chamador desta função deve ser o principal CO e deve ser co->main_co . E o 1º argumento co deve ser um co-principal.
Na primeira vez em que você retoma um co , ele começa a executar a função apontando por co->fp . Se co já foi rendida, aco_resume reinicia e continua a execução.
Após o chamado de aco_resume , nomeamos o estado do chamador - principal como "rendido".
void aco_yield (); Gerar a execução do co e retomar co->main_co . O chamador desta função deve ser um co-principal. E co->main_co não deve ser nulo.
Após a chamada de aco_yield , nomeamos o estado do chamador - co como "rendido".
aco_t * aco_get_co ();Retorne o ponteiro do atual co-principal co. O chamador desta função deve ser um co-principal.
void * aco_get_arg (); Igual a (aco_get_co()->arg) . E também, o chamador desta função deve ser um CO.
void aco_exit (); Além disso, faça o mesmo que aco_yield() , aco_exit() também define co->is_end como 1 para marcar o co no status de "end".
void aco_destroy ( aco_t * co ); Destrua o co . O argumento co não deve ser nulo. A pilha de salvamento privada também seria destruída se o co for um co-principal.
#define ACO_VERSION_MAJOR 1
#define ACO_VERSION_MINOR 2
#define ACO_VERSION_PATCH 4 Esses 3 macros são definidos no cabeçalho aco.h e o valor deles segue a especificação: Semântica Versão 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) Você pode optar por incluir o cabeçalho "aco_assert_override.h" para substituir o padrão C "afirmam" no aplicativo Libaco como test_aco_synopsis.c (esse cabeçalho, inclusive, deve estar na última lista de diretrizes no arquivo de origem, porque o C "Assert" também é uma definição de macro. Por favor, não inclua este cabeçalho no arquivo de origem do aplicativo, se desejar usar o padrão C "Assert".
Para obter mais detalhes, você pode consultar o arquivo de origem aco_assert_override.h.
Data: Sáb 30 de junho UTC 2018.
Máquina: C5D.Large na AWS.
OS: RHEL-7.5 (Red Hat Enterprise Linux 7.5).
Aqui está um breve resumo da parte de referência:
$ 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
É essencial estar muito familiarizado com o padrão do SYS V ABI do Intel386 e X86-64 antes de começar a implementar ou provar uma biblioteca corooutina.
A prova abaixo não possui descrição direta sobre o IP (ponteiro de instrução), SP (Ponteiro da pilha) e a economia/restauração entre a pilha de salvamento privada e a pilha de compartilhamentos, pois essas coisas são bastante triviais e fáceis de entender quando são comparadas com as restrições da ABI.
No thread do sistema operacional, a coroutina principal main_co é a coroutina que deve ser criada e começou a executar primeiro, antes que todas as outras coroutinas não-principais.
O próximo diagrama é um exemplo simples da alternância de contexto entre main_co e co.
Nesta prova, apenas assumimos que estamos sob o SYS V ABI da Intel386, pois não há diferenças fundamentais entre o SYS V ABI do Intel386 e X86-64. Também assumimos que nenhum código alteraria as palavras de controle da FPU e MXCSR.

O próximo diagrama é na verdade um modelo de corrida de coroutina simétrica, que possui um número ilimitado de co-s não-especial e um co-principal. Isso é bom porque a coroutina assimétrica é apenas um caso especial da coroutina simétrica. Provar a correção da coroutina simétrica é um pouco mais desafiadora do que da coroutina assimétrica e, portanto, mais divertida se tornaria. (Libaco implementou apenas a API da coroutina assimétrica atualmente porque o significado semântico da API de coroutina assimétrica é muito mais fácil de entender e usar do que a coroutina simétrica).)

Como o CO principal é a 1ª coroutina começa a ser executada, a 1ª troca de contexto neste thread do sistema operacional deve estar na forma de acosw(main_co, co) , onde o segundo argumento co é um co-main Co.
É fácil provar que existe apenas dois tipos de transferência de estado no diagrama acima:
Para provar a correção do void* acosw(aco_t* from_co, aco_t* to_co) é equivalente a provar todo o CO constantemente as restrições do SYS V ABI antes e depois da chamada do acosw . Assumimos que a outra parte do código binário (exceto acosw ) no CO já havia cumprido o ABI (eles normalmente são gerados pelo compilador corretamente).
Aqui está um resumo das restrições dos registros na Convenção de Chamada de Função do 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 o Intel386, o uso do registro é definido no "P13 - Tabela 2.3: Uso do registro" do SYS v ABI Intel386 v1.1, e para AMD64 está em "P23 - Figura 3.4: Uso de registro" do SYS V ABI AMD64 V1.0.
Prova:

O diagrama acima é para o 1º caso: "Rendimento do estado Co -> init State Co".
Restrições: C 1.0, 1.1, 1.2, 1,5 ( satisfeito ✓)
Os registros de arranhões abaixo podem manter qualquer valor na entrada de uma função:
EAX,ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Restrições: C 1.3, 1.4 ( satisfeito ✓)
Como a pilha da FPU já deve estar vazia e o DF já deve estar 0 antes de acosw(co, to_co) ser chamado (o código binário de CO já é cumprido ao ABI), a restrição 1.3 e 1.4 é cumprida pelo acosw .
Restrições: C 2.0, 2.1, 2.2 ( satisfeito ✓)
C 2.0 e 2.1 já estão satisfeitos. Como já assumimos que ninguém mudará as palavras de controle da FPU e MXCSR, C 2.2 também é satisfeito.

O diagrama acima é para o segundo caso: produziu o estado co -> cedido em estado co.
Restrições: C 1.0 ( satisfeito ✓)
A EAX já segura o valor de retorno quando acosw retornar para To_Co (currículo).
Restrições: C 1.1, 1.2, 1,5 ( satisfeito ✓)
Os registros de arranhões abaixo podem manter qualquer valor na entrada de uma função e após o retorno do acosw :
ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Restrições: C 1.3, 1.4 ( satisfeito ✓)
Como a pilha da FPU já deve estar vazia e o DF já deve estar 0 antes de acosw(co, to_co) ser chamado (o código binário de CO já é cumprido ao ABI), a restrição 1.3 e 1.4 é cumprida pelo acosw .
Restrições: C 2.0, 2.1, 2.2 ( satisfeito ✓)
C 2.0 e 2.1 são satisfeitos porque há salvamento e restauração dos registros salvos de Callee quando acosw foi chamado/devolvido. Como já assumimos que ninguém mudará as palavras de controle da FPU e MXCSR, C 2.2 também é satisfeito.
O 1º acosw no thread deve ser o primeiro caso: produziu o estado co -> init state co, e todo o próximo acosw deve ser um dos dois casos acima. Sequencialmente, poderíamos provar que "todo o CO constantemente cumpre as restrições do SYS V ABI antes e depois do chamado do acosw ". Assim, a prova está concluída.
Há uma coisa nova chamada Red Zone no System V Abi X86-64:
A área de 128 bytes além da localização apontada por %RSP é considerada reservada e não deve ser modificada por sinal de sinal ou interrupção. Portanto, as funções podem usar esta área para dados temporários que não são necessários nas chamadas de função. Em particular, as funções foliares podem usar essa área para toda a sua estrutura de pilha, em vez de ajustar o ponteiro da pilha no prólogo e no epílogo. Esta área é conhecida como zona vermelha.
Como a zona vermelha "não é preservada pelo Callee", simplesmente não nos importamos com isso no contexto alternando entre coroutinas (porque o acosw é uma função de folha).
O final da área de argumento de entrada deve ser alinhado em um limite de byte 16 (32 ou 64, se __m256 ou __m512 for passado. Em outras palavras, o valor (%ESP + 4) é sempre um múltiplo de 16 (32 ou 64) quando o controle é transferido para o ponto de entrada da função. O ponteiro da pilha, %ESP, sempre aponta para o final do mais recente quadro de pilha alocada.
-Intel386-psabi-1.1: 2.2.2 O quadro da pilha
O ponteiro da pilha, %RSP, sempre aponta para o final do mais recente quadro de pilha alocada.
- SYS V ABI AMD64 Versão 1.0: 3.2.2 O quadro da pilha
Aqui está um exemplo de bug no Libco de Tencent. O ABI afirma que o (E|R)SP deve sempre apontar para o final do mais recente quadro de pilha alocada. Mas no arquivo coctx_swap.s da libco, o (E|R)SP foi usado para abordar a memória na pilha.
Por padrão, o manipulador de sinal é invocado na pilha de processo normal. É possível providenciar que o manipulador de sinal use uma pilha alternativa; Consulte SigalStack (2) para uma discussão sobre como fazer isso e quando pode ser útil.
- Man 7 Signal: Disposições de sinal
Coisas terríveis podem acontecer se o (E|R)SP estiver apontando para a estrutura de dados na pilha quando o sinal ocorrer. (Usando os comandos de breakpoint e signal do GDB, pode produzir esse bug convenientemente. Embora usando sigalstack para alterar a pilha de sinal padrão possa aliviar o problema, mas ainda assim, esse tipo de uso de (E|R)SP ainda viola o ABI.)
Em resumo, se você deseja obter o Ultra Performance do Libaco, apenas mantenha o uso da pilha do Co não-Main Co no ponto de chamar aco_yield o mais pequeno possível. E tenha muito cuidado se quiser passar o endereço de uma variável local de um CO para outro, pois a variável local geralmente está na pilha de compartilhamentos . Alocar esse tipo de variável da pilha é sempre a escolha mais sábia.
Em detalhes, existem 5 dicas:
co_fp
/
/
f1 f2
/ /
/ f4
yield f3 f5
aco_yield para render novamente ao Co principal) tem um grande impacto no desempenho da troca de contexto entre os coroutines, como já indicado pelos resultados da referência. No diagrama acima, o uso da pilha da função F2, F3, F4 e F5 não tem influência direta sobre o desempenho da troca de contexto, pois não há aco_yield quando eles estão executando, enquanto o uso da pilha de CO_FP e F1 domina o valor do co->save_stack.max_cpsz do contexto. A chave para manter o uso da pilha de uma função o mais baixo possível é alocar as variáveis locais (especialmente as grandes) na pilha e gerenciar seu ciclo de vida manualmente, em vez de alocá -las na pilha por padrão. A opção -fstack-usage do GCC é muito útil sobre isso.
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 em Co_FP1 (linha 16) possui semântica totalmente diferente com o gl_ptr na linha 7 de co_fp0, e esse tipo de código provavelmente corrompe a pilha de execução de co_fp1. Mas a linha 11 é boa porque a variável ct e função inc_p estão no mesmo contexto de coroutina. Alocar esse tipo de variável (precisa compartilhar com outras coroutinas) na pilha simplesmente resolveria esses 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 ();
}Novas idéias são bem -vindas!
Adicione uma macro como aco_mem_new , que é a combinação de algo como p = malloc(sz); assertalloc_ptr(p) .
Adicione uma nova API aco_reset para apoiar a reutilização dos objetos coroutine.
Apoie outras plataformas (especialmente 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 ???
Sou um desenvolvedor de código aberto em tempo integral. Qualquer quantidade das doações será muito apreciada e poderá me trazer um grande incentivo.
PayPal
link PayPal.me
Alipay (支付 (宝 | 寶))


O logotipo de Libaco é generosamente doado por Peter Bech (Peteck). O logotipo está licenciado no CC BY-ND 4.0. O site do libaco.org também é gentilmente contribuído por Peter Bech (Peteck).
Copyright (c) 2018, por sen han [email protected].
Sob a licença Apache, versão 2.0.
Consulte o arquivo de licença para obter detalhes.