Когда memcpy работает быстрее, чем простое повторное присваивание?

Предположим, что кто-то хочет сделать копию массива, объявленного как

DATA_TYPE src[N];

Всегда ли memcpy так же быстро или быстрее, чем следующий фрагмент кода, независимо от того, что такое DATA_TYPE и количество элементов массива?

DATA_TYPE dest[N];

for (int i=0; i<N; i++)
    dest[i] = src[i];

Для маленького типа, такого как char, и большого N мы можем быть уверены, что memcpy быстрее (если только компилятор не заменит цикл вызовом memcpy). Но что, если тип больше, например double, и/или количество элементов массива мало?

Этот вопрос возник у меня при копировании множества массивов double, каждый из которых состоит из 3 элементов.

Я не нашел ответа на свой вопрос в ответе на другой вопрос, упомянутый Вольстадом в комментариях. Принятый ответ на этот вопрос, по сути, говорит: «Оставьте это на усмотрение компилятора». Это не тот ответ, который я ищу. Тот факт, что компилятор может оптимизировать копирование памяти, выбрав одну альтернативу, не является ответом. Почему и когда один из вариантов быстрее? Возможно, компиляторы знают ответ, но разработчики, включая разработчиков компиляторов, не знают!

Отвечает ли это на ваш вопрос? memcpy против присваивания в C

wohlstad 23.04.2022 09:36

@wohlstad, спасибо. Я читаю это.

apadana 23.04.2022 09:38

Используйте www.godbolt.org для проверки альтернативных кодов. Скорее всего, оптимизатор создаст почти одинаковую сборку для обоих.

hyde 23.04.2022 09:38

@wohlstad, принятый ответ на этот вопрос, по сути, говорит: «Оставьте это на усмотрение компилятора». Это не тот ответ, который я ищу.

apadana 23.04.2022 09:48

@wohlstad, тот факт, что компилятор может оптимизировать копирование памяти, выбрав одну альтернативу, не является ответом. Почему и когда один из вариантов быстрее? Возможно, компиляторы знают ответ, но разработчики, включая разработчиков компиляторов, не знают!

apadana 23.04.2022 09:54

Во многих реализациях memcpy есть оптимизации для копирования элементов размером слова на границах слов. Например, если вы DATA_TYPE были байтом, то ваш ручной цикл, предполагающий отсутствие оптимизации, будет выполнять N итераций. На 64-битной машине memcpy может выполнить всю работу за N/8 итераций — копирование более 64-бит за раз.

selbie 23.04.2022 10:00

Какой ответ находятся вы ищете? Нет серебряной пули, которая «лучше». Задействованные переменные слишком разнообразны и зависят от ситуации. Единственный способ узнать определенный — это создать свой оптимизированный asm, подсчитать тактовые циклы и остановки конвейера, включая потенциальные ошибки прогнозирования ветвлений, локальность кеша и, прежде всего, мера.

WhozCraig 23.04.2022 10:02

@WhozCraig, правильно, но обычно есть общие правила.

apadana 23.04.2022 10:19

"для мелкого типа... мы можем быть уверены, что memcpy быстрее" - Нет, нельзя.

Cheatah 23.04.2022 10:22

Общее правило таково: если у вас нет особой потребности в оптимизации, пишите код так, чтобы его было как можно проще читать и поддерживать. Когда возникает потребность в оптимизации, решение сильно зависит от целевой системы.

nielsen 23.04.2022 10:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку memcpy является библиотечной функцией, она полностью зависит от реализации библиотеки, насколько она эффективна на самом деле, и окончательный ответ невозможен.

Тем не менее, любая предоставленная стандартная библиотека, вероятно, будет сильно оптимизирована и может даже использовать специфические аппаратные функции, такие как передача DMA. Принимая во внимание, что производительность вашего цикла кода будет варьироваться в зависимости от настроек оптимизации, поэтому, вероятно, она будет работать намного хуже в неоптимизированных сборках отладки.

Еще одно соображение заключается в том, что производительность memcpy() не зависит от типа данных и, как правило, детерминированный, тогда как производительность вашего цикла, вероятно, будет зависеть от DATA_TYPE или даже от значения N.

Как правило, я ожидаю, что memcpy() будет оптимальным и быстрым или таким же быстрым, как цикл присваивания, и, безусловно, более последовательным и детерминированным, не зависящим от конкретных настроек компилятора и даже от используемого компилятора.

В конце концов, единственный способ сказать это — измерить его для вашей конкретной платформы, набора инструментов, библиотеки и вариантов сборки, а также для различных типов данных. В конечном счете, поскольку вам пришлось бы измерять его для каждой комбинации использования до знать, если бы он был быстрее, я полагаю, что это, как правило, пустая трата времени и представляет только академический интерес — используйте библиотеку — не только для производительности и согласованности, но и для ясность и ремонтопригодность.

Вы бы сами использовали memcpy для копирования массива из 3 double?

apadana 23.04.2022 10:30

@apadana Возможно, не потому, что для этого накладные расходы на вызов функции были бы значительными, но я бы, вероятно, не использовал цикл по той же причине. Хотя оптимизатор может развернуть такой цикл, это ни в коем случае не является данностью, и развертывание его самостоятельно приведет к повышению производительности в неоптимизированных сборках.

Clifford 23.04.2022 10:37

Хороший ответ. Теперь, когда вы упомянули DMA, забавно, что передача DMA может - в зависимости от платформы - быть медленнее, чем, например. memcpy(), но тем временем это позволит процессору заниматься другими делами. В таком случае «быстрее» зависит от того, может ли приложение использовать эту разгрузку.

nielsen 23.04.2022 10:44

@apadana: в общем, я бы не стал париться по мелочам. Такие микрооптимизации редко бывают продуктивными или необходимыми, и их трудно обобщить. Единственный раз, когда я помню, что мне нужно было беспокоиться о подобных вещах, это код DSP в системе жесткого реального времени на основе микроконтроллера. И там я протестировал и профилировал различные реализации, чтобы определить лучшее решение для этого конкретного приложения на этой конкретной платформе ant toolchain.

Clifford 23.04.2022 10:49

@nielsen: да, на практике я сомневаюсь, что реализация будет использовать DMA, поскольку она менее детерминирована и не переносима. Также для небольших копий время установки будет непомерно высоким. Вместо этого могут быть предоставлены специализированные функции копирования памяти DMA.

Clifford 23.04.2022 10:54

Другие вопросы по теме