error: function 'RunImpl' has cognitive complexity...

Criado em: 12 de fevereiro de 2025

Respondido usando O1 por Chat01

Pergunta

error: function 'RunImpl' has cognitive complexity of 135 (threshold 25) [readability-function-cognitive-complexity,-warnings-as-errors] как мне исправить эту ошибку в коде

Pensamento

Pensou por 5 segundos

Resposta

Сообщение об ошибке «function 'RunImpl' has cognitive complexity of 135 (threshold 25)» указывает на то, что функция RunImpl слишком «громоздкая» и сложная, с точки зрения когнитивной нагрузки на того, кто читает/поддерживает код. Анализатор ругается на высокую когнитивную сложность. Чем выше она, тем сложнее понимать и тестировать эту функцию.

Чтобы избавиться от этой ошибки (и реально упростить код), нужно уменьшить когнитивную сложность. На практике это означает:

  1. Разбить большую функцию на несколько более мелких

    • Перенести логические блоки в отдельные функции с понятными названиями (методами/процедурами/функциональными объектами).
    • Старайтесь, чтобы каждая «подфункция» выполняла одну конкретную задачу.
      Пример:
    cpp
    // Было (очень упрощённый пример): void RunImpl() { // ... 100+ строк логики ... // много if/else и циклов } // Стало: void RunImpl() { DoInitialization(); ProcessMainLogic(); Finalize(); } void DoInitialization() { // код инициализации } void ProcessMainLogic() { // основной блок, возможно тоже разбитый на подблоки // внутри тоже можно разбивать, если нужно } void Finalize() { // завершающие действия }

    Каждый блок может быть вынесен в отдельную функцию, что снизит вложенность в основной функции RunImpl.

  2. Уменьшить вложенность if/else, switch/case, циклов

    • Использовать ранний выход вместо глубоко вложенных блоков if.
    • Рассмотреть возможность упрощать сложные выражения в условии.
    • Вместо громоздкой конструкции if (...) { if (...) { if (...) {...} } } использовать «ранние return’ы» или комбинацию if (...) return;.
  3. Переосмыслить логику
    Иногда логика, ведущая к высокой когнитивной сложности, действительно может быть «подчищена»:

    • Свести подобные условия к более простым структурам или объединять похожие ветки if в одну, если это возможно.
    • Удалить/переписать «лишние» проверки, которые повторяются.
    • Посмотреть, не лучше ли использовать паттерны вроде «стратегий», «стейт-машин (машин состояний)» или «цепочек обязанностей», если у вас очень много случаев поведения.
  4. Проверить, не нужно ли вынести некоторые проверки в классы/структуры
    Если функция занимается всем сразу (подгрузкой данных, валидацией, обработкой, логированием, выводом, и так далее), то её можно декомпозировать:

    • Создать вспомогательные классы/структуры или отдельные компоненты, которые будут брать на себя часть логики.
    • Разделить код по ответственностям. Это улучшит читаемость и тестируемость.
  5. Использовать более высокий уровень абстракций
    Например, если вы часто и вручную обрабатываете одни и те же проверки (цикл, внутри куча if/else), возможно, вам помогут стандартные алгоритмы (например, std::find_if, std::any_of и т.д.) или паттерны функционального программирования (лямбда-функции, вложенные функции). Это позволяет сократить код и сделать его более декларативным, что в итоге снижает его когнитивную сложность.


Итог

Чтобы устранить ошибку «cognitive complexity of 135 (threshold 25)», главная мысль — разделить функцию на несколько более простых, уменьшить вложенность и переупорядочить логику таким образом, чтобы код стал более «линейным» и понятным. Обычно это включает вынос блоков кода в отдельные функции, удаление избыточных проверок, использование раннего выхода из функции при определённых условиях и общее упрощение конструкции.

Таким образом, вы и «погасите» предупреждение от анализатора (предупреждение превратилось в ошибку из-за ключа -warnings-as-errors), и сделаете код более поддерживаемым в долгосрочной перспективе.

Pergunta

narrowing conversion from 'double' to 'size_type' (aka 'unsigned long') [bugprone-narrowing-conversions,-warnings-as-errors]
36 | output_matrix_C_[(i * dimension) + j] +=

Pensamento

Identificando a causa

Estou mapeando a origem do erro de "conversion narrowing" ao usar variáveis do tipo double como índices de um array size_type. Hm, isso parece estar acontecendo na linha 36.

Ajustando tipos

Estou pensando em alinhar i, dimension e j como inteiros para garantir que a expressão (i * dimension) + j seja um inteiro. Isso elimina erros de "conversion narrowing" no array.

Ajustando tipos

Estou notando que converter dimension, i, j para um tipo inteiro resolveria o "conversion narrowing". Isso também atenderia ao esforço de manter a integridade dos índices do array.

Ajustando tipos

Estou redefinindo dimension, i, e j como inteiros para garantir índices de array válidos. Usar size_t é mais comum para essa aplicação, eliminando a conversão de tipo imprópria.

Ajustando o código

Estou ajustando para que dimension esteja em size_t ou inteiro e i, j sejam inteiros ou size_t. Isso evita conversões de tipo que podem levar a erros.

Ajustando índices

Estou trabalhando para garantir que os índices estejam sendo atribuídos corretamente em laços for, convertendo dimension e i, j para size_t se dimension for um double, minimizando erros de índice.

Pensou por 32 segundos

Resposta

Сообщение об ошибке вида:

text
narrowing 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 целое, в общем случае возможно потерять дробную часть или выйти за границы допустимого диапазона.


Как исправить

1. Использовать корректные целочисленные типы для индексов

Если dimension, i и j концептуально представляют целочисленные значения (размер матрицы, номер строки/столбца и т.п.), то лучшей практикой будет:

  1. Объявить их с целочисленным типом (например, int, long long, std::size_t и т.д.) — то есть избавиться от double в переменных, которые отражают дискретные индексы.
  2. Тогда выражение (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] += ...; } }

2. Если «double» действительно нужно

Бывают случаи, когда логика программы требует вычисления в double, а затем используется результат как индекс.
Тогда нужно:

  • Явно выполнить приведение типов (каст) с проверкой, что полученное значение не выходит за границы массива.
  • Например, сделать что-то вроде:
    cpp
    double doubleIndex = i * dimension + j; std::size_t index = static_cast<std::size_t>(doubleIndex); // Но тут важно убедиться, что doubleIndex >= 0 и <= размер_массива - 1 output_matrix_C_[index] += ...;
  • Однако в большинстве случаев это признак того, что лучше пересмотреть архитектуру и не использовать double для дискретных индексов, так как это может привести к скрытым ошибкам (особенно если doubleIndex окажется дробным).

3. Проверить логику и устранить избыточные преобразования

Иногда double «прокрадывается» по цепочке: результат вычислений (например, std::sqrt(…) или какие-нибудь рассчёты координат) пытаются напрямую использовать как индекс. Нужно убедиться, что это действительно индекс целочисленного массива, либо изменить структуру кода.


Итог

  1. Самое надёжное решение — хранить размерность матрицы (dimension), а также переменные i и j в целочисленных типах (например, size_t или int, в зависимости от ваших ограничений).
  2. Если же по бизнес-логике у вас обязательно должны быть double, то используйте явное приведение типов (с проверками), но обычно это менее предпочтительно для индексов.

В большинстве случаев достаточно заменить double dimension; на size_t dimension; и аналогично int i, j; или size_t i, j;, после чего ошибка «narrowing conversion» исчезнет.

Pergunta

narrowing conversion from 'double' to 'size_type' (aka 'unsigned long') [bugprone-narrowing-conversions,-warnings-as-errors]
36 | output_matrix_C_[(i * dimension) + j] +=

Pensamento

Pensou por dois segundos

Resposta

Ошибка «narrowing conversion from 'double' to 'size_type' (aka 'unsigned long')» означает, что в выражении (i * dimension) + j как минимум одна из переменных (i, dimension или j) имеет тип double, а результат вычисления используется как индекс (тип size_t). Компилятор (или статический анализатор) ругается, потому что это «сужающее» преобразование из double в целый тип: в общем случае дробная часть теряется, и программа потенциально может выйти за границы массива, если число не будет целым или окажется слишком большим/отрицательным.

Как исправить

1. Использовать целочисленные типы для индексов

Самое частое и правильное решение — заменить типы переменных, хранящих индексы и размерности (например, 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» полностью.

2. Делать явное приведение с проверкой (если правда нужен double)

Если у вас по логике одна из переменных действительно должна быть double, а затем её результат использоваться как индекс, то нужно:

  1. Явно привести значение к целому типу.
  2. Убедиться, что перед тем, как писать в output_matrix_C_[...], вы проверили, что приводимое значение не выходит за границы массива и не содержит «нецелую» часть (или округлили её надлежащим образом).

Пример (упрощённый):

cpp
double 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>(...)) с проверкой корректности — но в большинстве случаев правильнее пересмотреть архитектуру, чтобы избежать дробных типов для индексов.

Pergunta

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;
}

Разбей эту функцию на несколько небольших функций, разбиение должно быть осмысленным

Pensamento

Reorganizando funções

Estou analisando a função CannonsAlgorithmMPITaskParallel::RunImpl() para dividir em funções menores, como "PerformSingleProcessComputation" e "PerformCannonAlgorithmComputation", tornando o código mais modular e eficiente.

Desmembrando e explicando

Primeiro, eu verifico se é um caso trivial. Caso contrário, rodo o algoritmo de Cânnons em etapas como configuração de bloco, distribuição de dados, multiplicação local e coleta de resultados.

Mantendo alinhamento

Estou destacando a importância de preservar a lógica do código atual, integrando o objeto "world_" do Boost.MPI e os métodos privados para manter a integridade e funcionalidade.

Ajustando a abordagem

Estou reescrevendo o código para for loops, mantendo a lógica do código atual, organizando a função em sub-blocos menores e potencialmente renomeando variáveis locais para parâmetros de função ou membros da classe.

Organizando funções

Estou deixando a função RunImpl() mais clara, dividindo-a em IsTrivialCase(), HandleTrivialCase() e PerformCannonAlgorithm(), mantendo a lógica original e a funcionalidade.

Dando mais dimensão

Estou pegando a função PerformCannonAlgorithm() e dividindo-a em DistributeDataIfRoot(), ReceiveDataIfNotRoot(), LocalMultiply(), PerformCannonShiftPhases() e GatherResults(), mantendo a lógica original.

Razonado sobre el refactorizado de la función RunImpl por 22 segundos

Resposta

Ниже приведён пример, как можно логически разбить ваш текущий метод RunImpl() на несколько более мелких функций, чтобы снизить его когнитивную сложность и упростить чтение кода. Обратите внимание, что код разбит по ключевым этапам алгоритма Каннона и «тривиальной» ветви, но сохранена общая логика. Названия функций можно менять по своему усмотрению.

Предположим, что мы находимся внутри класса:

cpp
namespace 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 с разделением на шаги

cpp
bool deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::RunImpl() { // Проверяем, не &laquo;тривиальный&raquo; ли случай if (IsTrivialCase()) { HandleTrivialCase(); } else { PerformCannonAlgorithm(); } return true; // По логике возвращаете true }

1. Определение «тривиального» случая

cpp
bool 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 ); }

2. Обработка «тривиального» случая (обычный умножитель)

cpp
void 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]; } } } } }

3. Основной алгоритм Каннона

cpp
void 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(); }

Далее каждый из под-методов:

3.1. Инициализация и рассылка параметров

cpp
void deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::InitializeAndBroadcastParams() { if (world_.rank() == 0) { // dimension_ = &radic;(размер входной матрицы) dimension_ = static_cast<unsigned short>( std::sqrt(static_cast<unsigned short>(input_matrix_A_.size())) ); // block_rows_columns_ = &radic;(число процессов) 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); }

3.2. Отправка (DistributeDataIfRoot) локальных блоков (rank=0)

cpp
void deryabin_m_cannons_algorithm_mpi::CannonsAlgorithmMPITaskParallel::DistributeDataIfRoot() { // Сначала копируем &laquo;свои&raquo; данные (блок (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 }

3.3. Получение (ReceiveDataIfNotRoot) локальных блоков (rank != 0)

cpp
void 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_); } }

3.4. Умножение локальных блоков (один раз)

cpp
void 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]; } } } }

3.5. Многократные «сдвиги» (phases) алгоритма Каннона

cpp
void 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]; } } } } }

3.6. Сбор (Gather) результатов в rank=0

cpp
void 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_); } } } }

Комментарии и рекомендации

  1. Сигнатуры функций
    Вы можете сделать их приватными методами класса (как в примере), либо вынести часть логики в отдельные свободные функции (если это больше подходит вашей архитектуре).

  2. Типы переменных
    Обратите внимание, что у вас часто используется unsigned short и приведение к нему из double. Для индексов (размеров массивов и т.п.) обычно более надёжно использовать size_t или как минимум unsigned int. Это поможет избежать лишних ошибок и «narrowing conversion».

  3. Упрощение условия IsTrivialCase()

    • Проверки вида world_.size() != pow(sqrt(...), 2) часто можно переписать так, чтобы избежать повторной операции sqrt и потенциальных проблем с double->int. Например, можно целочисленно проверить, является ли world_.size() квадратом целого числа.
  4. Циклы for вместо while
    В оригинальном коде много while (i != dimension). Это можно заменить на for (i = 0; i < dimension; ++i), что обычно улучшает читаемость и упрощает контроль за счётчиком.

  5. Уменьшение глубины вложенности
    Часть if/else логики при отправке блоков может быть ещё больше упрощена, если вы заранее вычислите нужный destination_proc и используете один вызов world_.send(). Но это уже зависит от ваших требований к читабельности и точности повторения оригинальной логики.


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

Pergunta

error: function 'IsTrivialCase' should be marked [[nodiscard]] [modernize-use-nodiscard,-warnings-as-errors]
38 | bool IsTrivialCase() const;
| ^
| [[nodiscard]]

Pensamento

Pensou por dois segundos

Resposta

Сообщение

error: function 'IsTrivialCase' should be marked [[nodiscard]] [modernize-use-nodiscard,-warnings-as-errors]

означает, что статический анализатор (или компилятор с включённым предупреждением -Werror) рекомендует (и в данном случае требует) использовать атрибут [[nodiscard]] для функции, возвращающей значение. Это нужно для того, чтобы при вызове такой функции компилятор мог предупредить, если результат вызова игнорируется. Таким образом повышается безопасность кода: разработчик явно видит, что результат вызова важен для логики и не должен быть пропущен.

Как исправить

Просто добавьте [[nodiscard]] (C++17+) в объявлении (и желательно определении) функции IsTrivialCase(). Например:

cpp
class CannonsAlgorithmMPITaskParallel { public: // ... private: [[nodiscard]] bool IsTrivialCase() const; // ... };

Либо, если определяете метод прямо в классе, то:

cpp
class CannonsAlgorithmMPITaskParallel { private: [[nodiscard]] bool IsTrivialCase() const { // ... } };

Если вы используете более ранний стандарт языка, где нет [[nodiscard]], можно заменить его на макрос, например __attribute__((warn_unused_result)) (для GCC/Clang), но в современных компиляторах C++17+ [[nodiscard]] уже широко поддерживается.

Compartilhar esta Q&A