1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-libaco

Клонировать/Скачать
README_zh.md 45 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 22:11 7882b31

Перевод текста с английского языка на русский:

Описание

На рисунке thread_model_0 изображено, что состояние выполнения пользовательского пространства (обычно это поток ОС) состоит из четырёх основных элементов: регистров процессора, кода, кучи и стека.

Поскольку информация о местоположении исполняемого кода двоичной программы определяется регистрами (E|R)?IP, а информация об адресе памяти, выделенном из кучи, обычно сохраняется непосредственно или косвенно в рабочем стеке, мы можем свести эти четыре элемента к двум: регистрам процессора и стеку.

Мы определяем main co (основной поток) как поток, который монопольно использует рабочий стек текущего запущенного потока по умолчанию. Поскольку main co является единственным пользователем этого рабочего стека, при переключении контекста связанного с main co потока нам нужно только сохранить и восстановить некоторые необходимые регистры main co.

Далее мы определяем non-main co (не основной поток) как поток, использующий рабочий стек, отличный от рабочего стека текущего запущенного потока (это может быть собственный рабочий стек или общий с другими non-main co). Поэтому у non-main co есть собственный приватный стек сохранения, который используется для восстановления или сохранения рабочего стека при переключении в этот поток или из него (в реализации libaco стратегия сохранения приватного стека является ленивым оптимальным решением; подробности см. в исходном коде aco_resume).

Это особый случай non-main co, который в libaco называется standalone non-main co (независимый не основной поток), то есть поток, монопольно использующий один рабочий стек. При переключении контекста, связанного со standalone non-main co потоком, достаточно сохранить или восстановить только некоторые необходимые регистры (поскольку его рабочий стек является эксклюзивным, состояние рабочего стека не изменяется, когда он переключается вне).

В итоге мы получаем глобальный обзор libaco.

Если вы хотите реализовать свою собственную библиотеку потоков или глубже понять реализацию libaco, раздел «Доказательство корректности» будет очень полезен.

Затем вы можете прочитать раздел «Учебники» или раздел о производительности. Отчёт о тестировании производительности производит глубокое впечатление и заставляет задуматься.

Сборка и тестирование

CFLAGS

  • -m32

Опция компилятора -m32 позволяет пользователям создавать 32-битные двоичные файлы libaco на платформе AMD64.

  • C macro: ACO_CONFIG_SHARE_FPU_MXCSR_ENV

Если программа пользователя не изменяет во время выполнения управляющие слова FPU и MXCSR, можно выбрать определение глобального макроса C ACO_CONFIG_SHARE_FPU_MXCSR_ENV, чтобы немного ускорить переключение контекста между потоками. Если этот макрос не определён, каждый поток будет поддерживать свою собственную независимую среду управления FPU и MXCSR. Поскольку изменение управляющих слов FPU или MXCSR встречается крайне редко, пользователи могут выбрать постоянное определение этого макроса, но если это невозможно гарантировать, пользователям следует избегать определения этого макроса.

  • C macro: ACO_USE_VALGRIND

Если пользователи хотят использовать инструмент memcheck valgrind для тестирования приложения libaco, необходимо определить глобальный макрос C ACO_USE_VALGRIND при сборке, чтобы включить поддержку libaco для valgrind memcheck. Из соображений производительности использование этого макроса не рекомендуется в окончательной производственной сборке двоичных файлов. Перед сборкой приложения libaco с глобально определённым этим макросом пользователям необходимо установить заголовочный файл valgrind (например, пакет разработки для Centos называется «valgrind-devel»). В настоящее время memcheck valgrind поддерживает только потоки с независимым рабочим стеком, и memcheck выдаёт много ложных срабатываний при проверке потоков с общим рабочим стеком. Дополнительную информацию можно найти в test_aco_tutorial_6.c.

Сборка

$ mkdir output
$ bash make.sh

Скрипт make.sh содержит более подробные параметры сборки:

$bash make.sh -h
Использование: make.sh [-o <no-m32|no-valgrind>] [-h]

Пример:
    # сборка по умолчанию
    bash make.sh
    # сборка без вывода i386 двоичного файла
    bash make.sh -o no-m32
    # сборка без поддержки valgrind двоичного вывода
    bash make.sh -o no-valgrind
    # сборка без поддержки valgrind и без вывода i386 двоичного файла
    bash make.sh -o no-valgrind -o no-m32

Короче говоря, если в системе нет заголовочного файла valgrind C, можно использовать параметр -o no-valgrind для сборки тестового набора; если система представляет собой платформу AMD64 и нет установленного 32-разрядного инструментария разработки C-компилятора, можно использовать опцию -o no-m32 для сборки тестового комплекта.

Тестирование

$ cd output
$ bash ../test.sh

Учебники

Файл test_aco_tutorial_0.c содержит базовый пример использования libaco. В этом примере есть только один main co и один standalone non-main co, а комментарии в коде также полезны.

Файл test_aco_tutorial_1.c содержит пример использования статистики выполнения потоков libaco. Определение типа aco_t находится в aco.h и легко понятно.

В файле test_aco_tutorial_2.c есть один standalone non-main co и два non-main co, которые совместно используют один и тот же рабочий стек.

Файл test_aco_tutorial_3.c показывает, как использовать libaco в многопоточной среде. По сути, для достижения наилучшей производительности переключения контекста между потоками, экземпляр libaco должен работать только в одном фиксированном потоке во время проектирования. Таким образом, если вы хотите использовать libaco в нескольких потоках, вам просто нужно использовать его так же, как в однопоточном режиме, в каждом потоке отдельно. Внутри libaco нет никакого обмена данными между потоками; в многопоточных сценариях пользователь должен сам обрабатывать проблемы конкуренции данных (как это делается в переменной gl_race_aco_yield_ct, совместно используемой потоками в этом экземпляре).

Чтобы завершить работу с non-main потоком в libaco, вызовите API aco_exit(). co的执行, а не использовать напрямую ключевое слово C return для возврата (иначе libaco будет рассматривать такое поведение как исключение и запускать процесс protector: выводить сообщение об ошибке в stderr и немедленно вызывать abort для завершения выполнения процесса). В исходном файле test_aco_tutorial_4.c показан пример сопрограммы, нарушающей это правило.

Кроме того, пользователь может настроить логику обработки protector по своему усмотрению (например, выполнить некоторые пользовательские «последние слова» или «завещания»). Однако после завершения работы protector процесс обязательно будет прерван (abort). Исходный файл test_aco_tutorial_5.c описывает, как настроить protector.

В исходном файле test_aco_tutorial_6.c приведён пример простого диспетчера сопрограмм.

API

Рекомендуется читать исходный код вместе с документацией по API, так как он очень понятный и легко читаемый. Кроме того, перед чтением документации по API рекомендуется сначала прочитать раздел Tutorials.

Также перед началом написания приложения на основе libaco настоятельно рекомендуется ознакомиться с разделом Best Practice, где помимо описания того, как использовать libaco для достижения максимальной производительности, также описаны некоторые аспекты программирования с использованием libaco.

Обратите внимание: управление версиями libaco соответствует стандарту Semantic Versioning 2.0.0. Поэтому все перечисленные ниже функции имеют стандартные гарантии совместимости (обратите внимание, что вызовы функций, не включённых в этот список, таких гарантий не имеют).

aco_thread_init

typedef void (*aco_cofuncp_t)(void);
void aco_thread_init(aco_cofuncp_t last_word_co_fp);

Инициализирует среду выполнения libaco в текущем рабочем потоке.

Этот API сохраняет текущие значения регистров FPU и MXCSR в глобальную переменную TLS.

  • Если глобальный макрос C ACO_CONFIG_SHARE_FPU_MXCSR_ENV не определён, сохранённые значения регистра будут использоваться для инициализации FPU и MXCSR нового сопроцесса (aco_create), и каждый сопроцесс будет поддерживать свою собственную конфигурацию FPU и MXCSR отдельно при последующих переключениях контекста сопроцесса.
  • Если глобальный макрос C ACO_CONFIG_SHARE_FPU_MXCSR_ENV определён, все сопроцессы будут совместно использовать одну и ту же конфигурацию FPU и MXCSR. Если вы хотите узнать больше об этом, обратитесь к разделу «Build and Test».

Как описано в разделе Tutorials для исходного файла test_aco_tutorial_5.c, первый параметр функции API last_word_co_fp является указателем на функцию пользователя «last words», которая заменяет обработчик protector по умолчанию (выполняет некоторые действия перед завершением процесса с помощью abort). В этой функции «last word» можно вызвать API aco_get_co для получения указателя на текущий сопроцесс. Для получения дополнительной информации о том, как это работает, обратитесь к исходному файлу test_aco_tutorial_5.c.

aco_share_stack_new

aco_share_stack_t* aco_share_stack_new(size_t sz);

Эквивалентно вызову aco_share_stack_new2(sz, 1).

aco_share_stack_new2

aco_share_stack_t* aco_share_stack_new2(size_t sz, char guard_page_enabled);

Создаёт новый стек выполнения, где параметр sz — рекомендуемое значение размера в байтах для создаваемого стека выполнения, а параметр guard_page_enabled определяет, будет ли созданный стек выполнения иметь защитную страницу только для чтения, которая может использоваться для обнаружения переполнения стека выполнения.

Если параметр sz равен 0, используется значение по умолчанию 2 МБ. После серии вычислений, связанных с выравниванием памяти и резервированием, эта функция гарантирует, что созданный стек выполнения будет соответствовать следующим условиям:

  • final_valid_sz >= 4096
  • final_valid_sz >= sz
  • final_valid_sz % page_size == 0 if the guard_page_enabled != 0

и максимально приближен к значению параметра sz.

Когда параметр guard_page_enabled равен 1, созданный стек выполнения имеет защитную страницу только для чтения для обнаружения переполнения стека выполнения; когда параметр равен 0, защитная страница не создаётся.

Эта функция всегда успешно возвращает доступный стек выполнения.

aco_share_stack_destroy

void aco_share_stack_destroy(aco_share_stack_t* sstk);

Уничтожает стек выполнения sstk.

Перед уничтожением стека выполнения sstk убедитесь, что все сопроцессы, использующие этот стек выполнения, были уничтожены.

aco_create

typedef void (*aco_cofuncp_t)(void);
aco_t* aco_create(aco_t* main_coaco_share_stack_t* share_stack, 
        size_t save_stack_sz, aco_cofuncp_t co_fp, void* arg);

Создаёт новый сопроцесс.

Чтобы создать основной сопроцесс, просто вызовите: aco_create (NULL, NULL, 0, NULL, NULL). Основной сопроцесс — это особый автономный сопроцесс (standalone coroutine), который использует стек выполнения текущего потока. В одном потоке основной сопроцесс запускается первым и выполняется до запуска всех других не основных сопроцессов.

Для создания не основного сопроцесса:

  • Первый параметр main_co указывает на основной сопроцесс в текущем потоке, и созданный не основной сопроцесс передаст управление основному сопроцессу при вызове API aco_yield. Параметр main co должен быть не NULL.
  • Второй параметр share_stack указывает на стек выполнения для созданного не основного сопроцесса. Параметр share_stack должен быть не NULL.
  • Третий параметр save_stack_sz указывает начальный размер частного стека сохранения для созданного не основного сопроцесса, измеряемый в байтах. Значение 0 означает использование значения по умолчанию начального размера 64 байта. Поскольку размер стека сохранения не основного сопроцесса будет автоматически изменяться во время выполнения, обычно нет необходимости беспокоиться о его значении. Однако если большое количество сопроцессов (например, миллион) последовательно выполняют изменение размера, это может привести к снижению производительности для распределителя памяти, поэтому более разумным выбором будет присвоение параметру save_stack_sz максимального значения, необходимого для стека сохранения во время выполнения (т. е. значение co->save_stack.max_cpsz), см. раздел «Best Practice» для получения дополнительных сведений об оптимизации.
  • Четвёртый параметр co_fp — указатель на функцию входа для созданного не основного сопроцесса. Параметр co_fp должен быть не NULL.
  • Пятый параметр arg — указатель, который будет установлен как значение аргумента для созданного не основного сопроцесса co->arg, которое обычно используется в качестве входных параметров сопроцесса.

Эта функция всегда будет успешно возвращать доступный сопроцесс. Также мы определяем, что сопроцесс, возвращённый функцией aco_create, находится в состоянии «init».

aco_resume

void aco_resume(aco_t* co);

Передаёт управление от вызывающей стороны и начинает или продолжает выполнение сопроцесса co.

Вызывающая сторона должна быть основным сопроцессом и должна быть co->main_co, а параметр co должен быть не основным сопроцессом.

При первом возобновлении сопроцесса co функция, на которую указывает co->fp, начнёт выполняться. Если сопроцесс co уже передал управление, aco_resume продолжит выполнение сопроцесса.

После вызова API aco_resume мы определяем состояние вызывающего основного сопроцесса как «yielded».

aco_yield

void aco_yield();

Передаёт управление от вызывающего не основного сопроцесса и возобновляет выполнение основного сопроцесса co->main_co.

Параметр co вызывающего должен быть не основным сопроцессом, а co->main_co должен быть не NULL.

После вызова API aco_yield мы определяем состояние сопроцесса как «yielded».

aco_get_co

aco_t* aco_get_co();

Возвращает указатель на текущий не основной сопроцесс. Вызывающий должен быть не основным сопроцессом.

aco_get_arg

void* aco_get_arg();

Равнозначно (aco_get_co()->arg). Вызывающий также должен быть не основным сопроцессом.

aco_exit

void aco_exit();

Завершает выполнение сопроцесса без передачи управления другому сопроцессу. aco_resume/co_amount=2000000/copy_stack_size=40B

20 000 000 — 0,669 с — 33,45 нс/оп — 29 891 277,59 оп/с.

aco_destroy

2 000 000 — 0,080 с — 39,87 нс/оп — 25 084 242,29 оп/с.

aco_create/init_save_stk_sz=64B

2 000 000 — 0,224 с — 111,86 нс/оп — 8 940 010,49 оп/с.

aco_resume/co_amount=2000000/copy_stack_size=56B

20 000 000 — 0,678 с — 33,88 нс/оп — 29 515 473,53 оп/с.

aco_destroy

2 000 000 — 0,067 с — 33,42 нс/оп — 29 922 412,68 оп/с.

И так далее.

Примечание: в запросе не хватает данных для перевода. aco_create/init_save_stk_sz=64B — 20 000 000: 1,828 с, 91,40 нс/оп, 10 941 133,56 оп/с.

aco_destroy — 2 000 000: 0,145 с, 72,56 нс/оп, 13 781 182,82 оп/с.

aco_create/init_save_stk_sz=64B — 20 000 000: 1,829 с, 91,47 нс/оп, 10 932 139,32 оп/с.

aco_resume/co_amount=2000000/copy_stack_size=488B — 20 000 000: 1,829 с, 91,47 нс/оп, 10 932 139,32 оп/с.

aco_destroy — 2 000 000: 0,149 с, 74,70 нс/оп, 13 387 258,82 оп/с.

aco_create/init_save_stk_sz=64B — 10 000 000: 0,067 с, 66,63 нс/оп, 15 007 426,35 оп/с.

aco_resume/co_amount=1000000/copy_stack_size=1000B — 20 000 000: 4,224 с, 211,20 нс/оп, 4 734 744,76 оп/с.

aco_destroy — 1 000 000: 0,093 с, 93,36 нс/оп, 10 711 651,49 оп/с.

aco_create/init_save_stk_sz=64B — 1 000 000: 0,066 с, 66,28 нс/оп, 15 086 953,73 оп/с.

aco_resume/co_amount=1000000/copy_stack_size=1000B — 20 000 000: 4,222 с, 211,12 нс/оп, 4 736 537,93 оп/с.

aco_destroy — 1 000 000: 0,094 с, 94,09 нс/оп, 10 627 664,78 оп/с.

aco_create/init_save_stk_sz=64B — 100 000: 0,007 с, 70,72 нс/оп, 14 139 923,59 оп/с.

aco_resume/co_amount=100000/copy_stack_size=1000B — 20 000 000: 4,191 с, 209,56 нс/оп, 4 771 909,70 оп/с.

aco_destroy — 100 000: 0,010 с, 101,21 нс/оп, 9 880 747,28 оп/с.

aco_create/init_save_stk_sz=64B — 100 000: 0,007 с, 66,62 нс/оп, 15 010 433,00 оп/с.

aco_resume/co_amount=100000/copy_stack_size=2024B — 20 000 000: 7,002 с, 350,11 нс/оп, 2 856 228,03 оп/с.

aco_destroy — 100 000: 0,016 с, 159,69 нс/оп, 6 262 129,35 оп/с.

aco_create/init_save_stk_sz=64B — 100 000: 0,007 с, 65,76 нс/оп, 15 205 994,08 оп/с.

aco_resume/co_amount=100000/copy_stack_size=4072B — 20 000 000: 11,918 с, 595,90 нс/оп, 1 678 127,54 оп/с.

aco_destroy — 100 000: 0,019 с, 186,32 нс/оп, 5 367 189,85 оп/с. 2000000 0.121 с 60,42 нс/оп 16551368,04 оп/с aco_create/init_save_stk_sz=64B 2000000 0.132 с 66,08 нс/оп 15132547,65 оп/с aco_resume/co_amount=2000000/copy_stack_size=232B 20000000 1,198 с 59,88 нс/оп 16699389,91 оп/с aco_destroy 2000000 0,121 с 60,71 нс/оп 16471465,52 оп/с

aco_create/init_save_stk_sz=64B 2000000 0,133 с 66,50 нс/оп 15036985,95 оп/с aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1,853 с 92,63 нс/оп 10796126,04 оп/с aco_destroy 2000000 0,146 с 72,87 нс/оп 13723559,36 оп/с

aco_create/init_save_stk_sz=64B 2000000 0,132 с 66,14 нс/оп 15118324,13 оп/с aco_resume/co_amount=2000000/copy_stack_size=488B 20000000 1,855 с 92,75 нс/оп 10781572,22 оп/с aco_destroy 2000000 0,152 с 75,79 нс/оп 13194130,51 оп/с

aco_create/init_save_stk_sz=64B 1000000 0,067 с 66,97 нс/оп 14931921,56 оп/с aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4,218 с 210,90 нс/оп 4741536,66 оп/с aco_destroy 1000000 0,093 с 93,16 нс/оп 10734691,98 оп/с

aco_create/init_save_stk_sz=64B 1000000 0,066 с 66,49 нс/оп 15039274,31 оп/с aco_resume/co_amount=1000000/copy_stack_size=1000B 20000000 4,216 с 210,81 нс/оп 4743543,53 оп/с aco_destroy 1000000 0,094 с 93,97 нс/оп 10641539,58 оп/с

aco_create/init_save_stk_sz=64B 100000 0,007 с 70,95 нс/оп 14094724,73 оп/с aco_resume/co_amount=100000/copy_stack_size=1000B 20000000 4,190 с 209,52 нс/оп 4772746,50 оп/с aco_destroy 100000 0,010 с 100,99 нс/оп 9902271,51 оп/с

aco_create/init_save_stk_sz=64B 100000 0,007 с 66,49 нс/оп 15040038,84 оп/с aco_resume/co_amount=100000/copy_stack_size=2024B 20000000 7,028 с 351,38 нс/оп 2845942,55 оп/с aco_destroy 100000 0,016 с 159,15 нс/оп 6283444,42 оп/с

aco_create/init_save_stk_sz=64B 100000 0,007 с 65,73 нс/оп 15214482,36 оп/с aco_resume/co_amount=100000/copy_stack_size=4072B 20000000 11,879 с 593,95 нс/оп 1683636,60 оп/с aco_destroy 100000 0,018 с 184,23 нс/оп 5428119,00 оп/с

aco_create/init_save_stk_sz=64B 100000 0,006 с 63,41 нс/оп 15771072,16 оп/с aco_resume/co_amount=100000/copy_stack_size=7992B 20000000 21,808 с 1090,42 нс/оп 917081,56 оп/с aco_destroy 100000 0,038 с 376,78 нс/оп 2654073,13 оп/с Доказательство:

  1. Состояние co -> начальное состояние co:

На рисунке подробно описан процесс перехода из одного состояния в другое: «состояние co -> начальное состояние co».

Ограничения: C 1.0, 1.1, 1.2, 1.5 (выполняются ✓).

Перечисленные ниже регистры Scratch могут иметь произвольные значения в точке входа функции:

  • EAX, ECX, EDX;
  • XMM*, YMM*, MM*, K*.

Ограничение: C 1.3, 1.4 (выполняется ✓).

Поскольку перед вызовом acosw стек FPU пуст и DF равен 0 (поскольку двоичный код корутины co уже соответствует ABI), то acosw удовлетворяет ограничениям C1.3 и C1.4.

Ограничение: C 2.0, 2.1, 2.2 (выполняется ✓).

Ограничения C2.0 и C2.1 уже выполнены. Поскольку мы предположили, что управляющие слова FPU и MXCSR не будут изменены намеренно во время выполнения программы, ограничение C2.2 также выполняется для acosw.

  1. Состояние co -> состояние co:

Рисунок подробно описывает процесс перехода из одного состояния в другое: «Состояние co -> состояние co».

Ограничение: C 1.0 (выполняется ✓).

Очевидно, что когда acosw возвращается в to_co, ожидаемое возвращаемое значение уже сохранено в EAX.

Ограничение: C 1.1, 1.2, 1.5 (выполняется ✓).

Указанные ниже регистры Scratch могут принимать произвольные значения как в точке входа в функцию, так и после возврата из acosw:

  • ECX, EDX;
  • XMM*, YMM*, MM*, K*;
  • флаги состояния EFLAGS, FPU, MXCSR.

Ограничение: C 1.3, 1.4 (выполняется ✓).

Перед вызовом acosw стек FPU был пуст, а DF равнялся 0 (так как двоичный код корутины co уже соответствовал ABI), поэтому acosw выполняет ограничения C1.3 и C1.4.

Ограничение: C 2.0, 2.1, 2.2 (выполняется ✓).

С точки зрения вызывающего acosw, поскольку все регистры callee saved были соответствующим образом сохранены (или восстановлены) при вызове (или возврате) acosw, ограничения C2.0 и C2.1 выполняются для acosw. Поскольку мы предположили, что контрольные слова FPU и MXCSR не изменятся намеренно во время работы программы, ограничение C2.2 также выполнено для acosw.

  1. Математическая индукция:

Очевидно, что в текущем потоке ОС первый вызов acosw обязательно относится к первому классу переходов состояний: состояние co -> начальное состояние co, и все последующие вызовы acosw обязательно относятся к одному из этих двух классов переходов состояний. Последовательно применяя полученные выше выводы по очереди, можно доказать, что «все корутины всегда соответствуют ограничениям Sys V ABI при вызовах acosw до и после вызова». Таким образом, доказательство завершено.

Разное

Red Zone

В System V ABI x86-64 описана красная зона... Концепция зоны red zone:

Зона red zone — это область размером 128 байт, которая находится за пределами адреса, на который указывает %rsp. Эта область считается зарезервированной и не должна изменяться обработчиками сигналов или прерываний. Поэтому функции могут использовать эту область для временных данных, которые не нужны при вызовах функций. В частности, листовые функции (leaf functions) могут использовать эту зону для всего своего стекового фрейма, вместо того чтобы изменять указатель стека в прологе и эпилоге. Эта зона называется зоной red zone.

Поскольку зона red zone «не сохраняется вызываемой функцией», нам не нужно учитывать её при реализации контекста переключения сопрограмм (потому что acosw является листовой функцией).

Указатель стека

  • Конец области входных аргументов должен быть выровнен по границе 16 (32 или 64, если передаётся __m256 или __m512) байт. Другими словами, значение (%esp + 4) всегда кратно 16 (32 или 64), когда управление передаётся точке входа функции. Указатель стека, %esp, всегда указывает на конец последнего выделенного стекового фрейма.
  • Указатель стека %rsp всегда указывает на конец последнего выделенного стекового фрейма.

Это ошибка в Tencent libco. В ABI-спецификациях указано, что указатель пользовательского пространства программы на стек должен всегда указывать на вершину стека. Однако в coctx_swap.S используется указатель стека для непосредственного обращения к структуре данных в куче, что нарушает ABI.

По умолчанию обработчик сигнала вызывается в обычном процессе стека. Можно настроить так, чтобы обработчик сигнала использовал альтернативный стек; см. sigalstack(2) для обсуждения того, как это сделать и когда это может быть полезно.

Когда coctx_swap использует указатель стека для прямого обращения к структуре данных в куче, и в этот момент поток получает сигнал, ядро захватывает этот поток и начинает подготовку к выполнению обработчика сигнала в пользовательском пространстве потока, то, поскольку по умолчанию ядро будет выбирать основной стек в качестве стека выполнения обработчика сигналов, но в этот момент стек уже был направлен на кучу (пользовательское пространство программы нарушает ABI), тогда стек выполнения обработчика сигналов будет ошибочно размещён в куче. Таким образом, структура данных в куче после этого, вероятно, будет повреждена (более подробное воспроизведение ошибки см. в этом выпуске).

Best Practice

В целом, если вы хотите максимально использовать производительность libaco, убедитесь, что выполнение стека при вызове aco_yield в non-standalone non-main co минимально. Кроме того, будьте осторожны при передаче адреса локальной переменной одного сопроцесса другому, так как это может привести к путанице в памяти, если переменная находится в общем стеке. Поэтому всегда лучше выделять память, которую необходимо совместно использовать между сопроцессами, из кучи.

Вот пять рекомендаций:

  1. Использование стека основного сопроцесса (main co) не влияет напрямую на переключение контекста между сопроцессами (потому что main co занимает отдельный стек по умолчанию).
  2. Использование стека автономного сопроцесса (standalone non-main co) также не влияет напрямую на переключение контекста между сопроцессами (так как он занимает отдельный стек). Однако создание большого количества standalone non-main co может потреблять много виртуальной памяти (из-за создания большого количества стеков), поэтому не рекомендуется создавать большое количество standalone non-main co в одном потоке.
  3. Использование стека неавтономного сопроцесса, который не является основным (non-standalone non-main co), влияет на производительность переключения контекста между сопроцессами при вызове функции aco_yield. Это было ясно продемонстрировано в части тестирования производительности. В этой схеме функции f2, f3, f4 и f5 не влияют на переключение контекста, потому что они не были прерваны функцией aco_yield. Однако сумма использования стека функциями co_fp и f1 определяет значение co->save_stack.max_cpsz (максимальный размер сохранения частного стека во время выполнения сопрограммы), которое напрямую влияет на производительность переключения контекста.

Ключом к тому, чтобы функция использовала как можно меньше стека, является выделение локальных переменных (особенно тех, которые занимают много памяти) из кучи и ручное управление их жизненным циклом (malloc/free), а не автоматическое выделение и освобождение их из стека кучи с помощью компилятора C gcc. Опция -fstack-usage компилятора gcc полезна для этого.

  1. В приведённом выше фрагменте кода мы предполагаем, что сопроцессы co_fp0 и co_fp1 разделяют один и тот же стек выполнения, оба являются не основными сопроцессами. Их порядок выполнения — «co_fp0 → co_fp1 → co_fp0». Поскольку они используют один и тот же стек, значение указателя в gl_ptr на строке 16 отличается от значения указателя на строке 7. Это может повредить стек выполнения сопроцесса co_fp1. С другой стороны, строка 11 верна, потому что локальная переменная ct и функция inc_p выполняются в контексте одного и того же сопроцесса. Проблема такого рода может быть легко решена путём выделения памяти, необходимой для совместного использования между сопроцессами, из кучи:

  2. #TODO

Новые идеи приветствуются!

  • Добавить макрос aco_new, который представляет собой комбинацию чего-то вроде p = malloc(sz); assertalloc_ptr(p).
  • Добавить новый API aco_reset для поддержки повторного использования объектов сопрограмм.
  • Поддержка... Изменения
v1.2.2 Mon Jul 9 2018
    Добавлен новый параметр `-o <no-m32|no-valgrind>` в make.sh;
    Исправление значения макроса ACO_VERSION_PATCH (проблема №1, любезно сообщена Маркусом Эльфрингом @elfring);
    Скорректировано некоторое несоответствующее именование идентификаторов (двойное подчёркивание `__`) (проблема №1, предложено Маркусом Эльфрингом @elfring);
    Поддерживается включение заголовочного файла на C++ (проблема №4, предложено Маркусом Эльфрингом @elfring).
v1.2.1 Sat Jul 7 2018
    Исправлены некоторые несоответствующие защитные элементы включения в двух заголовочных файлах C (проблема №1, сообщена Маркусом Эльфрингом @elfring);
    Удалено слово «pure» из утверждения «pure C», поскольку оно содержит коды сборки (любезно сообщено Питером Коули @corsix);
    Много обновлений в документе README.md.
v1.2.0 Tue Jul 3 2018
    Предоставлен ещё один заголовок с именем `aco_assert_override.h`, чтобы пользователь мог выбрать переопределение стандартного `assert` или нет;
    Добавлены макросы о информации о версии.
v1.1   Mon Jul 2 2018
    Убрано требование к версии GCC (>= 5.0).
v1.0   Sun Jul 1 2018
    Выпуск v1.0 библиотеки libaco, ура 🎉🎉🎉

Пожертвования

Я независимый разработчик свободных проектов на полную ставку, и любое пожертвование будет для меня большой поддержкой ;-)

Авторские права и лицензия

Авторское право (C) 2018, Сен Хан 00hnes@gmail.com.

Под лицензией Apache, версия 2.0.

Подробности см. в файле LICENSE.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-libaco.git
git@api.gitlife.ru:oschina-mirror/mirrors-libaco.git
oschina-mirror
mirrors-libaco
mirrors-libaco
master