Сделал небольшой тест. Мы берем массив точек и поворачиваем их вокруг какой-то оси в трехмерном пространстве путем перемножения матрицы вращения на точку. Умножение матрицы вращения на 90 град вокруг z-оси на точку (1, 0, 0) в 3d выглядит так:
И матрица вращения вокруг произвольной оси (углы по осям X,Y, Z):Самое интересное начивается, когда мы, ускоряя процесс вычислений, распаллеливаем их между потоками. У нас в стандартной библиотеке thread есть функция std::thread::hardware_concurrency(), которая возвращает количество логических процессоров (с учетом HT/SMT). Между ними мы и делим данные, чтобы каждый поток обрабатывал свой кусочек. Это можно сделать двумя способами. Первый способ: каждый поток имеет айди от 0 до N-1, где N - это количество логических процессоров (то, что возвращает ф-я std::thread::hardware_concurrency), тогда мы можем давать каждому потоку весь массив и он будет обрабатывать в нем элемент, соотв. своему айди. То есть в каждой порции данных поток выбирает свой элемент и его обрабатывает. Можно улучшить этот способ путем обработки каждым потоком не одного элемента, а сразу многих. Выглядит это в коде вот так:
void applyToPoints(std::vector<Point3f>& points, const int elemCnt, PointFunc func, const unsigned int thrCnt = std::thread::hardware_concurrency()) { std::vector<std::thread> workers; for (int thrId = 0; thrId < thrCnt; ++thrId) { workers.push_back(std::thread([thrId, thrCnt, elemCnt, &points, &func]() { for (int i = thrId * elemCnt; i < vectSize; i += thrCnt * elemCnt) { int blockEndIdx = std::min(i + elemCnt, (int)vectSize); for (int j = i; j < blockEndIdx; ++j) { func(points[j]); } } })); } for (std::thread& t : workers) { t.join(); } workers.clear(); }
Второй способ: мы делим весь массив на равные части и "скармливаем" каждую часть своему потоку и он уже непрерывно обрабатывает все элементы:
Теперь посмотрим на результаты:void applyToPoints2ndWay(std::vector<Point3f>& points, PointFunc func, const unsigned int thrCnt = std::thread::hardware_concurrency()) { auto start = std::chrono::steady_clock::now(); std::vector<std::thread> workers; size_t rod = points.size() % thrCnt; auto thrItemCnt = (points.size() - rod) / thrCnt; for (int thrId = 0; thrId < thrCnt; ++thrId) { workers.push_back(std::thread([thrId, thrCnt, thrItemCnt, &points, &func]() { auto begIdx = thrId * thrItemCnt; bool isLastThread = (thrId == thrCnt - 1); auto endIdx = isLastThread ? points.size() : begIdx + thrItemCnt; for (auto i = begIdx; i < endIdx; ++i) { func(points[i]); } })); } for (std::thread& t : workers) { t.join(); } workers.clear(); }
-- create array.. -- create array done! -- init array: threads: 16, block size: 225000000 bytes, time: 2.15009 threads: 8, block size: 450000000 bytes, time: 2.77854 -- init array done! -- 1st way: many small blocks: threads: 1, block size: 12 bytes, time: 2.31308 threads: 16, block size: 12 bytes, time: 1.00347 threads: 16, block size: 96 bytes, time: 0.575906 threads: 16, block size: 1536 bytes, time: 0.39477 threads: 16, block size: 12288 bytes, time: 0.348051 threads: 16, block size: 122880 bytes, time: 0.324958 -- 2st way: the biggest blocks: threads: 16, block size: 225000000 bytes, time: 0.338135 threads: 8, block size: 450000000 bytes, time: 0.340936 threads: 4, block size: 900000000 bytes, time: 0.593947 threads: 2, block size: 1800000000 bytes, time: 1.14009Обращают на себя внимание несколько моментов.