Libaco - пылающая быстрое и легкое C -асимметричная библиотека Coroutine.
Кодовое название этого проекта - Arkenstone?
Асимметричный Coroutine & Arkenstone - причина, по которой он был назван aco .
В настоящее время поддерживает Sys v Abi из Intel386 и X86-64.
Вот краткое изложение этого проекта:
Фраза « быстрая » в вышеупомянутой означает самую быструю реализацию переключения контекста, которая соответствует системе V abi Intel386 или AMD64.
Проблемы и пиарщики приветствуются ???
Примечание. Пожалуйста, используйте релизы вместо master , чтобы построить последний бинар.
Помимо этого Readme, вы также можете посетить документацию с https://libaco.org/docs. Пожалуйста, следите за этим Readme, если есть какие -либо различия, потому что документация на веб -сайте может отставать от этого Readme.
Производство готово.
#include "aco.h"
#include <stdio.h>
// this header would override the default C `assert`;
// you may refer the "API : MACROS" part for more details.
#include "aco_assert_override.h"
void foo ( int ct ) {
printf ( "co: %p: yield to main_co: %dn" , aco_get_co (), * (( int * )( aco_get_arg ())));
aco_yield ();
* (( int * )( aco_get_arg ())) = ct + 1 ;
}
void co_fp0 () {
printf ( "co: %p: entry: %dn" , aco_get_co (), * (( int * )( aco_get_arg ())));
int ct = 0 ;
while ( ct < 6 ){
foo ( ct );
ct ++ ;
}
printf ( "co: %p: exit to main_co: %dn" , aco_get_co (), * (( int * )( aco_get_arg ())));
aco_exit ();
}
int main () {
aco_thread_init ( NULL );
aco_t * main_co = aco_create ( NULL , NULL , 0 , NULL , NULL );
aco_share_stack_t * sstk = aco_share_stack_new ( 0 );
int co_ct_arg_point_to_me = 0 ;
aco_t * co = aco_create ( main_co , sstk , 0 , co_fp0 , & co_ct_arg_point_to_me );
int ct = 0 ;
while ( ct < 6 ){
assert ( co -> is_end == 0 );
printf ( "main_co: yield to co: %p: %dn" , co , ct );
aco_resume ( co );
assert ( co_ct_arg_point_to_me == ct );
ct ++ ;
}
printf ( "main_co: yield to co: %p: %dn" , co , ct );
aco_resume ( co );
assert ( co_ct_arg_point_to_me == ct );
assert ( co -> is_end );
printf ( "main_co: destroy and exitn" );
aco_destroy ( co );
co = NULL ;
aco_share_stack_destroy ( sstk );
sstk = NULL ;
aco_destroy ( main_co );
main_co = NULL ;
return 0 ;
} # default build
$ gcc -g -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
$ ./test_aco_synopsis
main_co: yield to co: 0x1887120: 0
co: 0x1887120: entry: 0
co: 0x1887120: yield to main_co: 0
main_co: yield to co: 0x1887120: 1
co: 0x1887120: yield to main_co: 1
main_co: yield to co: 0x1887120: 2
co: 0x1887120: yield to main_co: 2
main_co: yield to co: 0x1887120: 3
co: 0x1887120: yield to main_co: 3
main_co: yield to co: 0x1887120: 4
co: 0x1887120: yield to main_co: 4
main_co: yield to co: 0x1887120: 5
co: 0x1887120: yield to main_co: 5
main_co: yield to co: 0x1887120: 6
co: 0x1887120: exit to main_co: 6
main_co: destroy and exit
# i386
$ gcc -g -m32 -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
# share fpu and mxcsr env
$ gcc -g -D ACO_CONFIG_SHARE_FPU_MXCSR_ENV -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
# with valgrind friendly support
$ gcc -g -D ACO_USE_VALGRIND -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
$ valgrind --leak-check=full --tool=memcheck ./test_aco_synopsisДля получения дополнительной информации вы можете ссылаться на часть «сборка и тест».

Существует 4 основных элемента обычного состояния выполнения: {cpu_registers, code, heap, stack} .
Поскольку информация о коде обозначена ({E|R})?IP Register, а адрес памяти, выделенной из кучи, обычно хранится в стеке прямо или косвенно, мы могли бы упростить 4 элемента только на 2 из них: {cpu_registers, stack} .

Мы определяем main co как кораку, которая монополизирует стек по умолчанию текущего потока. А поскольку основной CO является единственным пользователем этого стека, нам нужно только сохранить/восстановить состояние необходимого CPU Registers of Main CO, когда он был получен из-за/возобновляемого (переключенный/переключенный).
Далее, определение CON non-main co -это COROUTINE, стек выполнения которого представляет собой стек, который не является стеком по умолчанию текущего потока и может быть передана с другим не-MAIN CO. Таким образом, не-мэйн CO должен иметь private save stack для сохранения/восстановления своего стека выполнения при его переключении/переключении (поскольку успешный/предшествующий Co может/имел/использовал/использовал стек Share в качестве стека выполнения).

Существует особый случай, не являющийся Main Co, то есть standalone non-main co как мы называли в Libaco: в стеке Share of the Main Coroutine есть только один пользователь CO. Таким образом, нет необходимости делать сохранение/восстановление материала своего частного стека сохранения, когда он был переключен/переключен, поскольку нет другой CO, не будет касаться стека выполнения автономного Non-Main CO, кроме самого себя.

Наконец, мы получаем общую картину Либако.
Существует часть «доказательство правильства», которую вы можете найти действительно полезным, если хотите погрузиться во внутренний либако или хотите реализовать свою собственную библиотеку Coroutine.
Также настоятельно рекомендуется прочитать исходный код учебных пособий и эталона следующим образом. Результат теста очень впечатляет и поучительна.
-m32 Опция -m32 GCC может помочь вам построить применение Libaco i386 на машине x86_64.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV Вы можете определить Global C Macro ACO_CONFIG_SHARE_FPU_MXCSR_ENV , чтобы немного ускорить производительность переключения контекста между CORUTINES, если ни один из ваших кодов не изменит управляющие слова FPU и MXCSR. Если макрос не определен, все CO сохранит собственную копию управляющих слов FPU и MXCSR. Рекомендуется всегда определять этот макрос по всему миру, поскольку очень редко, что одна функция должна установить свою собственную специальную env fpu или mxcsr вместо использования ENV по умолчанию, определяемой ISO C., но вам, возможно, не нужно определять этот макрос, если вы не уверены в нем.
ACO_USE_VALGRIND Если вы хотите использовать инструмент Memcheck of Valgrind для тестирования приложения, вам может потребоваться определить глобальный C Macro ACO_USE_VALGRIND чтобы обеспечить дружественную поддержку Valgrind в Libaco. Но не рекомендуется определять этот макрос в финальной сборке выпуска по причине производительности. Вам также может потребоваться установить заголовки Valgrind (например, название пакета «Valgrind-Devel» в CentOS) для создания приложения Libaco с определением C-macro ACO_USE_VALGRIND . (Memcheck of Valgrind работает только хорошо с автономным СО в настоящее время.
ACO_USE_ASAN Global C Macro ACO_USE_ASAN обеспечит дружественную поддержку дезинфицирующего средства в Libaco (поддержка GCC и Clang).
Чтобы построить тестовые люксы Libaco:
$ mkdir output
$ bash make.shЕсть также несколько подробных вариантов в 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 Короче говоря, используя -o no-valgrind если у вас нет установленных заголовков Valgrind, -o no-m32 если у вас нет 32-битных инструментов разработки GCC, установленных на хосте AMD64.
На MacOS вам необходимо заменить команды sed по умолчанию и grep MacOS на GNU sed и grep для запуска make.sh и test.sh (такое требование будет удалено в будущем):
$ brew install grep --with-default-names
$ brew install gnu-sed --with-default-names$ cd output
$ bash ../test.sh test_aco_tutorial_0.c в этом репозитории показывает основное использование Libaco. В этом уроке есть только один основной CO и один отдельный Non-Main Co. Комментарии в исходном коде также очень полезны.
test_aco_tutorial_1.c показывает использование некоторой статистики не-мэйн co. Структура данных aco_t очень ясна и определена в aco.h
Существует одна основная CO, один отдельный не-Main CO и два CON-MAIN CO (указывающий на один и тот же стек обмена) в test_aco_tutorial_2.c .
test_aco_tutorial_3.c показывает, как использовать Libaco в многопоточном процессе. По сути, один экземпляр Libaco предназначен только для работы внутри одного определенного потока, чтобы получить максимальную производительность переключения контекста между Coroutines. Если вы хотите использовать Libaco в многопоточной среде, просто для создания одного экземпляра Libaco в каждом из потоков. В рамках Libaco нет обмена данными по темам, и вам приходится иметь дело с конкуренцией данных между несколькими потоками сами (например, что gl_race_aco_yield_ct в этом руководстве).
Одно из правил в Libaco состоит в том, чтобы вызвать aco_exit() чтобы завершить выполнение неэ-Мейн СО вместо return прямого стиля по умолчанию, в противном случае Libaco рассматривает такое поведение как незаконное и запускает защитный защитник по умолчанию, чья задача заключается в том, чтобы войти в систему ошибки, о том, что о возмещении CO, чтобы STDERR и сразу же прервать процесс. test_aco_tutorial_4.c показывает такую ситуацию «оскорбительного СО».
Вы также можете определить своего собственного защитника, чтобы заменить по умолчанию (для того, чтобы сделать некоторые индивидуальные «последние слова»). Но независимо от того, в каком случае процесс будет прерван после выполнения защитника. test_aco_tutorial_5.c показывает, как определить настроенную функцию последнего слова.
Последним примером является простой планировщик Coroutine в test_aco_tutorial_6.c .
Было бы очень полезно прочитать соответствующую реализацию API в исходном коде одновременно, когда вы читаете следующее описание API LIBACO, поскольку исходный код довольно ясен и прост для понимания. И также рекомендуется прочитать все учебные пособия, прежде чем читать документ API.
Настоятельно рекомендуется прочитать лучшую практику, прежде чем начать писать реальное применение Libaco (в дополнение к описанию того, как по -настоящему выпустить экстремальную производительность Libaco в вашем приложении, также есть уведомление о программировании Libaco).
ПРИМЕЧАНИЕ. Управление версией Libaco следует за спецификацией: семантическая версия 2.0.0. Таким образом, API в следующем списке имеет гарантию совместимости. (Обратите внимание, что в списке нет такой гарантии API NO.)
typedef void ( * aco_cofuncp_t )( void );
void aco_thread_init ( aco_cofuncp_t last_word_co_fp );Инициализирует среду Libaco в текущем потоке.
Он будет хранить текущие управляющие слова FPU и MXCSR в глобальную переменную потока.
ACO_CONFIG_SHARE_FPU_MXCSR_ENV не определен, сохраненные управляющие слова будут использоваться в качестве эталонного значения для настройки управляющих слов FPU и MXCSR New CO (в aco_create ), и каждый CO будет поддерживать свою собственную копию FPU и MXCSR CONTROLSER SLICERENGING.ACO_CONFIG_SHARE_FPU_MXCSR_ENV определяется, то все CO делится одинаковыми управляющими словами FPU и MXCSR. Вы можете направить «сборку и тест» часть этого документа для получения дополнительной информации об этом. И, как сказано в test_aco_tutorial_5.c части «Учебные пособия», когда 1 -й аргумент last_word_co_fp не является нулевой, тогда функция, указанная на last_word_co_fp заменит защитника по умолчанию, чтобы сделать какой -то «последние слова» о совместном совершении, прежде чем процесс будет прерван. В такой последней функции слова вы можете использовать aco_get_co , чтобы получить указатель Offending CO. Для получения дополнительной информации вы можете прочитать test_aco_tutorial_5.c .
aco_share_stack_t * aco_share_stack_new ( size_t sz ); Равно от aco_share_stack_new2(sz, 1) .
aco_share_stack_t * aco_share_stack_new2 ( size_t sz , char guard_page_enabled ); Создает новый стек Share с консультативным размером памяти sz в байтах и может иметь страницу охраны (только для чтения) для обнаружения переполнения стека, которая зависит от 2-го аргумента guard_page_enabled .
Чтобы использовать значение по умолчанию по размеру (2 МБ), если 1 -й аргумент sz 0. После некоторого вычисления выравнивания и резерва эта функция обеспечит окончательную допустимую длину стека Share в ответ:
final_valid_sz >= 4096final_valid_sz >= szfinal_valid_sz % page_size == 0 if the guard_page_enabled != 0 И как можно ближе к значению sz .
Когда значение 2-го аргумента guard_page_enabled составляет 1, стек Share в ответ будет иметь одну страницу Guard Guard для обнаружения переполнения стека, в то время как значение 0 guard_page_enabled означает без такой страницы охраны.
Эта функция всегда будет возвращать действительный стек акций.
void aco_share_stack_destroy ( aco_share_stack_t * sstk ); Destory The Share Stack sstk .
Будьте уверены, что все CO, чей общий стек sstk уже разрушен, когда вы уничтожаете 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 );Создайте новый CO.
Если это main_co, который вы хотите создать, просто позвоните: aco_create(NULL, NULL, 0, NULL, NULL) . Main Co - это специальная автономная коратика, стек обмена которых является стеком потоков по умолчанию. В потоке Main Co-это коратика, которая должна быть создана и начала выполнять, прежде чем все остальные не-мэйн-коратика.
В противном случае это не-мэйн, которую вы хотите создать:
main_co - это основной CO CO CO aco_yield в будущем переключении контекста. main_co не должен быть нулевым;share_stack -это адрес стека Share, который не Main Co, который вы хотите создать, будет использовать в качестве выполнения стека в будущем. share_stack не должен быть нулевым;save_stack_sz определяет размер init частного сохранения стека этого CO. Устройство в байтах. Значение 0 означает использовать размер по умолчанию 64 байт. Поскольку автоматическое изменение размера произойдет, когда частное стек сохранения не достаточно велик, чтобы удерживать выполнение стека CO, когда он должен привести к стеку Share, который он занимает другой CO, вы обычно не должны беспокоиться о значении sz вообще. Но это принесет некоторое влияние на выделение памяти, когда огромная сумма (скажем, 10 000 000) из CO непрерывно изменяет размеры их частного стека сохранения, поэтому очень мудрый и настоятельно рекомендуется установить save_stack_sz со значением, равным максимальному значению co->save_stack.max_cpsz когда CO работает (вы можете обратиться к «лучшей практике», частью этого документа для более подробной информации);co_fp - это указатель функции входа в CO. co_fp не должен быть нулевым;arg является значением указателя и будет устанавливать для создания co->arg . Это может быть использовано в качестве входного аргумента для CO.Эта функция всегда будет возвращать действительный CO. И мы называем состояние CO в ответ как «init», если это не-мэйн, вы хотите создать.
void aco_resume ( aco_t * co ); Доход от Aller Main Co и запустить или продолжить выполнение co .
Вызывающий абонент этой функции должен быть основным CO и должен быть co->main_co . И 1-й аргумент co должен быть не-мэйн.
В первый раз, когда вы возобновите co , он начинает запускать функцию, указывающую на co->fp . Если co уже дана, aco_resume перезагружает его и продолжает выполнение.
После вызова aco_resume мы называем состояние вызывающего абонента - Main Co как «данный».
void aco_yield (); Получить выполнение co и резюме co->main_co . Вызывающим абонент этой функции должен быть не-мэйн. И co->main_co не должен быть нулевым.
После звонка aco_yield мы называем состояние вызывающего абонента - co как «данный».
aco_t * aco_get_co ();Вернуть указатель текущего не-мэйн co. Вызывающим абонент этой функции должен быть не-мэйн.
void * aco_get_arg (); Равно (aco_get_co()->arg) . А также, вызывающим абонент этой функции должен быть не-мэйн.
void aco_exit (); Кроме того, сделайте то же самое, что и aco_yield() , aco_exit() также установите co->is_end на 1, чтобы отметить co в статусе «End».
void aco_destroy ( aco_t * co ); Уничтожьте co Аргумент co не должен быть нулевым. Частный стек Save также был бы уничтожен, если co не является CO.
#define ACO_VERSION_MAJOR 1
#define ACO_VERSION_MINOR 2
#define ACO_VERSION_PATCH 4 Эти 3 макроса определены в заголовке aco.h , и их значение следует по спецификации: Семантические версии 2.0.0.
// provide the compiler with branch prediction information
#define likely ( x ) aco_likely(x)
#define unlikely ( x ) aco_unlikely(x)
// override the default `assert` for convenience when coding
#define assert ( EX ) aco_assert(EX)
// equal to `assert((ptr) != NULL)`
#define assertptr ( ptr ) aco_assertptr(ptr)
// assert the successful return of memory allocation
#define assertalloc_bool ( b ) aco_assertalloc_bool(b)
#define assertalloc_ptr ( ptr ) aco_assertalloc_ptr(ptr) Вы можете включить заголовок "aco_assert_override.h" чтобы переопределить по умолчанию C «ASSERT» в приложении LIBACO, как TEST_ACO_SYNOPSIS.C (этот заголовок, включающий, должен быть на последнем из списка директивных директив в исходном файле, потому что «Assert» также является C -макро -определением) и определяет 5 других MACRO в приведенном выше. Пожалуйста, не включайте этот заголовок в исходный файл приложения, если вы хотите использовать по умолчанию C "Assert".
Для получения более подробной информации вы можете обратиться к исходному файлу ACO_ASSERT_OVERRIDE.H.
Дата: SAT 30 июня UTC 2018.
Машина: C5D.Large на AWS.
ОС: RHEL-7.5 (Red Hat Enterprise Linux 7.5).
Вот краткое изложение эталонной части:
$ LD_PRELOAD=/usr/lib64/libtcmalloc_minimal.so.4 ./test_aco_benchmark..no_valgrind.shareFPUenv
+build:x86_64
+build:-DACO_CONFIG_SHARE_FPU_MXCSR_ENV
+build:share fpu & mxcsr control words between coroutines
+build:undefined ACO_USE_VALGRIND
+build:without valgrind memcheck friendly support
sizeof(aco_t)=152:
comment task_amount all_time_cost ns_per_op speed
aco_create/init_save_stk_sz=64B 1 0.000 s 230.00 ns/op 4347824.79 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.412 s 20.59 ns/op 48576413.55 op/s
-> acosw 40000000 0.412 s 10.29 ns/op 97152827.10 op/s
aco_destroy 1 0.000 s 650.00 ns/op 1538461.66 op/s
aco_create/init_save_stk_sz=64B 1 0.000 s 200.00 ns/op 5000001.72 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.412 s 20.61 ns/op 48525164.25 op/s
-> acosw 40000000 0.412 s 10.30 ns/op 97050328.50 op/s
aco_destroy 1 0.000 s 666.00 ns/op 1501501.49 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.50 ns/op 15266771.53 op/s
aco_resume/co_amount=2000000/copy_stack_size=8B 20000000 0.666 s 33.29 ns/op 30043022.64 op/s
aco_destroy 2000000 0.066 s 32.87 ns/op 30425152.25 op/s
aco_create/init_save_stk_sz=64B 2000000 0.130 s 65.22 ns/op 15332218.24 op/s
aco_resume/co_amount=2000000/copy_stack_size=24B 20000000 0.675 s 33.75 ns/op 29630018.73 op/s
aco_destroy 2000000 0.067 s 33.45 ns/op 29898311.36 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.42 ns/op 15286937.97 op/s
aco_resume/co_amount=2000000/copy_stack_size=40B 20000000 0.669 s 33.45 ns/op 29891277.59 op/s
aco_destroy 2000000 0.080 s 39.87 ns/op 25084242.29 op/s
aco_create/init_save_stk_sz=64B 2000000 0.224 s 111.86 ns/op 8940010.49 op/s
aco_resume/co_amount=2000000/copy_stack_size=56B 20000000 0.678 s 33.88 ns/op 29515473.53 op/s
aco_destroy 2000000 0.067 s 33.42 ns/op 29922412.68 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.74 ns/op 15211896.70 op/s
aco_resume/co_amount=2000000/copy_stack_size=120B 20000000 0.769 s 38.45 ns/op 26010724.94 op/s
aco_destroy 2000000 0.088 s 44.11 ns/op 22669240.25 op/s
aco_create/init_save_stk_sz=64B 10000000 1.240 s 123.97 ns/op 8066542.54 op/s
aco_resume/co_amount=10000000/copy_stack_size=8B 40000000 1.327 s 33.17 ns/op 30143409.55 op/s
aco_destroy 10000000 0.328 s 32.82 ns/op 30467658.05 op/s
aco_create/init_save_stk_sz=64B 10000000 0.659 s 65.94 ns/op 15165717.02 op/s
aco_resume/co_amount=10000000/copy_stack_size=24B 40000000 1.345 s 33.63 ns/op 29737708.53 op/s
aco_destroy 10000000 0.337 s 33.71 ns/op 29666697.09 op/s
aco_create/init_save_stk_sz=64B 10000000 0.654 s 65.38 ns/op 15296191.35 op/s
aco_resume/co_amount=10000000/copy_stack_size=40B 40000000 1.348 s 33.71 ns/op 29663992.77 op/s
aco_destroy 10000000 0.336 s 33.56 ns/op 29794574.96 op/s
aco_create/init_save_stk_sz=64B 10000000 0.653 s 65.29 ns/op 15316087.09 op/s
aco_resume/co_amount=10000000/copy_stack_size=56B 40000000 1.384 s 34.60 ns/op 28902221.24 op/s
aco_destroy 10000000 0.337 s 33.73 ns/op 29643682.93 op/s
aco_create/init_save_stk_sz=64B 10000000 0.652 s 65.19 ns/op 15340872.40 op/s
aco_resume/co_amount=10000000/copy_stack_size=120B 40000000 1.565 s 39.11 ns/op 25566255.73 op/s
aco_destroy 10000000 0.443 s 44.30 ns/op 22574242.55 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.61 ns/op 15241722.94 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.947 s 47.36 ns/op 21114212.05 op/s
aco_destroy 2000000 0.125 s 62.35 ns/op 16039466.45 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.71 ns/op 15218784.72 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.948 s 47.39 ns/op 21101216.29 op/s
aco_destroy 2000000 0.125 s 62.73 ns/op 15941559.26 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.49 ns/op 15270258.18 op/s
aco_resume/co_amount=2000000/copy_stack_size=152B 20000000 1.069 s 53.44 ns/op 18714275.17 op/s
aco_destroy 2000000 0.122 s 61.05 ns/op 16378678.85 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 65.91 ns/op 15171336.62 op/s
aco_resume/co_amount=2000000/copy_stack_size=232B 20000000 1.190 s 59.48 ns/op 16813230.99 op/s
aco_destroy 2000000 0.123 s 61.26 ns/op 16324298.25 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.68 ns/op 15224361.30 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.828 s 91.40 ns/op 10941133.56 op/s
aco_destroy 2000000 0.145 s 72.56 ns/op 13781182.82 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 65.80 ns/op 15197461.34 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.829 s 91.47 ns/op 10932139.32 op/s
aco_destroy 2000000 0.149 s 74.70 ns/op 13387258.82 op/s
aco_create/init_save_stk_sz=64B 1000000 0.067 s 66.63 ns/op 15007426.35 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.224 s 211.20 ns/op 4734744.76 op/s
aco_destroy 1000000 0.093 s 93.36 ns/op 10711651.49 op/s
aco_create/init_save_stk_sz=64B 1000000 0.066 s 66.28 ns/op 15086953.73 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.222 s 211.12 ns/op 4736537.93 op/s
aco_destroy 1000000 0.094 s 94.09 ns/op 10627664.78 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 70.72 ns/op 14139923.59 op/s
aco_resume/co_amount=100000/copy_stack_size=1000B 20000000 4.191 s 209.56 ns/op 4771909.70 op/s
aco_destroy 100000 0.010 s 101.21 ns/op 9880747.28 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 66.62 ns/op 15010433.00 op/s
aco_resume/co_amount=100000/copy_stack_size=2024B 20000000 7.002 s 350.11 ns/op 2856228.03 op/s
aco_destroy 100000 0.016 s 159.69 ns/op 6262129.35 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 65.76 ns/op 15205994.08 op/s
aco_resume/co_amount=100000/copy_stack_size=4072B 20000000 11.918 s 595.90 ns/op 1678127.54 op/s
aco_destroy 100000 0.019 s 186.32 ns/op 5367189.85 op/s
aco_create/init_save_stk_sz=64B 100000 0.006 s 63.03 ns/op 15865531.37 op/s
aco_resume/co_amount=100000/copy_stack_size=7992B 20000000 21.808 s 1090.42 ns/op 917079.11 op/s
aco_destroy 100000 0.038 s 378.33 ns/op 2643225.42 op/s
$ LD_PRELOAD=/usr/lib64/libtcmalloc_minimal.so.4 ./test_aco_benchmark..no_valgrind.standaloneFPUenv
+build:x86_64
+build:undefined ACO_CONFIG_SHARE_FPU_MXCSR_ENV
+build:each coroutine maintain each own fpu & mxcsr control words
+build:undefined ACO_USE_VALGRIND
+build:without valgrind memcheck friendly support
sizeof(aco_t)=160:
comment task_amount all_time_cost ns_per_op speed
aco_create/init_save_stk_sz=64B 1 0.000 s 273.00 ns/op 3663004.27 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.415 s 20.76 ns/op 48173877.75 op/s
-> acosw 40000000 0.415 s 10.38 ns/op 96347755.51 op/s
aco_destroy 1 0.000 s 381.00 ns/op 2624672.26 op/s
aco_create/init_save_stk_sz=64B 1 0.000 s 212.00 ns/op 4716980.43 op/s
aco_resume/co_amount=1/copy_stack_size=0B 20000000 0.415 s 20.75 ns/op 48185455.26 op/s
-> acosw 40000000 0.415 s 10.38 ns/op 96370910.51 op/s
aco_destroy 1 0.000 s 174.00 ns/op 5747123.38 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.63 ns/op 15237386.02 op/s
aco_resume/co_amount=2000000/copy_stack_size=8B 20000000 0.664 s 33.20 ns/op 30119155.82 op/s
aco_destroy 2000000 0.065 s 32.67 ns/op 30604542.55 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.33 ns/op 15305975.29 op/s
aco_resume/co_amount=2000000/copy_stack_size=24B 20000000 0.675 s 33.74 ns/op 29638360.61 op/s
aco_destroy 2000000 0.067 s 33.31 ns/op 30016633.42 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.61 ns/op 15241767.78 op/s
aco_resume/co_amount=2000000/copy_stack_size=40B 20000000 0.678 s 33.88 ns/op 29518648.08 op/s
aco_destroy 2000000 0.079 s 39.74 ns/op 25163018.30 op/s
aco_create/init_save_stk_sz=64B 2000000 0.221 s 110.73 ns/op 9030660.30 op/s
aco_resume/co_amount=2000000/copy_stack_size=56B 20000000 0.684 s 34.18 ns/op 29253416.65 op/s
aco_destroy 2000000 0.067 s 33.40 ns/op 29938840.64 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.60 ns/op 15244077.65 op/s
aco_resume/co_amount=2000000/copy_stack_size=120B 20000000 0.769 s 38.43 ns/op 26021228.41 op/s
aco_destroy 2000000 0.087 s 43.74 ns/op 22863987.42 op/s
aco_create/init_save_stk_sz=64B 10000000 1.251 s 125.08 ns/op 7994958.59 op/s
aco_resume/co_amount=10000000/copy_stack_size=8B 40000000 1.327 s 33.19 ns/op 30133654.80 op/s
aco_destroy 10000000 0.329 s 32.85 ns/op 30439787.32 op/s
aco_create/init_save_stk_sz=64B 10000000 0.674 s 67.37 ns/op 14843796.57 op/s
aco_resume/co_amount=10000000/copy_stack_size=24B 40000000 1.354 s 33.84 ns/op 29548523.05 op/s
aco_destroy 10000000 0.339 s 33.90 ns/op 29494634.83 op/s
aco_create/init_save_stk_sz=64B 10000000 0.672 s 67.19 ns/op 14882262.88 op/s
aco_resume/co_amount=10000000/copy_stack_size=40B 40000000 1.361 s 34.02 ns/op 29393520.19 op/s
aco_destroy 10000000 0.338 s 33.77 ns/op 29609577.59 op/s
aco_create/init_save_stk_sz=64B 10000000 0.673 s 67.31 ns/op 14857716.02 op/s
aco_resume/co_amount=10000000/copy_stack_size=56B 40000000 1.371 s 34.27 ns/op 29181897.80 op/s
aco_destroy 10000000 0.339 s 33.85 ns/op 29540633.63 op/s
aco_create/init_save_stk_sz=64B 10000000 0.672 s 67.24 ns/op 14873017.10 op/s
aco_resume/co_amount=10000000/copy_stack_size=120B 40000000 1.548 s 38.71 ns/op 25835542.17 op/s
aco_destroy 10000000 0.446 s 44.61 ns/op 22415961.64 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 66.01 ns/op 15148290.52 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.944 s 47.22 ns/op 21177946.19 op/s
aco_destroy 2000000 0.124 s 61.99 ns/op 16132721.97 op/s
aco_create/init_save_stk_sz=64B 2000000 0.133 s 66.36 ns/op 15068860.85 op/s
aco_resume/co_amount=2000000/copy_stack_size=136B 20000000 0.944 s 47.20 ns/op 21187541.38 op/s
aco_destroy 2000000 0.124 s 62.21 ns/op 16073322.25 op/s
aco_create/init_save_stk_sz=64B 2000000 0.131 s 65.62 ns/op 15238955.93 op/s
aco_resume/co_amount=2000000/copy_stack_size=152B 20000000 1.072 s 53.61 ns/op 18652789.74 op/s
aco_destroy 2000000 0.121 s 60.42 ns/op 16551368.04 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 66.08 ns/op 15132547.65 op/s
aco_resume/co_amount=2000000/copy_stack_size=232B 20000000 1.198 s 59.88 ns/op 16699389.91 op/s
aco_destroy 2000000 0.121 s 60.71 ns/op 16471465.52 op/s
aco_create/init_save_stk_sz=64B 2000000 0.133 s 66.50 ns/op 15036985.95 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.853 s 92.63 ns/op 10796126.04 op/s
aco_destroy 2000000 0.146 s 72.87 ns/op 13723559.36 op/s
aco_create/init_save_stk_sz=64B 2000000 0.132 s 66.14 ns/op 15118324.13 op/s
aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1.855 s 92.75 ns/op 10781572.22 op/s
aco_destroy 2000000 0.152 s 75.79 ns/op 13194130.51 op/s
aco_create/init_save_stk_sz=64B 1000000 0.067 s 66.97 ns/op 14931921.56 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.218 s 210.90 ns/op 4741536.66 op/s
aco_destroy 1000000 0.093 s 93.16 ns/op 10734691.98 op/s
aco_create/init_save_stk_sz=64B 1000000 0.066 s 66.49 ns/op 15039274.31 op/s
aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4.216 s 210.81 ns/op 4743543.53 op/s
aco_destroy 1000000 0.094 s 93.97 ns/op 10641539.58 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 70.95 ns/op 14094724.73 op/s
aco_resume/co_amount=100000/copy_stack_size=1000B 20000000 4.190 s 209.52 ns/op 4772746.50 op/s
aco_destroy 100000 0.010 s 100.99 ns/op 9902271.51 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 66.49 ns/op 15040038.84 op/s
aco_resume/co_amount=100000/copy_stack_size=2024B 20000000 7.028 s 351.38 ns/op 2845942.55 op/s
aco_destroy 100000 0.016 s 159.15 ns/op 6283444.42 op/s
aco_create/init_save_stk_sz=64B 100000 0.007 s 65.73 ns/op 15214482.36 op/s
aco_resume/co_amount=100000/copy_stack_size=4072B 20000000 11.879 s 593.95 ns/op 1683636.60 op/s
aco_destroy 100000 0.018 s 184.23 ns/op 5428119.00 op/s
aco_create/init_save_stk_sz=64B 100000 0.006 s 63.41 ns/op 15771072.16 op/s
aco_resume/co_amount=100000/copy_stack_size=7992B 20000000 21.808 s 1090.42 ns/op 917081.56 op/s
aco_destroy 100000 0.038 s 376.78 ns/op 2654073.13 op/s
Важно быть очень знакомым со стандартом Sys v Abi Intel386 и x86-64, прежде чем начать реализовать или доказать библиотеку Coroutine.
Доказательство ниже не имеет прямого описания IP (указатель инструкции), SP (указатель стека) и сохранение/восстановление между частями сохранения и стеком общего пользования, поскольку эти вещи довольно тривиальны и легко понять, когда они сравниваются с материалами ABI.
В потоке ОС основной Coroutine main_co -это коратика, который должен быть создан и начал выполнять сначала, прежде чем все остальные, не связанные с мэндией.
Следующая диаграмма является простым примером переключения контекста между main_co и co.
В этом доказательстве мы просто предполагаем, что мы находимся в рамках Sys v Abi Intel386, поскольку нет фундаментальных различий между Sys V Abi Intel386 и X86-64. Мы также предполагаем, что ни один из кода не изменит управляющие слова FPU и MXCSR.

Следующая диаграмма на самом деле представляет собой модель, работающую симметричной COROUTINE, которая имеет неограниченное количество неэ-MAIN CO-S и один основной CO. Это нормально, потому что асимметричная коратика является лишь особым случаем симметричной коратики. Доказать, что правильность симметричной коратики немного сложнее, чем асимметричной коратики, и, следовательно, более увлекательным. (Libaco внедрил только API асимметричной коратики в настоящее время, потому что семантическое значение асимметричного API -API гораздо проще для понимания и использования, чем симметричная коратика.)

Поскольку основной СО-1-я коратика, начинающая работать, 1-е переключение контекста в этом потоке ОС должно быть в форме acosw(main_co, co) , где 2-й аргумент co является неэ-Main Co.
Легко доказать, что существует только два вида переноса состояния на приведенной выше диаграмме:
Чтобы доказать правильность void* acosw(aco_t* from_co, aco_t* to_co) эквивалентна доказывающему, чтобы все Co постоянно соответствовало ограничениям Sys v Abi до и после вызова acosw . Мы предполагаем, что другая часть бинарного кода (кроме acosw ) в CO уже соответствовала ABI (они обычно генерируются компилятором).
Вот краткое изложение ограничений регистров в функции, вызывая соглашение 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`)
(Для Intel386 использование регистра определено в «P13 - Таблица 2.3: Использование регистра» Sys v ABI Intel386 V1.1, а для AMD64 находится в «P23 - Рисунок 3.4: Использование регистра» SYS V ABI AMD64 V1.0.
Доказательство:

Приведенная выше диаграмма предназначена для 1 -го случая: «Получил State Co -> init state co».
Ограничения: C 1.0, 1.1, 1,2, 1,5 ( удовлетворено ✓)
Регистры царапин ниже могут содержать любое значение при вводе функции:
EAX,ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Ограничения: C 1.3, 1.4 ( удовлетворен ✓)
Поскольку стек FPU уже должен быть пустой, а DF уже должен быть 0 до того, как acosw(co, to_co) (двоичный код CO уже выполняется ABI), ограничение 1.3 и 1.4 соблюдается acosw .
Ограничения: C 2.0, 2.1, 2.2 ( удовлетворено ✓)
C 2.0 и 2.1 уже удовлетворены. Поскольку мы уже предполагали, что никто не изменит контрольные слова FPU и MXCSR, C 2.2 также удовлетворен.

Приведенная выше диаграмма предназначена для 2 -го случая: сведено в состоянии CO -> с датчивым состоянием co.
Ограничения: C 1.0 ( удовлетворен ✓)
EAX уже сохраняет возвратное значение, когда acosw возвращается обратно в TO_CO (резюме).
Ограничения: C 1.1, 1.2, 1,5 ( удовлетворено ✓)
Регистры скретков ниже могут содержать любое значение при входе функции и после возврата acosw :
ECX,EDX
XMM*,YMM*,MM*,K*...
status bits of EFLAGS,FPU,MXCSR
Ограничения: C 1.3, 1.4 ( удовлетворен ✓)
Поскольку стек FPU уже должен быть пустой, а DF уже должен быть 0 до того, как acosw(co, to_co) (двоичный код CO уже выполняется ABI), ограничение 1.3 и 1.4 соблюдается acosw .
Ограничения: C 2.0, 2.1, 2.2 ( удовлетворено ✓)
C 2.0 и 2.1 удовлетворены, потому что существует сохранение и восстановление спасенных регистров Callee, когда acosw были вызваны/возвращены. Поскольку мы уже предполагали, что никто не изменит контрольные слова FPU и MXCSR, C 2.2 также удовлетворен.
1 -й acosw в потоке должен быть 1 -й случай: сходное состояние co -> init state co, и все следующие acosw должны быть одним из 2 случаев выше. Последовательно, мы могли бы доказать, что «все СО постоянно соблюдает ограничения Sys V Abi до и после вызова acosw ». Таким образом, доказательство закончено.
Есть новая вещь, называемая красной зоной в системе V ABI X86-64:
Считается, что площадь 128-байтов за пределами местоположения, указанная на %RSP, считается зарезервированной и не должна быть изменена с помощью обработчиков сигнала или прерываний. Следовательно, функции могут использовать эту область для временных данных, которые не требуются для вызовов функций. В частности, функции листьев могут использовать эту область для всей своей каркаса стека, а не настраивать указатель стека в прологе и эпилоге. Эта область известна как красная зона.
Поскольку красная зона «не сохранилась Callee», мы просто не заботимся об этом во время переключения контекста между корытами (потому что acosw является функцией листьев).
Конец области входной аргументы должен быть выровнен на 16 (32 или 64, если __M256 или __M512 передается на границе стека). Другими словами, значение (%ESP + 4) всегда кратно 16 (32 или 64), когда элемент управления передается в точку входа функции. Указатель стека, %ESP, всегда указывает на конец последней распределенной кадры стека.
-Intel386-Psabi-1.1: 2.2.2
Указатель стека, %rsp, всегда указывает на конец последнего распределенного кадра стека.
- Sys v Abi AMD64 Версия 1.0: 3.2.2
Вот пример ошибок в Tencent's Libco. ABI утверждает, что (E|R)SP всегда должен указывать на конец последней распределенной кадры стека. Но в файле coctx_swap.s of libco (E|R)SP использовался для обращения к памяти на куче.
По умолчанию обработчик сигнала вызывается в обычном стеке процессов. Можно организовать, что обработчик сигнала использует альтернативный стек; См. Sigalstack (2) для обсуждения того, как это сделать и когда это может быть полезно.
- Человек 7 Сигнал: расположение сигналов
Страшные вещи могут произойти, если (E|R)SP указывает на структуру данных на куче, когда появляется сигнал. (Использование breakpoint и signal GDB может создать такую ошибку удобно. Хотя с помощью sigalstack для изменения стека сигналов по умолчанию может смягчить проблему, но, тем не менее, такое использование (E|R)SP все еще нарушает Abi.)
Таким образом, если вы хотите получить ультра-производительность Libaco, просто сохраните использование стека не Standalone Non-Main CO в точке вызова aco_yield как можно меньше. И будьте очень осторожны, если вы хотите передать адрес локальной переменной от одного CO к другому CO, так как локальная переменная обычно находится в стеке Share . Распределение такого рода переменных из кучи всегда является разумным выбором.
Подробно, есть 5 советов:
co_fp
/
/
f1 f2
/ /
/ f4
yield f3 f5
aco_yield , чтобы дать обратно в Main CO), оказывает большое влияние на производительность переключения контекста между корешками, как уже указано в результатах эталона. На приведенной выше диаграмме использование стека функции F2, F3, F4 и F5 не оказывает непосредственного влияния на производительность переключения контекста, поскольку нет aco_yield , когда они выполняют, тогда как использование стека CO_FP и F1 доминирует в значении co->save_stack.max_cpsz и обладает большим влиянием на производительность переключения контекста. Ключом к сохранению использования стека функции максимально низким является выделение локальных переменных (особенно больших) на кучу и управлять своим жизненным циклом вручную вместо того, чтобы распределять их на стеке по умолчанию. Вариант -fstack-usage GCC очень полезен в этом.
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 в CO_FP1 (строка 16), имеет совершенно другую семантику с gl_ptr в строке 7 CO_FP0, и этот вид кода, вероятно, испортит стек выполнения CO_FP1. Но строка 11 хороша, потому что переменная ct и функция inc_p находится в одном и том же контексте коратики. Распределение такого рода переменных (необходимо поделиться с другими коратиками) на кучу просто решить такие проблемы: int * gl_ptr ;
void inc_p ( int * p ){ ( * p ) ++ ; }
void co_fp0 () {
int * ct_ptr = malloc ( sizeof ( int ));
assert ( ct_ptr != NULL );
* ct_ptr = 0 ;
gl_ptr = ct_ptr ;
aco_yield ();
check ( * ct_ptr );
int * ptr = ct_ptr ;
inc_p ( ptr );
free ( ct_ptr );
gl_ptr = NULL ;
aco_exit ();
}
void co_fp1 () {
do_sth ( gl_ptr );
aco_exit ();
}Новые идеи приветствуются!
Добавьте макрос, такой как aco_mem_new , который является комбинацией чего -то вроде p = malloc(sz); assertalloc_ptr(p) .
Добавьте новый API aco_reset , чтобы поддержать возможности повторного использования объектов COROUTINE.
Поддержите другие платформы (особенно Arm & Arm64).
v1.2.4 Sun Jul 29 2018
Changed `asm` to `__asm__` in aco.h to support compiler's `--std=c99`
flag (Issue #16, proposed by Theo Schlossnagle @postwait).
v1.2.3 Thu Jul 26 2018
Added support for MacOS;
Added support for shared library build of libaco (PR #10, proposed
by Theo Schlossnagle @postwait);
Added C macro ACO_REG_IDX_BP in aco.h (PR #15, proposed by
Theo Schlossnagle @postwait);
Added global C config macro ACO_USE_ASAN which could enable the
friendly support of address sanitizer (both gcc and clang) (PR #14,
proposed by Theo Schlossnagle @postwait);
Added README_zh.md.
v1.2.2 Mon Jul 9 2018
Added a new option `-o <no-m32|no-valgrind>` to make.sh;
Correction about the value of macro ACO_VERSION_PATCH (issue #1
kindly reported by Markus Elfring @elfring);
Adjusted some noncompliant naming of identifiers (double underscore
`__`) (issue #1, kindly proposed by Markus Elfring @elfring);
Supported the header file including by C++ (issue #4, kindly
proposed by Markus Elfring @elfring).
v1.2.1 Sat Jul 7 2018
Fixed some noncompliant include guards in two C header files (
issue #1 kindly reported by Markus Elfring @elfring);
Removed the "pure" word from "pure C" statement since it is
containing assembly codes (kindly reported by Peter Cawley
@corsix);
Many updates in the README.md document.
v1.2.0 Tue Jul 3 2018
Provided another header named `aco_assert_override.h` so user
could choose to override the default `assert` or not;
Added some macros about the version information.
v1.1 Mon Jul 2 2018
Removed the requirement on the GCC version (>= 5.0).
v1.0 Sun Jul 1 2018
The v1.0 release of libaco, cheers ???
Я полный рабочий день разработчика с открытым исходным кодом. Любое количество пожертвований будет высоко оценено и может принести мне большую поддержку.
PayPal
PayPal.me ссылка
Alipay (支付 (宝 | 寶))


Логотип Libaco щедро пожертвован Питером Бехом (Петиком). Логотип лицензирован в соответствии с CC By-ND 4.0. Веб -сайт libaco.org также любезно внесен Питер Бех (Петик).
Copyright (C) 2018, Sen Han [email protected].
По лицензии Apache, версия 2.0.
Смотрите файл лицензии для получения подробной информации.