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

OSCHINA-MIRROR/mirrors-libaco

Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

yield to main_co: %d\n", aco_get_co(), ((int)(aco_get_arg())));

aco_yield(); ((int)(aco_get_arg())) = ct + 1; }

void co_fp0() { printf("co: %p: entry: %d\n", aco_get_co(), ((int)(aco_get_arg()))); int ct = 0; while(ct < 6){ foo(ct); ct++; } printf("co: %p: exit to main_co: %d\n", 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: %d\n", co, ct);
    aco_resume(co);
    assert(co_ct_arg_point_to_me == ct);
    ct++;
}
printf("main_co: yield to co: %p: %d\n", co, ct);
aco_resume(co);
assert(co_ct_arg_point_to_me == ct);
assert(co->is_end);

printf("main_co: destroy and exit\n");
aco_destroy(co);
co = NULL;
aco_share_stack_destroy(sstk);
sstk = NULL;
aco_destroy(main_co);
main_co = NULL;

return 0;

}


**В запросе представлен фрагмент кода на языке C, который реализует работу с сопрограммами (coroutines) в рамках библиотеки libaco.**

В коде создаются две сопрограммы: main_co и co. Сопрограмма main_co является основной и создаёт новую сопрограмму co с помощью функции aco_create(). Затем main_co выполняет цикл из шести итераций, на каждой из которых она приостанавливает своё выполнение с помощью aco_yield() и передаёт управление сопрограмме co. После этого main_co возобновляет выполнение и проверяет состояние сопрограммы co.

Сопрограмма co также выполняет цикл из шести итераций. На каждой итерации она увеличивает значение переменной ct на единицу. По завершении цикла сопрограмма co завершает своё выполнение с помощью функции aco_exit().

После завершения работы с сопрограммой co основная сопрограмма main_co также завершает свою работу. Она уничтожает созданные объекты и освобождает ресурсы. libaco в каждом из потоков. В libaco нет совместного использования данных между потоками, и вам придётся самостоятельно справляться с конкуренцией за данные между несколькими потоками (как это делает `gl_race_aco_yield_ct` в этом руководстве).

Одно из правил в libaco — вызывать `aco_exit()` для завершения выполнения неосновного co вместо стандартного прямого возврата в стиле C, иначе libaco будет рассматривать такое поведение как незаконное и запускать стандартный защитник, задача которого — регистрировать информацию об ошибке о нарушающем co в stderr и немедленно завершать процесс. Ситуация «нарушающего co» показана в `test_aco_tutorial_4.c`.

Вы также можете определить свой собственный защитник для замены стандартного (чтобы сделать некоторые настроенные «последние слова»). Но независимо от ситуации процесс будет прерван после выполнения защитника. Как определить функцию последнего слова показано в `test_aco_tutorial_5.c`.

Последний пример — простой планировщик сопрограмм в `test_aco_tutorial_6.c`.

# API

Будет очень полезно читать соответствующую реализацию API в исходном коде одновременно с чтением следующего описания API libaco, поскольку исходный код довольно понятен и лёгок для понимания. Также рекомендуется прочитать все [учебники](#tutorials) перед чтением документа API.

Перед началом написания реального приложения на основе libaco настоятельно рекомендуется прочитать раздел [Best Practice](#best-practice) (в дополнение к описанию того, как действительно раскрыть экстремальную производительность libaco в вашем приложении, есть также замечание о программировании libaco).

Примечание: контроль версий libaco соответствует спецификации: [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). Поэтому API в следующем списке имеют гарантию совместимости. (Обратите внимание, что такой гарантии для API нет в списке.)

## aco_thread_init
```c
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 нового co (в aco_create), и каждый co будет поддерживать свою собственную копию контрольных слов FPU и MXCSR во время последующего переключения контекста.
  • Если глобальный макрос ACO_CONFIG_SHARE_FPU_MXCSR_ENV определён, то все co используют одни и те же контрольные слова FPU и MXCSR. Дополнительную информацию об этом можно найти в разделе «Сборка и тестирование» этого документа.

Как сказано в test_aco_tutorial_5.c раздела «Учебники», когда 1-й аргумент last_word_co_fp не равен NULL, функция, на которую указывает last_world_co_fp, заменит стандартный защитник, чтобы выполнить некоторые «последние действия» относительно нарушающего co перед прерыванием процесса. В такой функции последнего слова вы можете использовать aco_get_co, чтобы получить указатель на нарушающий 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.

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

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

И как можно ближе к значению sz. Эти 3 макроса определены в заголовке aco.h, и их значение соответствует спецификации: Semantic Versioning 2.0.0 (https://semver.org/spec/v2.0.0.html).

aco_assert_override.h

// 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](http://man7.org/linux/man-pages/man3/assert.3.html) в приложении libaco, как это делает test_aco_synopsis.c (этот заголовок следует включать последним в списке директив include в исходном файле, поскольку C-макрос [assert] также является определением макроса C), и определить остальные 5 макросов, указанных выше. Не включайте этот заголовок в исходный файл приложения, если вы хотите использовать стандартный C-макрос [assert].

Для получения более подробной информации обратитесь к исходному файлу aco_assert_override.h.

Benchmark

Дата: Sat Jun 30 UTC 2018. Машина: c5d.large на AWS. ОС: RHEL-7.5 (Red Hat Enterprise Linux 7.5).

Вот краткое изложение результатов тестирования:

  • Время переключения контекста между сопрограммами составляет всего около 10,29 нс (в случае отдельного стека, где слова управления x87 и mxcsr совместно используются сопрограммами);
  • Время переключения контекста между сопрограммами составляет всего около 10,38 нс (в случае отдельного стека, когда каждая сопрограмма поддерживает свои собственные слова управления x87 и mxcsr);
  • Это чрезвычайно эффективно с точки зрения использования памяти: для одновременного запуска 10 000 000 сопрограмм требуется всего 2,8 ГБ физической памяти (с tcmalloc, где каждая сопрограмма имеет размер конфигурации копирующего стека 120 байт). op/s aco_destroy 1 0.000 с 174.00 нс/оп 5747123.38 оп/с

aco_create/init_save_stk_sz=64B 2 000 000 0.131 с 65.63 нс/оп 15237386.02 оп/с aco_resume/co_amount=2 000 000/copy_stack_size=8B 20 000 000 0.664 с 33.20 нс/оп 30119155.82 оп/с aco_destroy 2 000 000 0.065 с 32.67 нс/оп 30604542.55 оп/с

aco_create/init_save_stk_sz=64B 2 000 000 0.131 с 65.33 нс/оп 15305975.29 оп/с aco_resume/co_amount=2 000 000/copy_stack_size=24B 20 000 000 0.675 с 33.74 нс/оп 29638360.61 оп/с aco_destroy 2 000 000 0.067 с 33.31 нс/оп 30016633.42 оп/с

aco_create/init_save_stk_sz=64B 2 000 000 0.131 с 65.61 нс/оп 15241767.78 оп/с aco_resume/co_amount=2 000 000/copy_stack_size=40B 20 000 000 0.678 с 33.88 нс/оп 29518648.08 оп/с aco_destroy 2 000 000 0.079 с 39.74 нс/оп 25163018.30 оп/с

aco_create/init_save_stk_sz=64B 2 000 000 0.221 с 110.73 нс/оп 9030660.30 оп/с aco_resume/co_amount=2 000 000/copy_stack_size=56B 20 000 000 0.684 с 34.18 нс/оп 29253416.65 оп/с aco_destroy 2 000 000 0.067 с 33.40 нс/оп 29938840.64 оп/с

aco_create/init_save_stk_sz=64B 2 000 000 0.131 с 65.60 нс/оп 15244077.65 оп/с aco_resume/co_amount=2 000 000/copy_stack_size=120B 20 000 000 0.769 с 38.43 нс/оп 26021228.41 оп/с aco_destroy 2 000 000 0.087 с 43.74 нс/оп 22863987.42 оп/с

aco_create/init_save_stk_sz=64B 10 000 000 1.251 с 125.08 нс/оп 7994958.59 оп/с aco_resume/co_amount=10 000 000/copy_stack_size=8B 40 000 000 1.327 с 33.19 нс/оп 30133654.80 оп/с aco_destroy 10 000 000 0.329 с 32.85 нс/оп 30439787.32 оп/с

aco_create/init_save_stk_sz=64B 10 000 000 0.674 с 67.37 нс/оп 14843796.57 оп/с aco_resume/co_amount=10 000 000/copy_stack_size=24B 40 000 000 1.354 с 33.84 нс/оп 29548523.05 оп/с aco_destroy 10 000 000 0.339 с 33.90 нс/оп 29494634.83 оп/с

aco_create/init_save_stk_sz=64B 10 000 000 0.672 с 67.19 нс/оп 14882262.88 оп/с aco_resume/co_amount=10 000 000/copy_stack_size=40B 40 000 000 1.361 с 34.02 нс/оп 29393520.19 оп/с aco_destroy 10 000 000 0.338 с 33.77 нс/оп 29609577.59 оп/с

aco_create/init_save_stk_sz=64B 10 000 000 0.673 с 67.31 нс/оп 14857716.02 оп/с aco_resume/co_amount=10 000 000/copy_stack_size=56B 40 000 000 1.371 с 34.27 нс/оп 29181897.80 оп/с aco_destroy 10 000 000 0.339 с 33.85 нс/оп 29540633.63 оп/с

aco_create/init_save_stk_sz=64B 10 000 000 0.672 с 67.24 нс/оп 14873017.10 оп/с aco_resume/co_amount=10 000 000/copy_stack_size=120B 40 000 000 1.548 с 38.71 нс/оп 25835542.17 оп/с aco_destroy 10 000 000 0.446 с 44.61 нс/оп 22415961.64 оп/с

aco_create/init_save_stk_sz=64B 2 000 000 0.132 с 66.01 нс/оп 15148290.52 оп/с aco_resume/co_amount=2 000 000/copy_stack_size=136B 20 000 000 0.944 с 47.22 нс/оп 21177946.19 оп/с aco_destroy 2 000 000 0.124 с 61.99 нс/оп 16132721.97 оп/с Все остальные сопрограммы, не являющиеся основными, делают то же самое.

Следующая диаграмма представляет собой простой пример переключения контекста между main_co и co.

В этом доказательстве мы просто предполагаем, что работаем под Sys V ABI от intel386, поскольку нет принципиальных различий между Sys V ABI от intel386 и x86-64. Мы также предполагаем, что ни один код не изменит управляющие слова FPU и MXCSR.

На следующей диаграмме фактически представлена модель работы симметричной сопрограммы с неограниченным количеством не основных co и одной основной co. Это нормально, потому что асимметричная сопрограмма — это всего лишь частный случай симметричной сопрограммы. Доказать корректность симметричной сопрограммы немного сложнее, чем асимметричной, и поэтому интереснее. (libaco в настоящее время реализовал только API асимметричной сопрограммы, потому что семантическое значение API асимметричной сопрограммы гораздо легче понять и использовать, чем у симметричной.)

Поскольку основная co — это первая сопрограмма, которая начинает работать, первое переключение контекста в этом потоке ОС должно быть в форме acosw(main_co, co), где второй аргумент co — это не основная co.

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

Легко доказать, что существует только два вида передачи состояния на приведённой выше диаграмме:

  • переданное состояние co → начальное состояние co;
  • переданное состояние co → переданное состояние co.

Чтобы доказать корректность реализации void* acosw(aco_t* from_co, aco_t* to_co), нужно доказать, что все co постоянно соответствуют ограничениям Sys V ABI до и после вызова acosw. Мы предполагаем, что другая часть двоичного кода (кроме acosw) в co уже соответствует ABI (обычно они правильно генерируются компилятором).

Вот краткое изложение ограничений регистров в соглашении о вызовах функций Intel386 Sys V ABI:

Использование регистров в соглашении о вызове функций Intel386 System V ABI:

Регистры, сохраняемые вызывающим (временные) регистры: C1.0: EAX При входе в вызов функции: может иметь любое значение После возврата из acosw: содержит возвращаемое значение для acosw C1.1: ECX,EDX При входе в вызов функции: может иметь любое значение После возврата из acosw: может иметь любое значение C1.2: Арифметические флаги, флаги x87 и mxcsr При входе в вызов функции: может иметь любое значение После возврата из acosw: может иметь любое значение C1.3: ST(0-7) При входе в вызов функции: стек FPU должен быть пустым После возврата из acosw: стек FPU должен быть пустым C1.4: Флаг направления При входе в вызов функции: DF должен быть 0 После возврата из acosw: DF должен быть 0 C1.5: другие: xmm*,ymm*,mm*,k*... При входе в вызов функции: может иметь любое значение После возврата из acosw: может иметь любое значение

Сохраняемые вызываемым регистры: C2.0: EBX,ESI,EDI,EBP При входе в вызов функции: может иметь любое значение После возврата из acosw: должно быть таким же, как при входе в acosw C2.1: ESP При входе в вызов функции: должен быть действительным указателем стека (выравнивание по 16 байтам, retaddr и т. д.) После возврата из acosw: должен быть таким же, как перед вызовом acosw C2.2: управляющее слово FPU & mxcsr При входе в вызов функции: может быть любой конфигурации После возврата из acosw: должно быть таким же, как до вызова acosw, если только вызывающий acosw не предполагает, что acosw может изменить управляющие слова FPU или MXCSR намеренно, как fesetenv Использование регистра определяется в «P13 — Таблица 2.3: Использование регистра» Sys V ABI Intel386 V1.1 и для AMD64 в «P23 — Рисунок 3.4: Использование регистра» Sys V ABI AMD64 V1.0.

Доказательство:

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

Рисунок proof_2 (не приведён).

Диаграмма выше относится к первому случаю: «состояние co -> начальное состояние 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 также удовлетворяется.

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

Рисунок proof_3 (не приведён).

Диаграмма выше относится ко второму случаю: состояние 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 удовлетворяются, поскольку происходит сохранение и восстановление регистров, сохранённых вызываемым объектом, при вызове/возврате acosw. Поскольку мы уже предположили, что никто не будет изменять управляющие слова FPU и MXCSR, C 2.2 также удовлетворяется.

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

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

Разное

Красная зона

Существует новая концепция, называемая красной зоной в System V ABI x86-64:

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

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

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

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

— Intel386-psABI-1.1:2.2.2 Стек

Указатель стека, %rsp, всегда указывает на конец последнего выделенного стекового фрейма.

— Sys V ABI AMD64 Version 1.0:3.2.2 Стек

Вот пример ошибки в Tencent's libco. ABI утверждает, что (E|R)SP должен всегда указывать на конец последнего выделенного стекового фрейма. Но в файле coctx_swap.S. Из документации libco: использование (E|R)SP для адресации памяти в куче

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

— man 7 signal: Signal dispositions

Могут произойти ужасные вещи, если (E|R)SP указывает на структуру данных в куче, когда приходит сигнал. (Использование команд breakpoint и signal в gdb может удобно создать такую ошибку. Хотя с помощью sigalstack для изменения стандартного стека сигналов можно решить проблему, но всё же такое использование (E|R)SP нарушает ABI.)

Лучшая практика

Если вы хотите получить максимальную производительность от libaco, просто сделайте использование стека неавтономным неглавным co в точке вызова aco_yield как можно меньше. И будьте очень осторожны, если вы хотите передать адрес локальной переменной от одного co другому co, поскольку локальная переменная обычно находится в общем стеке. Всегда разумнее выделять такие переменные из кучи.

В деталях есть 5 советов:

  1. Использование стека главным co не оказывает прямого влияния на производительность переключения контекста между сопрограммами (поскольку у него есть отдельный стек выполнения);
  2. Использование стека автономным неглавным co не оказывает прямого влияния на производительность переключения контекста между сопрограммами. Но огромное количество автономных неглавных co будет стоить слишком много виртуальной памяти (из-за автономного стека), поэтому не рекомендуется создавать огромное количество автономных неглавных co в одном потоке;
  3. Использование стека неавтономными (общий стек с другими сопрограммами) неглавными co при их выполнении (то есть вызов aco_yield для возврата к главному 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; // строка 7
    aco_yield();
    check(ct);
    int* ptr = &ct;
    inc_p(ptr);   // строка 11
    aco_exit();
}

void co_fp1() {
    do_sth(gl_ptr); // строка 16
    aco_exit();
}
  1. В приведённом выше фрагменте кода мы предполагаем, что co_fp0 и co_fp1 используют один и тот же общий стек (они оба неглавные co), и последовательность их выполнения — «co_fp0 → co_fp1 → co_fp0». Поскольку они используют один и тот же стек, адрес, хранящийся в 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();
}

TODO

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

  • Добавить макрос вроде aco_mem_new, который представляет собой комбинацию чего-то вроде p = Добавление нового API aco_reset для поддержки повторного использования объектов сопрограмм.

Поддержка других платформ (особенно arm и arm64).

ИЗМЕНЕНИЯ

v1.2.4 Sun Jul 29 2018
    Изменено `asm` на `__asm__` в aco.h для поддержки флага компилятора `--std=c99`
    (проблема №16, предложенная Тео Шлосснаглом @postwait).
v1.2.3 Thu Jul 26 2018
    Добавлена поддержка MacOS;
    Добавлена поддержка сборки разделяемой библиотеки libaco (PR #10, предложена
    Тео Шлосснаглем @postwait);
    Добавлен макрос C ACO_REG_IDX_BP в aco.h (PR #15, предложен Тео Шлосснаглем
    @postwait);
    Добавлен глобальный макрос конфигурации C ACO_USE_ASAN, который может включить
    дружественную поддержку санитайзера адресов (как gcc, так и clang) (PR #14,
    предложен Тео Шлосснаглом @postwait);
    Добавлен README_zh.md.
v1.2.2 Mon Jul 9 2018
    В make.sh добавлена новая опция `-o <no-m32|no-valgrind>`;
    Исправление значения макроса ACO_VERSION_PATCH (проблема №1, любезно сообщена
    Маркусом Эльфрингом @elfring);
    Скорректировано некоторое несоответствующее именование идентификаторов (двойное
    подчёркивание `__`) (проблема №1, предложено Маркусом Эльфрингом @elfring);
    Поддерживается включение заголовочного файла на C++ (проблема №4, предложено
    Маркусом Эльфрингом @elfring).
v1.2.1 Sat Jul 7 2018
    Исправлены некоторые несоответствующие защитные включения в двух заголовочных
    файлах C (проблема №1 любезно сообщена Маркусом Эльфрингом @elfring);
    Удалено слово «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, ура 🎉🎉🎉

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

Я разработчик с полной занятостью в открытом исходном коде. Любая сумма пожертвований будет высоко оценена и может стать для меня большим стимулом.

qr_alipay

  • Wechat (微信)

qr_wechat

Спасибо

Логотип libaco любезно предоставлен Питером Бехом (Peteck). Логотип лицензирован по CC BY-ND 4.0. Веб-сайт libaco.org также любезно предоставлен Питером Бехом (Peteck).

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

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

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

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

Комментарии ( 0 )

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

Введение

Описание недоступно Развернуть Свернуть
Apache-2.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