Skip to content

Commit b5dfeca

Browse files
committed
Updated README
1 parent 5f8c32e commit b5dfeca

1 file changed

Lines changed: 254 additions & 27 deletions

File tree

README.md

Lines changed: 254 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -335,59 +335,286 @@ auto first = tasks.begin();
335335
auto last = tasks.end();
336336
co_await pot::coroutines::when_all(first, last);
337337
```
338-
## Скалярное произведение
339-
Высокопроизводительная реализация скалярного произведения с векторизацией (SIMD) и параллельной обработкой по блокам через `executor`.
338+
## Elementwise_reduce
339+
Асинхронная поэлементная редукция над двумя массивами. Сначала к каждой паре элементов применяется бинарная операция (`elem_op(a[i], b[i])`), затем результаты сводятся редукцией (`reduce_op`) с начальным элементом `identity`. Есть удобные перегрузки для `pointer`/`std::span`/`std::vector`.
340+
### Сигнатуры
341+
```cpp
342+
// 1) Указатели
343+
template <typename T, typename R = T,
344+
typename ElemOp = std::plus<T>,
345+
typename ReduceOp = std::plus<R>>
346+
pot::coroutines::lazy_task<R>
347+
elementwise_reduce(pot::executor& exec,
348+
const T* a, const T* b, std::size_t n,
349+
ElemOp elem_op, ReduceOp reduce_op, R identity)
350+
requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<R>);
351+
352+
// 2) std::span
353+
template <typename T, typename R = T,
354+
typename ElemOp = std::plus<T>,
355+
typename ReduceOp = std::plus<T>>
356+
pot::coroutines::lazy_task<R>
357+
elementwise_reduce(pot::executor& exec,
358+
std::span<const T> a, std::span<const T> b,
359+
ElemOp elem_op, ReduceOp reduce_op, R identity);
360+
361+
// 3) std::vector
362+
template <typename T, typename R, typename ElemOp, typename ReduceOp>
363+
pot::coroutines::lazy_task<R>
364+
elementwise_reduce(pot::executor& exec,
365+
const std::vector<T>& a, const std::vector<T>& b,
366+
ElemOp elem_op, ReduceOp reduce_op, R identity);
367+
```
368+
369+
### Параметры и требования
370+
371+
- `T` — тип входных элементов (арифметический).
372+
373+
- `R` — тип результата (арифметический, по умолчанию `T`).
374+
375+
- `ElemOp` — вызываемый `(T, T) -> R`, применяется поэлементно.
376+
377+
- `ReduceOp` — вызываемый `(R, R) -> R`, сводит результаты.
378+
379+
- `exec` — `pot::executor` для планирования задач (используется `parfor`).
380+
381+
- Для перегрузок `span`/`vector` размеры должны совпадать, иначе `std::invalid_argument`.
382+
340383
384+
### Возвращаемое значение
385+
386+
`pot::coroutines::lazy_task<R>` — завершается значением редукции (при `n == 0` возвращается `identity`).
387+
388+
### Пример использования
389+
390+
**Скаларное произведение**
391+
```cpp
392+
auto dot = [&](pot::executor& exec,
393+
const std::vector<double>& a,
394+
const std::vector<double>& b)
395+
-> pot::coroutines::lazy_task<double>
396+
{
397+
co_return co_await pot::algorithms::elementwise_reduce<double,double>(
398+
exec, a, b,
399+
std::multiplies<double>{},
400+
std::plus<double>{},
401+
0.0
402+
);
403+
};
404+
405+
```
406+
407+
## Elementwise_reduce_simd
408+
SIMD-вариант поэлементной редукции: обрабатывает несколько элементов за итерацию через `pot::simd::simd_forced<..., ST>`, затем доредуцирует хвост скалярно.
341409
### Сигнатуры
342410
```cpp
343-
// указатели + длина
411+
// 1) Указатели (SIMD)
412+
template <typename T, typename R = T,
413+
pot::simd::SIMDType ST,
414+
typename SimdElemOp, // (simd_forced<T, ST>, simd_forced<T, ST>) -> simd_forced<R, ST>
415+
typename ScalarElemOp, // (T, T) -> R
416+
typename ReduceOp> // (R, R) -> R
417+
pot::coroutines::lazy_task<R>
418+
elementwise_reduce_simd(pot::executor& exec,
419+
const T* a, const T* b, std::size_t n,
420+
SimdElemOp simd_elem_op,
421+
ScalarElemOp scalar_elem_op,
422+
ReduceOp reduce_op,
423+
R identity)
424+
requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<R>);
425+
426+
// 2) std::span (SIMD)
427+
template <typename T, typename R, pot::simd::SIMDType ST,
428+
typename SimdElemOp, typename ScalarElemOp, typename ReduceOp>
429+
pot::coroutines::lazy_task<R>
430+
elementwise_reduce_simd(pot::executor& exec,
431+
std::span<const T> a, std::span<const T> b,
432+
SimdElemOp simd_elem_op,
433+
ScalarElemOp scalar_elem_op,
434+
ReduceOp reduce_op,
435+
R identity);
436+
437+
// 3) std::vector (SIMD)
438+
template <typename T, typename R, pot::simd::SIMDType ST,
439+
typename SimdElemOp, typename ScalarElemOp, typename ReduceOp>
440+
pot::coroutines::lazy_task<R>
441+
elementwise_reduce_simd(pot::executor& exec,
442+
const std::vector<T>& a, const std::vector<T>& b,
443+
SimdElemOp simd_elem_op,
444+
ScalarElemOp scalar_elem_op,
445+
ReduceOp reduce_op,
446+
R identity);
447+
```
448+
### Параметры и требования
449+
450+
- `ST` — конкретный векторный тип `pot::simd::SIMDType` (например, SSE/AVX и т.п.).
451+
452+
- `simd_elem_op` — операция на SIMD-регистры (возвращает SIMD-аккумулятор).
453+
454+
- `scalar_elem_op` — операция для хвостовых скалярных элементов.
455+
456+
- Остальные требования аналогичны скалярной версии.
457+
458+
459+
### Возвращаемое значение
460+
461+
`pot::coroutines::lazy_task<R>` — результат редукции с использованием SIMD и параллельной обработки блоков.
462+
463+
### Пример использования
464+
**L1-норма**
465+
```cpp
344466
template <typename T, pot::simd::SIMDType ST>
345467
pot::coroutines::lazy_task<T>
346-
dot_simd(pot::executor& exec, const T* a, const T* b, std::size_t n)
347-
requires(std::is_arithmetic_v<T>);
468+
l1_simd(pot::executor& exec, std::span<const T> a, std::span<const T> b)
469+
{
470+
auto simd_abs_diff = [](auto va, auto vb){
471+
auto vd = va - vb; // simd_forced<T, ST>
472+
return vd.abs();
473+
};
474+
auto scalar_abs_diff = [](T x, T y){ return std::abs(x - y); };
475+
476+
co_return co_await pot::algorithms::elementwise_reduce_simd<T, T, ST>(
477+
exec, a, b, simd_abs_diff, scalar_abs_diff, std::plus<T>{}, T{0});
478+
}
479+
```
348480

349-
// vectors
481+
## Dot / Dot_simd
482+
Асинхронное скалярное произведение двух массивов. Есть обычная и SIMD-версия; обе возвращают ленивую корутину с результатом.
483+
### Сигнатуры
484+
```cpp
485+
// SIMD: std::span
350486
template <typename T, pot::simd::SIMDType ST>
351487
pot::coroutines::lazy_task<T>
352-
dot_simd(pot::executor& exec, const std::vector<T>& a, const std::vector<T>& b)
353-
requires(std::is_arithmetic_v<T>);
488+
dot_simd(pot::executor& exec, std::span<const T> a, std::span<const T> b)
489+
requires(std::is_arithmetic_v<T>);
354490

355-
// полу-указатели (a_begin/a_end + b_begin)
491+
// SIMD: std::vector
356492
template <typename T, pot::simd::SIMDType ST>
357493
pot::coroutines::lazy_task<T>
358-
dot_simd(pot::executor& exec, const T* a_begin, const T* a_end, const T* b_begin)
359-
requires(std::is_arithmetic_v<T>);
494+
dot_simd(pot::executor& exec, const std::vector<T>& a, const std::vector<T>& b)
495+
requires(std::is_arithmetic_v<T>);
496+
497+
// Без SIMD: std::span
498+
template <typename T>
499+
pot::coroutines::lazy_task<T>
500+
dot(pot::executor& exec, std::span<const T> a, std::span<const T> b)
501+
requires(std::is_arithmetic_v<T>);
502+
503+
// Без SIMD: std::vector
504+
template <typename T>
505+
pot::coroutines::lazy_task<T>
506+
dot(pot::executor& exec, const std::vector<T>& a, const std::vector<T>& b)
507+
requires(std::is_arithmetic_v<T>);
360508
```
361-
### Параметры
509+
### Параметры и требования
362510
363511
- `T` — арифметический тип элементов.
364512
365-
- `ST` — стратегия SIMD из `pot::simd::SIMDType` (SSE/AVX/AVX512 ).
513+
- `ST` — целевой SIMD-тип (`pot::simd::SIMDType`) для `dot_simd`.
514+
515+
- `exec` — `pot::executor` для распараллеливания.
516+
517+
- `a`, `b` — входные последовательности одинаковой длины (иначе `std::invalid_argument`).
518+
519+
520+
### Возвращаемое значение
521+
522+
`pot::coroutines::lazy_task<T>` — завершается значением скалярного произведения.
523+
524+
### Примеры использования
525+
526+
1. Обычная версия (vector)
527+
```cpp
528+
std::vector<double> a = /* ... */, b = /* ... */;
529+
auto res = co_await pot::algorithms::dot(exec, a, b);
530+
```
531+
2. SIMD-версия (span)
532+
```cpp
533+
pot::coroutines::lazy_task<float> run_simd(pot::executor& exec,
534+
std::span<const float> a,
535+
std::span<const float> b) {
536+
co_return co_await pot::algorithms::dot_simd<float, AVX>(exec, a, b);
537+
}
538+
```
539+
540+
## Parsections
541+
Запускает несколько независимых секций параллельно на заданном исполнителе. Каждая секция — это вызываемый объект (`void()` или корутина, возвращающая `task<void>` / `lazy_task<void>`). Завершается, когда **все** секции отработают.
542+
543+
### Сигнатуры
544+
545+
```cpp
546+
template<typename... Funcs> requires (std::is_invocable_v<Funcs> && ...)
547+
pot::coroutines::lazy_task<void> parsections(pot::executor& executor, Funcs&&... funcs);
548+
```
549+
550+
### Параметры и требования
551+
552+
- `executor` — экземпляр `pot::executor`, на котором будут запущены все секции.
553+
554+
- `funcs...` — один или несколько вызываемых объектов:
366555

367-
- `exec` — исполнитель (одно- или многопоточный).
556+
- синхронные `void()` функции/лямбда-функции;
557+
558+
- корутины, возвращающие `pot::coroutines::task<void>` или `pot::coroutines::lazy_task<void>`.
559+
560+
- Требования:
368561

369-
- `a`, `b`, `n` — массивы и их размер; для перегрузок со `span`/`vector` размеры должны совпадать (иначе будет `std::invalid_argument`).
562+
- как минимум один аргумент (`static_assert(sizeof...(Funcs) > 0)`).
563+
564+
- каждый `Func` должен быть вызываем без аргументов (`std::is_invocable_v`).
565+
370566

371567
### Возвращаемое значение
372568

373-
`lazy_task<T>` — ленивая корутина, завершающаяся, когда всё посчитано. Результат — сумма типа `T`.
569+
`pot::coroutines::lazy_task<void>`завершается после завершения **всех** секций.
374570

375571
### Пример использования
376572
```cpp
377-
#include "pot/algorithms/dot_simd.h"
378-
#include "pot/executors/thread_pool_executor_lfgq.h"
573+
pot::coroutines::task<void> coroA();
574+
pot::coroutines::lazy_task<void> coroB();
575+
576+
co_await pot::algorithms::parsections(exec,
577+
[] { prepare(); },
578+
[]() -> pot::coroutines::task<void> { co_await coroA(); co_return; },
579+
[]() -> pot::coroutines::lazy_task<void> { co_await coroB(); co_return; }
580+
);
581+
```
582+
## Resume_on
583+
Функция, возвращающая awaitable, которая возобновляет выполнение текущей корутины на заданном `executor`.
379584

380-
pot::coroutines::task<void> example() {
381-
std::vector<T> a = {/* ... */};
382-
std::vector<T> b = {/* ... */};
585+
### Сигнатуры
383586

384-
pot::executors::thread_pool_executor_lfgq pool("dot-pool", 8);
587+
```cpp
588+
namespace pot::coroutines {
589+
// Возвращает awaitable-объект; при await — планирует продолжение на executor
590+
auto resume_on(pot::executor& executor) noexcept;
591+
}
592+
```
385593
386-
// как lazy_task<T>: можно co_await прямо здесь или позже
387-
T res =
388-
co_await pot::algorithms::dot_simd<float, pot::simd::SIMDType::AVX>(pool, a, b);
594+
### Поведение
389595
390-
std::cout << "dot(a,b) = " << res << "\n";
596+
- `co_await resume_on(exec)` всегда откладывает продолжение и передаёт `std::coroutine_handle<>` в `executor.run_detached(...)`.
597+
598+
- `await_ready()` всегда `false` — продолжение **всегда** переназначается на переданный `executor`.
599+
600+
- Без возвращаемого значения; ошибки (если есть) определяются реализацией `executor`.
601+
602+
603+
### Параметры и требования
604+
605+
- `executor` — экземпляр `pot::executor`, способный принять `std::coroutine_handle<>` через `run_detached(handle)` и возобновить его на собственном планировщике.
606+
607+
### Примеры использования
608+
```cpp
609+
using pot::coroutines::resume_on;
610+
611+
pot::coroutines::task<void> do_work(pot::executor& cpu1, pot::executor& cpu2)
612+
{
613+
co_await resume_on(cpu1); // продолжить на CPU1
614+
co_await heavy_compute(); // тяжёлая работа на CPU1
615+
co_await resume_on(cpu2); // продолжить на CPU2
616+
update(); // update() на CPU2
391617
co_return;
392618
}
393-
```
619+
```
620+

0 commit comments

Comments
 (0)