2008-11-06 7 views
9

У нас есть машины Core2 (Dell T5400) с XP64.Различия производительности memcpy между процессами 32 и 64 бит

Мы наблюдаем, что при выполнении 32-битных процессов производительность memcpy составляет 1.2GByte/s; однако memcpy в 64-битном процессе достигает около 2,2 ГБ/с (или 2,4 ГБ/с с memcpy от CRM-компилятора Intel). В то время как первоначальная реакция может состоять в том, чтобы просто объяснить это , поскольку из-за более широких доступных регистров в 64-битном коде мы видим, что наш собственный memcpy-подобный код сборки SSE (который должен использовать широкую нагрузку 128 бит -страницы независимо от 32/64-битности процесса) демонстрируют аналогичные верхние пределы для ширины полосы пропускания, которую она достигает.

Мой вопрос: что это такое на самом деле из-за? Должны ли 32-битные процессы перепрыгнуть через некоторые дополнительные WOW64-обручи, чтобы добраться до ОЗУ? Это что-то , чтобы делать с TLB или prefetchers или ... что?

Спасибо за понимание.

Также поднят на Intel forums.

+0

Вы говорите, что ваш код SSE также в два раза быстрее в родной 64-битном режиме, чем в WOW64? Вы сравнили это на 32-разрядной версии XP, чтобы увидеть, влияет ли WOW64 на производительность? – bk1e 2008-11-06 17:07:06

+0

Да, это точно. 32-битный тест ОС - отличное предложение ... но, к сожалению, у нас нет эквивалентного H/W с 32-битной ОС! Я надеялся, что кто-нибудь скажет мне, является ли WOW64 проблемой или нет. Посмотрите на получение 32-битной установки. – timday 2008-11-07 13:30:24

ответ

3

Конечно, вы действительно должны смотреть на фактические машинные инструкции, которые исполн внутри самого внутреннего цикла memcpy, введя машинный код с отладчиком. Все остальное - просто спекуляция.

Моя проблема заключается в том, что она, вероятно, не имеет ничего общего с 32-битным и 64-битным по себе; я предполагаю, что более быстрая библиотечная процедура была написана с использованием невременных хранилищ SSE.

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

Я не знаю библиотеки CRT компилятора Intel, так что это всего лишь предположение. Нет никакой особой причины, по которой 32-битный libCRT не может сделать то же самое, но ускорение, которое вы цитируете, находится в фокусе того, что я ожидаю, просто преобразуя инструкции movdqa в movnt ...

Поскольку memcpy is не делая никаких вычислений, он всегда связан тем, насколько быстро вы можете читать и писать память.

1

У меня вопрос о том, что 64-битные процессы используют собственный 64-разрядный размер памяти процессора, который оптимизирует использование шины памяти.

8

Я думаю, что следующее может объяснить:

Чтобы скопировать данные из памяти в регистр и обратно в память, вы

mov eax, [address] 
mov [address2], eax 

Это перемещает 32 бита (4 байта) от адреса ADDRESS2 , То же самое с 64 бит в 64-битном режиме

mov rax, [address] 
mov [address2], rax 

Это перемещает 64 бит, 2 байта, от адреса до address2. «mov», независимо от того, имеет ли он 64-битное или 32-разрядное значение латентность 0,5 и пропускную способность 0,5 согласно спецификациям Intel. Задержка - это количество тактовых циклов, которые команда принимает для прохождения по конвейеру, а пропускная способность - это то, как долго процессор должен ждать, прежде чем снова принять ту же инструкцию. Как вы можете видеть, он может выполнять два цикла mov за такт, однако ему приходится ждать половину такта между двумя mov, поэтому он может эффективно выполнять только один mov за такт (или я ошибаюсь здесь и неправильно интерпретирую термины? См. PDF here).

Конечно, mov reg, mem может быть длиннее 0,5 такта, в зависимости от того, находятся ли данные в кеше 1-го или 2-го уровня или нет в кеше вообще, и их необходимо захватить из памяти. Тем не менее, время ожидания выше игнорирует этот факт (как указано выше в PDF-состояниях), он предполагает, что все данные, необходимые для mov, уже присутствуют (в противном случае латентность будет увеличиваться по тому, сколько времени потребуется для получения данных из любой точки прямо сейчас - это может быть несколько тактовых циклов и полностью не зависит от выполняемой команды, говорит PDF на стр. 482/C-30).

Что интересно, независимо от того, является ли mov 32 или 64 бит не играет никакой роли. Это означает, что, если полоса пропускания памяти становится ограничивающим фактором, 64-битные mov одинаково быстны для 32-битных mov, и поскольку для использования одного и того же количества данных от A до B при использовании 64 бит требуется всего половина, (теоретически) в два раза выше (тот факт, что это не так, потому что память не является неограниченной быстро).

Хорошо, теперь вы думаете, что при использовании более крупных регистров SSE вы должны получить более высокую пропускную способность, не так ли? AFAIK xmm регистры не 256, а 128 бит в ширину, BTW (reference at Wikipedia). Однако вы считаете, что время ожидания и пропускная способность? Либо данные, которые вы хотите переместить, равно 128 бит или нет. В зависимости от того, что вы либо переместить его с помощью

movdqa xmm1, [address] 
movdqa [address2], xmm1 

или если не выровнены

movdqu xmm1, [address] 
movdqu [address2], xmm1 

Ну, MOVDQA/movdqu имеет задержку 1 и пропускную способность 1. Таким образом, инструкции в два раза дольше и время ожидания после того, как инструкции в два раза длиннее обычного mov.

И что-то еще, что мы даже не учли, это тот факт, что ЦП фактически разбивает инструкции на микрооперации и может выполнять их параллельно. Теперь он начинает становиться действительно сложным ... даже слишком сложным для меня.

В любом случае, я знаю по опыту загрузки данных в/из регистров xmm намного медленнее, чем загрузка данных в/из нормальных регистров, поэтому ваша идея ускорить передачу с использованием регистров xmm была обречена с первой секунды. Я действительно удивлен тем, что в конце SSE memmove не намного медленнее, чем обычный.

+0

Очень хорошо написано, я это понял, и я мало знаю о том, как работают процессоры. – cfeduke 2008-11-06 18:32:46

+0

Ну, это все очень хорошо (спасибо за коррекцию ширины SSE), но на самом деле он не отвечает на основной вопрос: почему код, который должен просто насыщать пропускную способность памяти, намного лучше работает на родном 64-битном, а не 32-битном под WOW64. Где узкое место? – timday 2008-11-07 13:41:19

0

У меня нет ссылки передо мной, поэтому я не совсем уверен в таймингах/инструкциях, но я все еще могу дать теорию. Если вы перемещаете память под 32-битным режимом, вы будете делать что-то вроде «rep movsd», который перемещает одно 32-битное значение за каждый такт. В 64-битном режиме вы можете сделать «rep movsq», который выполняет однократное 64-битное перемещение каждого такта. Эта инструкция недоступна для 32-битного кода, поэтому вы должны сделать 2 x rep movsd (в 1 цикл куска) за половину скорости выполнения.

ОЧЕНЬ упрощена, игнорируя все вопросы пропускной способности памяти/выравнивания и т.д., но это, где все это начинается ...

+0

Но это не объясняет, почему копирование кода через регистры SSE (которые являются 128-разрядными, если вы находитесь в 32-разрядном или 64-битном режиме), по-видимому, ограничено пропускной способностью в 32 бит. – timday 2008-11-14 13:27:58

+0

Регистры SSE должны хранить магазины по ширине шины данных (64-бит). Однако, поскольку у меня нет таймингов передо мной, магазины SSE могут использовать в два раза тактовые циклы обычного хранилища регистров и, следовательно, иметь ту же скорость передачи данных, что и 32-битная копия. – 2008-11-14 13:40:33

5

я, наконец, добрался до нижней части этого (и умереть в ответ SENTE был на правильном пути, спасибо)

В приведенном ниже, целевой_адрес и СРК являются 512 МБайт станд :: вектор. Я использую компилятор Intel 10.1.029 и CRT.

На 64-битной и

memcpy(&dst[0],&src[0],dst.size())

и

memcpy(&dst[0],&src[0],N)

, где N предварительно объявлена ​​const size_t N=512*(1<<20); вызова

__intel_fast_memcpy

основная часть которых состоит из:

000000014004ED80 lea   rcx,[rcx+40h] 
    000000014004ED84 lea   rdx,[rdx+40h] 
    000000014004ED88 lea   r8,[r8-40h] 
    000000014004ED8C prefetchnta [rdx+180h] 
    000000014004ED93 movdqu  xmm0,xmmword ptr [rdx-40h] 
    000000014004ED98 movdqu  xmm1,xmmword ptr [rdx-30h] 
    000000014004ED9D cmp   r8,40h 
    000000014004EDA1 movntdq  xmmword ptr [rcx-40h],xmm0 
    000000014004EDA6 movntdq  xmmword ptr [rcx-30h],xmm1 
    000000014004EDAB movdqu  xmm2,xmmword ptr [rdx-20h] 
    000000014004EDB0 movdqu  xmm3,xmmword ptr [rdx-10h] 
    000000014004EDB5 movntdq  xmmword ptr [rcx-20h],xmm2 
    000000014004EDBA movntdq  xmmword ptr [rcx-10h],xmm3 
    000000014004EDBF jge   000000014004ED80 

и работает на частоте ~ 2200 МБайт/с.

Но на 32-битном

memcpy(&dst[0],&src[0],dst.size())

звонки

__intel_fast_memcpy

большая часть которого состоит из

004447A0 sub   ecx,80h 
    004447A6 movdqa  xmm0,xmmword ptr [esi] 
    004447AA movdqa  xmm1,xmmword ptr [esi+10h] 
    004447AF movdqa  xmmword ptr [edx],xmm0 
    004447B3 movdqa  xmmword ptr [edx+10h],xmm1 
    004447B8 movdqa  xmm2,xmmword ptr [esi+20h] 
    004447BD movdqa  xmm3,xmmword ptr [esi+30h] 
    004447C2 movdqa  xmmword ptr [edx+20h],xmm2 
    004447C7 movdqa  xmmword ptr [edx+30h],xmm3 
    004447CC movdqa  xmm4,xmmword ptr [esi+40h] 
    004447D1 movdqa  xmm5,xmmword ptr [esi+50h] 
    004447D6 movdqa  xmmword ptr [edx+40h],xmm4 
    004447DB movdqa  xmmword ptr [edx+50h],xmm5 
    004447E0 movdqa  xmm6,xmmword ptr [esi+60h] 
    004447E5 movdqa  xmm7,xmmword ptr [esi+70h] 
    004447EA add   esi,80h 
    004447F0 movdqa  xmmword ptr [edx+60h],xmm6 
    004447F5 movdqa  xmmword ptr [edx+70h],xmm7 
    004447FA add   edx,80h 
    00444800 cmp   ecx,80h 
    00444806 jge   004447A0 

и работает только при ~ 1350 Мбайт/с.

ОДНАКО

memcpy(&dst[0],&src[0],N) 

, где N предварительно объявлена ​​const size_t N=512*(1<<20); компилирует (на 32 бита) для прямого вызова к

__intel_VEC_memcpy 

основная часть которого состоит из

0043FF40 movdqa  xmm0,xmmword ptr [esi] 
    0043FF44 movdqa  xmm1,xmmword ptr [esi+10h] 
    0043FF49 movdqa  xmm2,xmmword ptr [esi+20h] 
    0043FF4E movdqa  xmm3,xmmword ptr [esi+30h] 
    0043FF53 movntdq  xmmword ptr [edi],xmm0 
    0043FF57 movntdq  xmmword ptr [edi+10h],xmm1 
    0043FF5C movntdq  xmmword ptr [edi+20h],xmm2 
    0043FF61 movntdq  xmmword ptr [edi+30h],xmm3 
    0043FF66 movdqa  xmm4,xmmword ptr [esi+40h] 
    0043FF6B movdqa  xmm5,xmmword ptr [esi+50h] 
    0043FF70 movdqa  xmm6,xmmword ptr [esi+60h] 
    0043FF75 movdqa  xmm7,xmmword ptr [esi+70h] 
    0043FF7A movntdq  xmmword ptr [edi+40h],xmm4 
    0043FF7F movntdq  xmmword ptr [edi+50h],xmm5 
    0043FF84 movntdq  xmmword ptr [edi+60h],xmm6 
    0043FF89 movntdq  xmmword ptr [edi+70h],xmm7 
    0043FF8E lea   esi,[esi+80h] 
    0043FF94 lea   edi,[edi+80h] 
    0043FF9A dec   ecx 
    0043FF9B jne   ___intel_VEC_memcpy+244h (43FF40h) 

и работает при ~ 2100 Мбайт/с (и доказывает 32 бит не ограничена пропускной способностью).

Я отозвал свое утверждение о том, что мой собственный memcpy-подобный код SSE страдает от подобных ~ 1300 Мбайт/лимит в 32-битных сборках; У меня теперь нет никаких проблем получение> 2GByte/s на 32 или 64 бит; трюк (как подсказка выше) - использовать невременные («потоковые») магазины (например, _mm_stream_ps).

Это кажется немного странным, что 32-битный «dst.size()» тетср не в конечном итоге вызова быстрее «movnt» версия (если вы вступаете в тетсру есть самое невероятное количество CPUID проверки и эвристические логики, например, сравнивая число байтов, которые должны быть скопированы с размером кеша и т. Д., Прежде чем он будет находиться рядом с вашими фактическими данными ), но по крайней мере я понимаю наблюдаемое поведение сейчас (и это не связано с SysWow64 или H/W).

1

Благодарим Вас за положительный отзыв! Я думаю, что могу отчасти объяснить, что здесь происходит.

Использования без временных магазинов для тетсра определенно постилось если вы только временный вызов тетсра.

С другой стороны, если вы сравниваете приложение, то магазины movdqa имеют преимущество в том, что они оставляют память назначения в кеше. Или, по крайней мере, его часть, которая вписывается в кеш.

Итак, если вы разрабатываете библиотеку времени выполнения, и если вы можете предположить, что приложение, которое вызвало memcpy, будет использовать буфер назначения сразу после вызова memcpy, тогда вы захотите предоставить версию movdqa. Это эффективно оптимизирует поездку из памяти обратно в процессор, который будет следовать за версией movntdq, и все инструкции, следующие за вызовом, будут работать быстрее.

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

Итак, идея memcpy будет иметь несколько версий под капотом. Когда буфер назначения мал по сравнению с кешем процессора, используйте movdqa, в противном случае буфер назначения будет большим по сравнению с кешем процессора, используйте movntdq. Похоже, это то, что происходит в 32-битной библиотеке.

Конечно, ничто из этого не имеет ничего общего с различиями между 32-битными и 64-битными.

Мое предположение заключается в том, что 64-битная библиотека не так зрелая. Разработчики просто не получили возможности предоставить обе программы в этой версии библиотеки.

0

Ниже приведен пример процедуры memcpy, специально предназначенной для архитектуры с 64-разрядной архитектурой.

void uint8copy(void *dest, void *src, size_t n){ 
    uint64_t * ss = (uint64_t)src; 
    uint64_t * dd = (uint64_t)dest; 
    n = n * sizeof(uint8_t)/sizeof(uint64_t); 

    while(n--) 
     *dd++ = *ss++; 
}//end uint8copy() 

Полный текст статьи здесь: http://www.godlikemouse.com/2008/03/04/optimizing-memcpy-routines/