В интернете можно найти описание проблем, связанных с использованием DMA для стандартной функции копирования данных из памяти в память, например тут:
Linux – DMA memcpy operation in Linux
Давайте попробуем разобраться, как можно использовать интерфейс к DMA для реализации стандартной операции копирования memcpy и есть ли в этом смысл.
Реализация memcpy в Линуксе через обращение к DMA контроллеру могла бы выглядеть вот так:
void memcpyWithDma (u16* dest, u16* src, size_t len)
{
dma_cookie_t cookie = dma_async_memcpy_buf_to_buf(chan, dest, src, len);
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
{
dma_sync_wait(chan, cookie);
}
}
Это описание: Linux – DMA memcpy operation in Linux начинается с демонстрации кода, в котором измеряется и логируется время исполнения реализации копирования с использованием DMA и время исполнения такого же копирования с помощью функции memcpy().
Вот этот код:
void foo ()
{
int index = 0;
dma_cookie_t cookie;
size_t len = 0x20000;
ktime_t start, end, end1, end2, end3;
s64 actual_time;
u16* dest;
u16* src;
dest = kmalloc(len, GFP_KERNEL);
src = kmalloc(len, GFP_KERNEL);
for (index = 0; index < len/2; index++)
{
dest[index] = 0xAA55;
src[index] = 0xDEAD;
}
start = ktime_get();
cookie = dma_async_memcpy_buf_to_buf(chan, dest, src, len);
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
{
dma_sync_wait(chan, cookie);
}
end = ktime_get();
actual_time = ktime_to_ns(ktime_sub(end, start));
printk("Time taken for function() execution dma: %lld\n",(long long)actual_time);
memset(dest, 0 , len);
start = ktime_get();
memcpy(dest, src, len);
end = ktime_get();
actual_time = ktime_to_ns(ktime_sub(end, start));
printk("Time taken for function() execution non-dma: %lld\n",(long long)actual_time);
}
Наша memcpyWithDma() это копия инлайн кода обращения к DMA из этого примера, оформленная как С-функция.
Вопросы
Автор этого кода удивляется:
что время копирования с обращениями к DMA оказывается больше чем время копирования старым дедовским способом с помощью функции memcpy();
просит подтвердить что это корректный способ использования DMA функциональности.
интересуется тем какие есть методы измерения нагрузки на процессор (производительности)
спрашивает возможно ли выполнение DMA операции на уровне пользовательских приложений, так как тест, который он написал для сравнения производительности двух реализаций копирования, он написал внутри модуля ядра.
Далее приводится ответ эксперта, который вполне совпадает с моим пониманием вопроса. Собственно этот ответ я и хочу, здесь, адаптировать на язык Пушкина и Толстого с некоторыми моими дополнениями.
Что же отвечает эксперт.
Эксперт говорит, что вопрос или вопросы не совсем корректные, поэтому он не может ответить прямо на перечисленные вопросы, но излагает некоторые аспекты теории которые должны помочь сформировать правильное понимание проблем связанных с практическим использованием функциональности DMA.
1. Во первых эксперт признал что такая реализация копирования данных с использованием DMA является корректной, то есть наша функция memcpyWithDma() тоже вполне могла бы копировать данные также, как это делает стандартная функция memcpy();
2. Тем не менее существует фундаментальная разница между методом копирования посредством DMA и стандартным методом, который реализован в функции memcpy(). Эта разница заключается в том, что DMA копирование не предполагает прямого увеличения производительности за счет простого ускорения копирования. Есть два аспекта которые определяют фундаментальную (принципиальную) разницу этих двух методов копирования:
a. Использование DMA не изменяет состояние кэша (при том, что при использовании обычного старого memcpy кэш заполняется адресами памяти, участвующей в копировании)
b. DMA копирование выполняется внешним аппаратным модулем - контроллером и процессор остается свободным во время такого копирования, и может выполнять код, который не имеет отношения к копированию, хотя в нашей реализации memcpyWithDma() мы ни как не используем эту возможность. У нас все-таки выполняется код который СВЯЗАН с текущим копированием самым непосредственным образом:
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
{
dma_sync_wait(chan, cookie);
}
Здесь мы просто ждем завершения операции, при этом вызов dma_sync_wait()
на самом деле, все таки, сэкономит нам производительность при определенных условиях. Если приложение, в котором вызывается эта функция, является многопоточным(multithreaded), этот вызов деактивирует текущий поток(thread), который выполнил этот вызов, и позволит исполняться другим потокам приложения параллельно с текущей, инициированной здесь, операцией копирования данных. Таким образом, учитывать время после вызова функции dma_sync_wait()
до возврата из нее, как время копирования не корректно с точки зрения измерения времени использования процессора, хотя это время конечно должно учитываться с точки зрения длительности копирования данных. То есть данные могут копироваться дольше, но при этом процессор фактически не будет занят во время копирования. Вообще говоря, это ставит перед разработчиком достаточно сложную дилемму, что предпочесть: реальную скорость копирования или возможность параллельной работы процессора во время копирования.
3. В этом пункте эксперт говорит, что: «Учитывая содержание подпункта (a) из предыдущего пункта, бессмысленно использовать операции DMA для чего-либо меньшего, чем размер кэша процессора, то есть десятков мегабайт. Обычно это делается для быстрой обработки потока вне ЦП, т.е. перемещения данных, которые в любом случае были бы произведены / потреблены внешними устройствами, такими как быстрые сетевые карты, оборудование для потоковой передачи / захвата / кодирования видео и т.д.» Честно говоря я бы не стал так безапеляционно увязывать использование DMA с параметрами кэша процессора, потому что существуют процессоры, которые не имеют кэша, но уже используют DMA. Наверно тут важно обратить внимание, на то, что DMA обычно используется в связке с каким-то внешним устройством или встроенным переферийным юнитом специальной функции процессора.
4. В этом пункте эксперт говорит о том, что некорректно сравнивать синхронные и асинхронные операции с точки зрения абсолютного и универсального времени. Время может оцениваться по-разному с точки зрения разных аспектов работы и/или использования разных компонент системы. Речь идет примерно о том же, о чем я написал выше в контексте того является ваше приложение многопоточным (multithreaded) или нет. Плюс эксперт указывает на то, что многопоточные системы вносят случайную ошибку при таком способе измерения, который используется в примере, так как переключение потоков происходит случайным образом с точки зрения пользователя системных функций.
5. Этот пункт по сути повторяет тезисы предыдущего пункта, однозначно указывая на то, что использование функции ktime_get() для измерений в целях сравнительного анализа совершенно неприемлемо, так как является очень не точным, особенно в случае такой короткой операции как то копирование, которое анализируется в примере.
6. Измерение "тиков" для современных процессоров также в некотором роде бессмысленно, указывает эксперт, хотя вы можете использовать инструменты конкретного производителя процессоров, такие как Intel VTune. Я бы добавил, что измерение «тиков» для современных процессоров бессмысленно до тех пор, пока вы не выяснили с какой точностью воспроизводятся ваши результаты. Иногда это сделать достаточно просто, достаточно лишь собрать статистику по нескольким измерениям.
7. "Использование операций копирования DMA на уровне приложения довольно бессмысленно – по крайней мере, я не могу придумать ни одного сценария, имеющего практический смысл, когда это стоило бы затраченных усилий" – по мнению эксперта, и я с ним здесь абсолютно согласен. "Это не обязательно быстрее, и, что более важно, я серьезно сомневаюсь, что узким местом производительности вашего приложения является копирование данных в памяти. Чтобы это было так, вы, как правило, должны делать все остальное быстрее, чем обычное копирование данных в памяти, и я действительно не могу придумать ничего на уровне приложения, что было бы быстрее, чем memcpy", ведь любая операция процессора так или иначе требует доступа к памяти, а значит является по определению боле сложной операцией чем тривиальное копирование. "И если мы говорим о взаимодействии с каким-либо другим устройством обработки данных вне центрального процессора, то это автоматически не уровень приложения", это уровень драйверов или уровень модулей ядра системы.
8. Как правило, производительность копирования данных в памяти (из одной физической локации памяти в другую физическую локацию) ограничена скоростью работы памяти, т.е. тактовой частотой и таймингами. Вы не получите никакого чудесного повышения производительности по сравнению с обычным memcpy хотя бы потому, что memcpy, выполняемый на процессоре, достаточно скоростной, поскольку процессор обычно работает с тактовой частотой в 3x-5x-10x выше, чем память (чем шина доступа к памяти).
9. Дополнительно стоит отметить что DMA работает с памятью напрямую, минуя подсистему MMU и кэш. Это требует специального способа выделения памяти который описан в предыдущей статье Выделение памяти для DMA в Linux, а если вы после копирования собираетесь использовать скопированные данные из памяти которую, до этого использовал или собирается использовать DMA контроллер вы должны выполнить какие-то аппаратно-зависимые функции для синхронизации данных в памяти, с данными, которые процессор или DMA контроллер будет читать из кэш памяти. Это могут быть функции подобные следующим:
void ResetDataCache(void* address, int len);
чтобы кэш «забыл» адреса указанной области и при следующем обращении выполнил чтение из физической памяти
void WriteBackDataCache(void* address, int len);
чтобы гарантированно сохранить данные из кэш памяти в физическую память в момент исполнения этой функции.
Некоторые архитектуры позволяют настроить определенные области памяти как «не кэшируемые», что позволяет исключить необходимость управления памятью для через кэш, но это можно делать в случае если у вас не ведется никакой вычислительной работы с данными в указанных регионах памяти, иначе пострадает скорость выполнения такой вычислительной работы.
Выводы
Наверно главное, что нужно вынести из этой статьи, это то что теоретически аналог функции memcpy реализованный через DMA может существовать, но с практической точки зрения он будет уступать по эффективности старой доброй, можно сказать классической реализации memcpy. Более того, такая реализация на основе DMA накладывает ряд ограничений и/или требований на способ выделения памяти, способ использования памяти до и после копирования. Фактически такая реализация всегда будет оставаться аппаратно-зависимой и навсегда станет проблемой пользовательского приложения.
P.S.: никак не могу сосредоточиться на изложении своего примера практического использования DMA в реальном девайсе, надеюсь еще одна вводная статья позволит мне собраться с мыслями.