error: function 'RunImpl' has cognitive complexity...
Criado em: 12 de fevereiro de 2025
Criado em: 12 de fevereiro de 2025
error: function 'RunImpl' has cognitive complexity of 135 (threshold 25) [readability-function-cognitive-complexity,-warnings-as-errors] как мне исправить эту ошибку в коде
Сообщение об ошибке «function 'RunImpl' has cognitive complexity of 135 (threshold 25)» указывает на то, что функция RunImpl
слишком «громоздкая» и сложная, с точки зрения когнитивной нагрузки на того, кто читает/поддерживает код. Анализатор ругается на высокую когнитивную сложность. Чем выше она, тем сложнее понимать и тестировать эту функцию.
Чтобы избавиться от этой ошибки (и реально упростить код), нужно уменьшить когнитивную сложность. На практике это означает:
Разбить большую функцию на несколько более мелких
cpp// Было (очень упрощённый пример): void RunImpl() { // ... 100+ строк логики ... // много if/else и циклов } // Стало: void RunImpl() { DoInitialization(); ProcessMainLogic(); Finalize(); } void DoInitialization() { // код инициализации } void ProcessMainLogic() { // основной блок, возможно тоже разбитый на подблоки // внутри тоже можно разбивать, если нужно } void Finalize() { // завершающие действия }
Каждый блок может быть вынесен в отдельную функцию, что снизит вложенность в основной функции RunImpl
.
Уменьшить вложенность if/else
, switch/case
, циклов
if
.if (...) { if (...) { if (...) {...} } }
использовать «ранние return’ы» или комбинацию if (...) return;
.Переосмыслить логику
Иногда логика, ведущая к высокой когнитивной сложности, действительно может быть «подчищена»:
if
в одну, если это возможно.Проверить, не нужно ли вынести некоторые проверки в классы/структуры
Если функция занимается всем сразу (подгрузкой данных, валидацией, обработкой, логированием, выводом, и так далее), то её можно декомпозировать:
Использовать более высокий уровень абстракций
Например, если вы часто и вручную обрабатываете одни и те же проверки (цикл, внутри куча if/else
), возможно, вам помогут стандартные алгоритмы (например, std::find_if
, std::any_of
и т.д.) или паттерны функционального программирования (лямбда-функции, вложенные функции). Это позволяет сократить код и сделать его более декларативным, что в итоге снижает его когнитивную сложность.
Чтобы устранить ошибку «cognitive complexity of 135 (threshold 25)», главная мысль — разделить функцию на несколько более простых, уменьшить вложенность и переупорядочить логику таким образом, чтобы код стал более «линейным» и понятным. Обычно это включает вынос блоков кода в отдельные функции, удаление избыточных проверок, использование раннего выхода из функции при определённых условиях и общее упрощение конструкции.
Таким образом, вы и «погасите» предупреждение от анализатора (предупреждение превратилось в ошибку из-за ключа -warnings-as-errors
), и сделаете код более поддерживаемым в долгосрочной перспективе.
narrowing conversion from 'double' to 'size_type' (aka 'unsigned long') [bugprone-narrowing-conversions,-warnings-as-errors]
36 | output_matrix_C_[(i * dimension) + j] +=
Сообщение об ошибке вида:
textnarrowing conversion from 'double' to 'size_type' (aka 'unsigned long') [bugprone-narrowing-conversions,-warnings-as-errors] 36 | output_matrix_C_[(i * dimension) + j] += ...
означает, что в выражении (i * dimension) + j
как минимум одна из переменных (i
, dimension
или j
) имеет тип double
, а результат при этом используется как индекс массива (который, по сути, должен быть целочисленным — чаще всего size_t
).
Компилятор (или статический анализатор) жалуется на «сужающее» (narrowing) преобразование из double
в size_t
: даже если само число в double
целое, в общем случае возможно потерять дробную часть или выйти за границы допустимого диапазона.
Если dimension
, i
и j
концептуально представляют целочисленные значения (размер матрицы, номер строки/столбца и т.п.), то лучшей практикой будет:
int
, long long
, std::size_t
и т.д.) — то есть избавиться от double
в переменных, которые отражают дискретные индексы.(i * dimension) + j
автоматически станет целочисленным, и «сужающее» преобразование исчезнет.Пример (наиболее типичный для индексов в матрице):
cpp// Вместо double dimension, i, j // используем size_t (или int), если гарантированно не будет отрицательных значений size_t dimension = 10; for (size_t i = 0; i < dimension; ++i) { for (size_t j = 0; j < dimension; ++j) { output_matrix_C_[(i * dimension) + j] += ...; } }
Бывают случаи, когда логика программы требует вычисления в double
, а затем используется результат как индекс.
Тогда нужно:
cppdouble doubleIndex = i * dimension + j; std::size_t index = static_cast<std::size_t>(doubleIndex); // Но тут важно убедиться, что doubleIndex >= 0 и <= размер_массива - 1 output_matrix_C_[index] += ...;
double
для дискретных индексов, так как это может привести к скрытым ошибкам (особенно если doubleIndex
окажется дробным).Иногда double
«прокрадывается» по цепочке: результат вычислений (например, std::sqrt(…)
или какие-нибудь рассчёты координат) пытаются напрямую использовать как индекс. Нужно убедиться, что это действительно индекс целочисленного массива, либо изменить структуру кода.
dimension
), а также переменные i
и j
в целочисленных типах (например, size_t
или int
, в зависимости от ваших ограничений).double
, то используйте явное приведение типов (с проверками), но обычно это менее предпочтительно для индексов.В большинстве случаев достаточно заменить double dimension;
на size_t dimension;
и аналогично int i, j;
или size_t i, j;
, после чего ошибка «narrowing conversion» исчезнет.
narrowing conversion from 'double' to 'size_type' (aka 'unsigned long') [bugprone-narrowing-conversions,-warnings-as-errors]
36 | output_matrix_C_[(i * dimension) + j] +=
Ошибка «narrowing conversion from 'double' to 'size_type' (aka 'unsigned long')» означает, что в выражении (i * dimension) + j
как минимум одна из переменных (i
, dimension
или j
) имеет тип double
, а результат вычисления используется как индекс (тип size_t
). Компилятор (или статический анализатор) ругается, потому что это «сужающее» преобразование из double
в целый тип: в общем случае дробная часть теряется, и программа потенциально может выйти за границы массива, если число не будет целым или окажется слишком большим/отрицательным.
Самое частое и правильное решение — заменить типы переменных, хранящих индексы и размерности (например, i
, j
и dimension
) на целочисленные (int
, long long
, size_t
и т.п.). Например:
cpp// Вместо double dimension size_t dimension = 10; // Вместо double i, j for (size_t i = 0; i < dimension; ++i) { for (size_t j = 0; j < dimension; ++j) { // Теперь в выражении (i * dimension + j) нет никакого double output_matrix_C_[(i * dimension) + j] += ...; } }
Так вы исключите «narrowing conversion» полностью.
Если у вас по логике одна из переменных действительно должна быть double
, а затем её результат использоваться как индекс, то нужно:
output_matrix_C_[...]
, вы проверили, что приводимое значение не выходит за границы массива и не содержит «нецелую» часть (или округлили её надлежащим образом).Пример (упрощённый):
cppdouble iDouble = ...; // вычислили где-то в коде size_t dimension = ...; size_t j = ...; // Перед использованием в качестве индекса – проверяем/кастим: size_t i = static_cast<size_t>(iDouble); if (i >= dimension) { // Обработать ошибку или обрезать i до dimension-1 } output_matrix_C_[(i * dimension) + j] += ...;
Но обычно хранить индексы в double
нежелательно: это легко приводит к потерям точности, особенно при больших значениях.
double
, используйте явный каст (например, static_cast<size_t>(...)
) с проверкой корректности — но в большинстве случаев правильнее пересмотреть архитектуру, чтобы избежать дробных типов для индексов.bool deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::RunImpl() {
unsigned short i = 0;
unsigned short j = 0;
unsigned short k = 0;
unsigned short dimension = 0;
if (world_.size() == 1 || world_.size() != pow(sqrt(static_cast<unsigned short>(world_.size())), 2) ||
(unsigned short)sqrt(static_cast<unsigned short>(input_matrix_A_.size())) % (unsigned short)sqrt(static_cast<unsigned short>(world_.size())) != 0) {
if (world_.rank() == 0) {
dimension = (unsigned short)sqrt(static_cast<unsigned short>(input_matrix_A_.size()));
output_matrix_C_ = std::vector<double>(dimension * dimension);
while (i != dimension) {
j = 0;
while (j != dimension) {
k = 0;
while (k != dimension) {
output_matrix_C_[(i * dimension) + j] += input_matrix_A_[(i * dimension) + k] * input_matrix_B_[(k * dimension) + j];
k++;
}
j++;
}
i++;
}
}
} else {
unsigned short block_dimension = 0;
unsigned short block_rows_columns = 0;
if (world_.rank() == 0) {
dimension = (unsigned short)sqrt(static_cast<unsigned short>(input_matrix_A_.size()));
block_rows_columns = (unsigned short)sqrt(world_.size());
block_dimension = dimension / block_rows_columns;
}
boost::mpi::broadcast(world_, dimension, 0);
output_matrix_C_ = std::vector<double>(dimension * dimension);
boost::mpi::broadcast(world_, block_dimension, 0);
local_input_matrix_A_ = std::vector<double>(block_dimension * block_dimension);
local_input_matrix_B_ = std::vector<double>(block_dimension * block_dimension);
local_output_matrix_C_ = std::vector<double>(block_dimension * block_dimension);
boost::mpi::broadcast(world_, block_rows_columns, 0);
if (world_.rank() == 0) {
k = 0;
while (k != block_dimension) {
std::copy(input_matrix_A_.data() + (k * dimension), input_matrix_A_.data() + (k * dimension) + block_dimension,
local_input_matrix_A_.begin() + (k * block_dimension));
std::copy(input_matrix_B_.data() + (k * dimension), input_matrix_B_.data() + (k * dimension) + block_dimension,
local_input_matrix_B_.begin() + (k * block_dimension));
k++;
}
while (i != block_rows_columns) {
j = 0;
while (j != block_rows_columns) {
if (i != 0 || j != 0) {
k = 0;
while (k != block_dimension) {
if (i == 0) {
world_.send((i * block_rows_columns) + j, 0,
input_matrix_A_.data() + ((i * block_dimension + k) * dimension) + (j * block_dimension),
block_dimension);
} else {
if ((i * block_rows_columns) + j - i < i * block_rows_columns) {
world_.send((i * block_rows_columns) + j + block_rows_columns - i, 0,
input_matrix_A_.data() + ((i * block_dimension + k) * dimension) + (j * block_dimension),
block_dimension);
} else {
world_.send((i * block_rows_columns) + j - i, 0,
input_matrix_A_.data() + ((i * block_dimension + k) * dimension) + (j * block_dimension),
block_dimension);
}
}
if (j == 0) {
world_.send((i * block_rows_columns) + j, 1,
input_matrix_B_.data() + ((i * block_dimension + k) * dimension) + (j * block_dimension),
block_dimension);
} else {
if ((i - j) * block_rows_columns + j < 0) {
world_.send(((i + block_rows_columns - j) * block_rows_columns) + j, 1,
input_matrix_B_.data() + ((i * block_dimension + k) * dimension) + (j * block_dimension),
block_dimension);
} else {
world_.send(((i - j) * block_rows_columns) + j, 1,
input_matrix_B_.data() + (((i * block_dimension) + k) * dimension) + (j * block_dimension),
block_dimension);
}
}
k++;
}
}
j++;
}
i++;
}
} else {
k = 0;
while (k != block_dimension) {
world_.recv(0, 0, local_input_matrix_A_.data() + (k * block_dimension), block_dimension);
world_.recv(0, 1, local_input_matrix_B_.data() + (k * block_dimension), block_dimension);
k++;
}
}
i = 0;
while (i != block_dimension) {
j = 0;
while (j != block_dimension) {
k = 0;
while (k != block_dimension) {
local_output_matrix_C_[(i * block_dimension) + j] +=
local_input_matrix_A_[(i * block_dimension) + k] * local_input_matrix_B_[(k * block_dimension) + j];
k++;
}
j++;
}
i++;
}
unsigned short p = 1;
while (p != block_rows_columns) {
if (block_rows_columns != 0 && world_.rank() % block_rows_columns == 0) {
world_.send(world_.rank() + block_rows_columns - 1, 2, local_input_matrix_A_.data(),
block_dimension * block_dimension);
} else {
world_.send(world_.rank() - 1, 3, local_input_matrix_A_.data(), block_dimension * block_dimension);
}
if (world_.rank() < block_rows_columns) {
world_.send(world_.rank() + (block_rows_columns * (block_rows_columns - 1)), 4, local_input_matrix_B_.data(),
block_dimension * block_dimension);
} else {
world_.send(world_.rank() - block_rows_columns, 5, local_input_matrix_B_.data(),
block_dimension * block_dimension);
}
if (block_rows_columns != 0 && (world_.rank() + 1) % block_rows_columns == 0) {
world_.recv(world_.rank() - block_rows_columns + 1, 2, local_input_matrix_A_.data(),
block_dimension * block_dimension);
} else {
world_.recv(world_.rank() + 1, 3, local_input_matrix_A_.data(), block_dimension * block_dimension);
}
if (world_.rank() >= block_rows_columns * (block_rows_columns - 1)) {
world_.recv(world_.rank() - (block_rows_columns * (block_rows_columns - 1)), 4, local_input_matrix_B_.data(),
block_dimension * block_dimension);
} else {
world_.recv(world_.rank() + block_rows_columns, 5, local_input_matrix_B_.data(),
block_dimension * block_dimension);
}
i = 0;
while (i != block_dimension) {
j = 0;
while (j != block_dimension) {
k = 0;
while (k != block_dimension) {
local_output_matrix_C_[(i * block_dimension) + j] +=
local_input_matrix_A_[(i * block_dimension) + k] * local_input_matrix_B_[(k * block_dimension) + j];
k++;
}
j++;
}
i++;
}
p++;
}
if (world_.rank() != 0) {
for (unsigned short block_row = 0; block_row < block_dimension; ++block_row) {
world_.send(0, 0, local_output_matrix_C_.data() + (block_row * block_dimension), block_dimension);
}
} else {
for (int proc = 1; proc < world_.size(); ++proc) {
for (unsigned short block_row = 0; block_row < block_dimension; ++block_row) {
std::copy(
local_output_matrix_C_.begin() + (block_row * block_dimension),
local_output_matrix_C_.begin() + ((block_row + 1) * block_dimension),
output_matrix_C_.begin() + (((world_.rank() / block_rows_columns) * block_dimension) + (block_row * dimension) +
((world_.rank() % block_rows_columns) * block_dimension)));
world_.recv(proc, 0,
output_matrix_C_.data() + ((((proc / block_rows_columns) * block_dimension) + block_row) * dimension) +
((proc % block_rows_columns) * block_dimension),
block_dimension);
}
}
}
}
return true;
}
Разбей эту функцию на несколько небольших функций, разбиение должно быть осмысленным
Ниже приведён пример, как можно логически разбить ваш текущий метод RunImpl()
на несколько более мелких функций, чтобы снизить его когнитивную сложность и упростить чтение кода. Обратите внимание, что код разбит по ключевым этапам алгоритма Каннона и «тривиальной» ветви, но сохранена общая логика. Названия функций можно менять по своему усмотрению.
Предположим, что мы находимся внутри класса:
cppnamespace deryabin_m_cannons_algorithm_mpi { class CannonsAlgorithmMPITaskParallel { public: // ... bool RunImpl(); private: // --- Приватные методы для декомпозиции --- bool IsTrivialCase() const; void HandleTrivialCase(); void PerformCannonAlgorithm(); // вспомогательные под-этапы алгоритма Каннона void InitializeAndBroadcastParams(); void DistributeDataIfRoot(); void ReceiveDataIfNotRoot(); void MultiplyLocalBlocks(); void PerformCannonShifts(); void GatherResults(); private: // --- Поля, которые вы используете в RunImpl() --- boost::mpi::communicator world_; std::vector<double> input_matrix_A_; std::vector<double> input_matrix_B_; std::vector<double> output_matrix_C_; // локальные блоки std::vector<double> local_input_matrix_A_; std::vector<double> local_input_matrix_B_; std::vector<double> local_output_matrix_C_; // служебные поля // ... unsigned short dimension_ = 0; unsigned short block_dimension_ = 0; unsigned short block_rows_columns_ = 0; }; } // namespace deryabin_m_cannons_algorithm_mpi
RunImpl
с разделением на шагиcppbool deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::RunImpl() { // Проверяем, не «тривиальный» ли случай if (IsTrivialCase()) { HandleTrivialCase(); } else { PerformCannonAlgorithm(); } return true; // По логике возвращаете true }
cppbool deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::IsTrivialCase() const { // Логика совпадает с вашим if(...): // 1) world_.size() == 1 // 2) ИЛИ world_.size() != pow(sqrt(...), 2) // 3) ИЛИ (sqrt(...) % sqrt(world_.size()) != 0) // Обратите внимание, что вычисления double->unsigned short можно упростить или явно кастить. // Здесь оставим как в вашем коде. return ( world_.size() == 1 || world_.size() != static_cast<int>(std::pow(std::sqrt(static_cast<unsigned short>(world_.size())), 2)) || (static_cast<unsigned short>(std::sqrt(static_cast<unsigned short>(input_matrix_A_.size()))) % static_cast<unsigned short>(std::sqrt(static_cast<unsigned short>(world_.size())))) != 0 ); }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::HandleTrivialCase() { if (world_.rank() == 0) { // В вашем коде dimension вычислялся один раз unsigned short dimension = static_cast<unsigned short>( std::sqrt(static_cast<unsigned short>(input_matrix_A_.size())) ); output_matrix_C_.resize(dimension * dimension, 0.0); // Три вложенных цикла. Можно оставить while, можно переписать на for. for (unsigned short i = 0; i < dimension; ++i) { for (unsigned short j = 0; j < dimension; ++j) { for (unsigned short k = 0; k < dimension; ++k) { output_matrix_C_[(i * dimension) + j] += input_matrix_A_[(i * dimension) + k] * input_matrix_B_[(k * dimension) + j]; } } } } }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::PerformCannonAlgorithm() { // Инициализируем dimension_, block_dimension_ и block_rows_columns_, // а также делаем broadcast по всем процессам. InitializeAndBroadcastParams(); // Готовим выходную матрицу требуемого размера output_matrix_C_.resize(dimension_ * dimension_, 0.0); // Локальные блоки local_input_matrix_A_.resize(block_dimension_ * block_dimension_, 0.0); local_input_matrix_B_.resize(block_dimension_ * block_dimension_, 0.0); local_output_matrix_C_.resize(block_dimension_ * block_dimension_, 0.0); // Распределяем данные (только root отправляет, остальные получают) if (world_.rank() == 0) { DistributeDataIfRoot(); } else { ReceiveDataIfNotRoot(); } // Умножаем локальные блоки один раз MultiplyLocalBlocks(); // Выполняем циклы "сдвигов" (phases) алгоритма Каннона PerformCannonShifts(); // Собираем (gather) результаты в root GatherResults(); }
Далее каждый из под-методов:
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::InitializeAndBroadcastParams() { if (world_.rank() == 0) { // dimension_ = √(размер входной матрицы) dimension_ = static_cast<unsigned short>( std::sqrt(static_cast<unsigned short>(input_matrix_A_.size())) ); // block_rows_columns_ = √(число процессов) block_rows_columns_ = static_cast<unsigned short>( std::sqrt(static_cast<unsigned short>(world_.size())) ); // block_dimension_ = dimension_ / block_rows_columns_ block_dimension_ = dimension_ / block_rows_columns_; } // Рассылаем всем процессам boost::mpi::broadcast(world_, dimension_, 0); boost::mpi::broadcast(world_, block_dimension_, 0); boost::mpi::broadcast(world_, block_rows_columns_, 0); }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::DistributeDataIfRoot() { // Сначала копируем «свои» данные (блок (0,0)) в local_input_matrix_A_ / local_input_matrix_B_. for (unsigned short k = 0; k < block_dimension_; ++k) { // Копируем block_dimension_ элементов из строки k std::copy( input_matrix_A_.data() + (k * dimension_), input_matrix_A_.data() + (k * dimension_) + block_dimension_, local_input_matrix_A_.begin() + (k * block_dimension_) ); std::copy( input_matrix_B_.data() + (k * dimension_), input_matrix_B_.data() + (k * dimension_) + block_dimension_, local_input_matrix_B_.begin() + (k * block_dimension_) ); } // Рассылаем остальным процессам их блоки for (unsigned short i = 0; i < block_rows_columns_; ++i) { for (unsigned short j = 0; j < block_rows_columns_; ++j) { // Если i=0 и j=0, это уже обработано (локальный блок) if (i == 0 && j == 0) { continue; } // Отправляем по строкам block_dimension_ for (unsigned short k = 0; k < block_dimension_; ++k) { // Адрес процесса int destination_proc = (i * block_rows_columns_) + j; // Отправка блока из A if (i == 0) { // как в вашем коде world_.send( destination_proc, 0, input_matrix_A_.data() + ((i * block_dimension_ + k) * dimension_) + (j * block_dimension_), block_dimension_ ); } else { // логика с (i * block_rows_columns_) + j - i < i * block_rows_columns_ // сохраняем без изменений if (static_cast<int>((i * block_rows_columns_) + j - i) < static_cast<int>(i * block_rows_columns_)) { world_.send( destination_proc + block_rows_columns_ - i, 0, input_matrix_A_.data() + ((i * block_dimension_ + k) * dimension_) + (j * block_dimension_), block_dimension_ ); } else { world_.send( destination_proc - i, 0, input_matrix_A_.data() + ((i * block_dimension_ + k) * dimension_) + (j * block_dimension_), block_dimension_ ); } } // Отправка блока из B if (j == 0) { world_.send( destination_proc, 1, input_matrix_B_.data() + ((i * block_dimension_ + k) * dimension_) + (j * block_dimension_), block_dimension_ ); } else { // (i - j) * block_rows_columns_ + j может быть < 0, проверяем if (static_cast<int>((i - j) * block_rows_columns_ + j) < 0) { world_.send( (i + block_rows_columns_ - j) * block_rows_columns_ + j, 1, input_matrix_B_.data() + ((i * block_dimension_ + k) * dimension_) + (j * block_dimension_), block_dimension_ ); } else { world_.send( (i - j) * block_rows_columns_ + j, 1, input_matrix_B_.data() + (((i * block_dimension_) + k) * dimension_) + (j * block_dimension_), block_dimension_ ); } } } // конец for k } // конец for j } // конец for i }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::ReceiveDataIfNotRoot() { // Просто считываем block_dimension_ строк от процесса 0 (или иного). // Логика взята из вашего else for (unsigned short k = 0; k < block_dimension_; ++k) { // Получаем часть матрицы A world_.recv(0, 0, local_input_matrix_A_.data() + (k * block_dimension_), block_dimension_); // Получаем часть матрицы B world_.recv(0, 1, local_input_matrix_B_.data() + (k * block_dimension_), block_dimension_); } }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::MultiplyLocalBlocks() { for (unsigned short i = 0; i < block_dimension_; ++i) { for (unsigned short j = 0; j < block_dimension_; ++j) { for (unsigned short k = 0; k < block_dimension_; ++k) { local_output_matrix_C_[(i * block_dimension_) + j] += local_input_matrix_A_[(i * block_dimension_) + k] * local_input_matrix_B_[(k * block_dimension_) + j]; } } } }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::PerformCannonShifts() { // p от 1 до block_rows_columns_ - 1 (в вашем коде while(p != block_rows_columns)) for (unsigned short p = 1; p < block_rows_columns_; ++p) { // Отправляем/получаем local_input_matrix_A_ if (block_rows_columns_ != 0 && (world_.rank() % block_rows_columns_ == 0)) { // world_.rank() + block_rows_columns_ - 1 world_.send(world_.rank() + block_rows_columns_ - 1, 2, local_input_matrix_A_.data(), block_dimension_ * block_dimension_); } else { // world_.rank() - 1 world_.send(world_.rank() - 1, 3, local_input_matrix_A_.data(), block_dimension_ * block_dimension_); } // Отправляем/получаем local_input_matrix_B_ if (world_.rank() < block_rows_columns_) { world_.send(world_.rank() + (block_rows_columns_ * (block_rows_columns_ - 1)), 4, local_input_matrix_B_.data(), block_dimension_ * block_dimension_); } else { world_.send(world_.rank() - block_rows_columns_, 5, local_input_matrix_B_.data(), block_dimension_ * block_dimension_); } // Получаем обратно local_input_matrix_A_ if (block_rows_columns_ != 0 && ((world_.rank() + 1) % block_rows_columns_ == 0)) { world_.recv(world_.rank() - block_rows_columns_ + 1, 2, local_input_matrix_A_.data(), block_dimension_ * block_dimension_); } else { world_.recv(world_.rank() + 1, 3, local_input_matrix_A_.data(), block_dimension_ * block_dimension_); } // Получаем обратно local_input_matrix_B_ if (world_.rank() >= block_rows_columns_ * (block_rows_columns_ - 1)) { world_.recv(world_.rank() - (block_rows_columns_ * (block_rows_columns_ - 1)), 4, local_input_matrix_B_.data(), block_dimension_ * block_dimension_); } else { world_.recv(world_.rank() + block_rows_columns_, 5, local_input_matrix_B_.data(), block_dimension_ * block_dimension_); } // Повторяем умножение локальных блоков for (unsigned short i = 0; i < block_dimension_; ++i) { for (unsigned short j = 0; j < block_dimension_; ++j) { for (unsigned short k = 0; k < block_dimension_; ++k) { local_output_matrix_C_[(i * block_dimension_) + j] += local_input_matrix_A_[(i * block_dimension_) + k] * local_input_matrix_B_[(k * block_dimension_) + j]; } } } } }
cppvoid deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::GatherResults() { if (world_.rank() != 0) { // Не root-процессы посылают локальные блоки for (unsigned short block_row = 0; block_row < block_dimension_; ++block_row) { world_.send(0, 0, local_output_matrix_C_.data() + (block_row * block_dimension_), block_dimension_); } } else { // Собираем свой блок сначала for (unsigned short block_row = 0; block_row < block_dimension_; ++block_row) { // Копируем local_output_matrix_C_ в нужное место выходной матрицы std::copy( local_output_matrix_C_.begin() + (block_row * block_dimension_), local_output_matrix_C_.begin() + ((block_row + 1) * block_dimension_), output_matrix_C_.begin() + (((world_.rank() / block_rows_columns_) * block_dimension_ + block_row) * dimension_) + ((world_.rank() % block_rows_columns_) * block_dimension_) ); } // Получаем блоки от остальных процессов for (int proc = 1; proc < world_.size(); ++proc) { for (unsigned short block_row = 0; block_row < block_dimension_; ++block_row) { world_.recv(proc, 0, output_matrix_C_.data() + ((((proc / block_rows_columns_) * block_dimension_) + block_row) * dimension_) + ((proc % block_rows_columns_) * block_dimension_), block_dimension_); } } } }
Сигнатуры функций
Вы можете сделать их приватными методами класса (как в примере), либо вынести часть логики в отдельные свободные функции (если это больше подходит вашей архитектуре).
Типы переменных
Обратите внимание, что у вас часто используется unsigned short
и приведение к нему из double
. Для индексов (размеров массивов и т.п.) обычно более надёжно использовать size_t
или как минимум unsigned int
. Это поможет избежать лишних ошибок и «narrowing conversion».
Упрощение условия IsTrivialCase()
world_.size() != pow(sqrt(...), 2)
часто можно переписать так, чтобы избежать повторной операции sqrt
и потенциальных проблем с double->int. Например, можно целочисленно проверить, является ли world_.size()
квадратом целого числа.Циклы for
вместо while
В оригинальном коде много while (i != dimension)
. Это можно заменить на for (i = 0; i < dimension; ++i)
, что обычно улучшает читаемость и упрощает контроль за счётчиком.
Уменьшение глубины вложенности
Часть if/else логики при отправке блоков может быть ещё больше упрощена, если вы заранее вычислите нужный destination_proc
и используете один вызов world_.send()
. Но это уже зависит от ваших требований к читабельности и точности повторения оригинальной логики.
Таким образом, основной подход к сокращению когнитивной сложности — выделение крупных логических шагов (инициализация, распределение, локальное умножение, сдвиги, сбор результатов) в отдельные методы с понятными именами. Даже если внутри каждого метода код остаётся довольно большим, уровень вложенности в основном методе RunImpl()
существенно сокращается, и поддерживать такой код проще.
error: function 'IsTrivialCase' should be marked [[nodiscard]] [modernize-use-nodiscard,-warnings-as-errors]
38 | bool IsTrivialCase() const;
| ^
| [[nodiscard]]
Сообщение
error: function 'IsTrivialCase' should be marked [[nodiscard]] [modernize-use-nodiscard,-warnings-as-errors]
означает, что статический анализатор (или компилятор с включённым предупреждением -Werror
) рекомендует (и в данном случае требует) использовать атрибут [[nodiscard]]
для функции, возвращающей значение. Это нужно для того, чтобы при вызове такой функции компилятор мог предупредить, если результат вызова игнорируется. Таким образом повышается безопасность кода: разработчик явно видит, что результат вызова важен для логики и не должен быть пропущен.
Просто добавьте [[nodiscard]]
(C++17+) в объявлении (и желательно определении) функции IsTrivialCase()
. Например:
cppclass CannonsAlgorithmMPITaskParallel { public: // ... private: [[nodiscard]] bool IsTrivialCase() const; // ... };
Либо, если определяете метод прямо в классе, то:
cppclass CannonsAlgorithmMPITaskParallel { private: [[nodiscard]] bool IsTrivialCase() const { // ... } };
Если вы используете более ранний стандарт языка, где нет [[nodiscard]]
, можно заменить его на макрос, например __attribute__((warn_unused_result))
(для GCC/Clang), но в современных компиляторах C++17+ [[nodiscard]]
уже широко поддерживается.