Общая ситуация
Приложение, которое чрезвычайно интенсивно использует как пропускную способность, так и использование ЦП, и использование графического процессора, должно передавать около 10-15 ГБ в секунду с одного GPU на другой. Он использует API DX11 для доступа к графическому процессору, поэтому загрузка на GPU может происходить только с буферами, которым требуется сопоставление для каждой отдельной загрузки. Загрузка происходит в кусках по 25 Мбайт за раз, а 16 потоков одновременно записывают буферы в сопоставленные буферы. Там не так много, что можно сделать по любому из этого. Фактический уровень записи concurrency должен быть ниже, если бы не следующая ошибка.
Это мощная рабочая станция с 3 GPU Pascal, high-end процессором Haswell и четырехканальной ОЗУ. На аппаратном уровне не так много улучшений. Он запускает настольную версию Windows 10.
Актуальная проблема
Как только я пропускаю загрузку процессора на уровне 50%, что-то в MmPageFault()
(внутри ядра Windows, вызываемого при обращении к памяти, которая была отображена в ваше адресное пространство, но еще не была зафиксирована ОС) ломается ужасно, а оставшаяся 50% загрузка процессора расходуется на спин-замок внутри MmPageFault()
. Процессор становится 100% использованным, и производительность приложения полностью ухудшается.
Я должен предположить, что это связано с огромным объемом памяти, который требуется распределять процессу каждую секунду и который также полностью не отображается из процесса каждый раз, когда буфер DX11 не отображается. Соответственно, на самом деле это тысячи вызовов MmPageFault()
в секунду, повторяющихся последовательно, когда memcpy()
записывается последовательно в буфер. Для каждой отдельной незарегистрированной страницы.
Одна загрузка процессора выходит за пределы 50%, оптимистичная прямая блокировка в ядре Windows, защищающая управление страницей, полностью ухудшает производительность.
Вопросы
Буфер распределяется драйвером DX11. Ничто не может быть изменено в отношении стратегии распределения. Использование другого API памяти и особенно повторного использования невозможно.
Звонки в API DX11 (отображение/развязка буферов) происходит из одного потока. Фактические операции копирования потенциально происходят многопоточными по большему количеству потоков, чем в системе есть виртуальные процессоры.
Уменьшение требований к пропускной способности памяти невозможно. Это приложение в режиме реального времени. Фактически, в настоящее время жестким ограничением является PCIe 3.0 16x пропускная способность основного графического процессора. Если бы я мог, мне уже нужно было продвигаться дальше.
Избежать многопоточных копий невозможно, так как существуют независимые очереди производителей-потребителей, которые нельзя объединить тривиально.
Ухудшение производительности спин-блокировки, по-видимому, настолько редки (потому что прецедент так далеко продвигает его), что в Google вы не найдете ни одного результата для имени функции блокировки спина.
Выполняется обновление до API, который дает больший контроль над сопоставлениями (Vulkan), но не подходит для краткосрочного исправления. Переключение на лучшее ядро ОС в настоящее время не является вариантом по той же причине.
Снижение нагрузки на процессор также не работает; слишком много работы, которая должна быть выполнена иначе, чем (обычно тривиальная и недорогая) буферная копия.
Вопрос
Что можно сделать?
Мне нужно значительно уменьшить количество отдельных страниц. Я знаю адрес и размер буфера, который был отображен в мой процесс, и я также знаю, что память еще не была зафиксирована.
Как я могу гарантировать, что память будет совершена с наименьшим количеством транзакций?
Экзотические флаги для DX11, которые предотвратили бы отмену выделения буферов после разворачивания, API Windows для принудительной фиксации в одной транзакции, почти все приветствуется.
Текущее состояние
// In the processing threads
{
DX11DeferredContext->Map(..., &buffer)
std::memcpy(buffer, source, size);
DX11DeferredContext->Unmap(...);
}