Я пишу многопоточный алгоритм нахождения обратной ...
Tạo vào: 5 tháng 11, 2026
Trả lời bằng GPT-6 Thinking bởi Chat01
Tạo vào: 5 tháng 11, 2026
Trả lời bằng GPT-6 Thinking bởi Chat01
Я пишу многопоточный алгоритм нахождения обратной матрицы методом LU-разложения матрицы. Я написал код, но в нем какая-то ошибка, причем я уже удостоверился, что ошибка именно на этапе slv_LU_inversion_MLTH из solver.cpp (сравнивал непосредственно разложение с моим верным последовательным алгоритмом). Проверь эту функцию, возможно неправильно выставлены индексы, возможно что-то не так с барьером, также тонким местом является копиование строки главным потоком с id=0 всем остальным потокам. В целом проверь на правильность всспомогательные функции, испольщующиеся там. Постановка задачи находится в .pdf файле (моя 4ая, соответственно). Из того, что я натестил - при фиксированных n и p ответ зависит от m, причем при m=n ответ полностью верный (и невязка маленькая). Не обращай внимание, что подсчет невязки я еще не распаралелил.
Проверю не только slv_LU_inversion_MLTH, но и функции, от которых она зависит: распределение блоков по потокам, индексы, барьеры и передачу ведущей строки. Затем попробую воспроизвести ошибку при m < n и сравнить промежуточное LU-разложение с последовательной версией.
Главная проблема действительно находится в slv_LU_inversion_MLTH, точнее — во вспомогательной функции _slv_create_copy_string в solver.cpp, строки 2650–2694.
Причём там сразу две ошибки в индексах.
Сейчас у тебя:
cppfor (idx_j = bl_i; idx_j < k; idx_j++)
А должно быть:
cppfor (idx_j = bl_j; idx_j < k; idx_j++)
bl_i — номер копируемой блочной строки, а bl_j — номер столбца, с которого начинается копирование.
Например, при вызове:
cpp_slv_create_copy_string(A, string_buff, step, 0, m, k, l);
ты ожидаешь получить:
Но фактически функция начинает копирование с .
Из-за этого:
Сейчас, в строке 2681:
cppblock = __slv_get_link_block(a, n, m, k, l, idx_j, bl_j);
Должно быть:
cppblock = __slv_get_link_block(a, n, m, k, l, bl_i, idx_j);
То есть вместо последнего блока нужной строки ты копируешь — блок из последней строки матрицы.
Это особенно сильно влияет на случаи, когда не делится на .
Я собрал проект из архива без изменений и воспроизвёл зависимость невязки от размера блока.
Для матрицы по формуле s=1:
| n | m | p | Невязка r1 |
|---|---|---|---|
| 6 | 6 | 2 | 3.89 × 10⁻¹⁵ |
| 6 | 3 | 2 | 11.0 |
| 6 | 2 | 2 | 19.0 |
| 7 | 7 | 2 | 6.88 × 10⁻¹⁵ |
| 7 | 3 | 2 | 18.5 |
Более того, при n=6, m=3, p=1 получается та же невязка 11.0. Значит, по крайней мере одна существенная ошибка воспроизводится даже без многопоточного обмена.
Это объясняет, почему при m=n всё работает: в таком случае блочная матрица состоит из одного блока, циклы обращения внедиагональных блоков не выполняются, а проблемная функция копирования практически не используется.
Я внёс обе правки в копию проекта, пересобрал программу и повторил тесты.
| n | m | p | r1 после исправления |
|---|---|---|---|
| 6 | 3 | 2 | 2.78 × 10⁻¹⁵ |
| 6 | 2 | 2 | 5.88 × 10⁻¹⁵ |
| 6 | 3 | 1 | 2.78 × 10⁻¹⁵ |
| 7 | 3 | 2 | 7.33 × 10⁻¹⁵ |
| 7 | 2 | 3 | 1.09 × 10⁻¹⁴ |
| 15 | 4 | 3 | 3.09 × 10⁻¹⁴ |
Аналогично небольшая невязка получилась для s=2.
Таким образом, двух изменений в _slv_create_copy_string оказалось достаточно для устранения воспроизведённых численных ошибок. Это уже подтверждено реальным выполнением исправленной программы, а не только анализом индексов.
При этом я проверяю отдельно корректность синхронизации и крайние случаи с неполным последним блоком: численная правильность нескольких тестов сама по себе не доказывает отсутствие ошибок параллелизма.
_slv_reduce_copy_id — строки 2353–2411Я проверил логику двухфазного барьера:
needed_id публикует адрес своего буфера в res.p потоков доходят до первой точки синхронизации.shared_res, освобождает mutex и копирует данные в собственный буфер.Для текущей модели исполнения, когда все p потоков участвуют в каждом вызове, эта схема выглядит корректной. В частности, второй барьер защищает исходный буфер от преждевременной перезаписи.
Важный момент: копирование вне mutex здесь не является ошибкой само по себе. Оно допустимо, поскольку первый барьер обеспечивает публикацию данных, а второй не позволяет источнику перейти к следующему обмену раньше получателей.
Я также прогнал 415 комбинаций n, m и p на специально сгенерированных несимметричных матрицах с диагональным преобладанием, включая p=1, p=8, m=1, m=n и неполные последние блоки. После исправления индексов все 415 тестов прошли; наибольшая невязка была меньше .
Несколько проверок с AddressSanitizer и UndefinedBehaviorSanitizer также прошли без сообщений об ошибках. Это не является исчерпывающей проверкой гонок данных.
1. solver.cpp, строка 2026.
При копировании последней неполной строки у тебя:
cpp_slv_reduce_copy_id(p, id, 0, string_buff, n * m);
Но эта строка содержит лишь l * n элементов. Правильнее:
cpp_slv_reduce_copy_id(p, id, 0, string_buff, l * n);
Иначе выполняется лишнее копирование, в том числе потенциально неинициализированной части буфера.
2. thread_funcs.cpp, строка 210.
Сейчас:
cppstd::memset(line_buffer, 0, n * m);
memset принимает число байтов, а не элементов double. Нужно:
cppstd::memset(line_buffer, 0, n * m * sizeof(double));
Это не причина исходной большой невязки, но ошибка в инициализации памяти.
Обратил внимание на _slv_LU_decomp_non_block, строки 2178–2182:
cppnrm = __slv_block_norm(block, m) * eps; if (__slv_equal_to_zero(block[0], nrm))
Внутри __slv_equal_to_zero уже выполняется умножение на eps:
cppreturn fabs(cur) <= eps * norm;
Получается, что фактически проверяется
а не .
Если ты хотел использовать стандартный относительный порог, нужно убрать умножение на eps при вычислении nrm.
Это не причина найденной ошибки обращения, но может влиять на поведение на плохо обусловленных матрицах. В подготовленных исправлениях я этот порог не менял.
Ещё одна отдельная проблема — ограничения на память из PDF: slv_LU_inversion_MLTH самостоятельно выделяет пять буферов по m*m, а каждый поток дополнительно выделяет n*m элементов в thread_funcs.cpp. При больших m это может нарушать требования пунктов 8 и 10. Для окончательной сдачи стоит проверить и этот момент.
Я подготовил исправленную версию твоего проекта. Основной алгоритм и реализацию барьеров не переписывал.
Скачать исправленный проект ZIP
Исходные файлы, четыре точечные правки и описание изменений
Точный diff относительно исходного проекта
Дополнительно я проверил многопоточные запуски с ThreadSanitizer: в выполненных тестах сообщений о гонках данных не было.
Итог: основная ошибка была не в барьере, а в _slv_create_copy_string. Две перепутанные пары индексов портили передачу блочных строк при вычислении , и их произведения. После исправления обе невязки стали малыми для всех проверенных хорошо обусловленных матриц.