Выполнение инструкций x86 rep на современных (конвейерных / суперскалярных) процессорах - PullRequest
18 голосов
/ 08 декабря 2011

Я писал в сборке x86 в последнее время (для шутки), и мне было интересно, действительно ли инструкции с префиксными строками rep имеют преимущество в производительности на современных процессорах или они просто реализованы для обратной совместимости.

Я могу понять, почему Intel изначально внедрила инструкции rep, когда процессоры выполняли только одну инструкцию за раз, но есть ли преимущество в их использовании сейчас?

С циклом, который компилирует больше инструкций, есть больше, чтобы заполнить конвейер и / или быть выведенным из строя. Созданы ли современные процессоры для оптимизации этих повторяющихся инструкций или же эти реплики используются так редко в современном коде, что они не важны для производителей?

Ответы [ 3 ]

36 голосов
/ 08 декабря 2011

В руководствах по оптимизации для AMD и Intel много места отводится таким вопросам. Срок действия рекомендаций, данных в этой области, имеет «период полураспада» - разные поколения процессоров ведут себя по-разному, например:

В Руководстве по оптимизации архитектуры Intel приведены сравнительные показатели производительности для различных методов блочного копирования (включая rep stosd) для Таблица 7-2. Относительная производительность процедур копирования в память , стр. 7-37f., Для разных процессоров, и опять же, то, что является самым быстрым на одном, может быть не самым быстрым на других.

Во многих случаях последние процессоры x86 (которые имеют «строковые» операции SSE4.2) могут выполнять строковые операции через модуль SIMD, см. это исследование .

Чтобы следить за всем этим (и / или оставаться в курсе, когда что-то неизбежно изменится, прочитайте Руководства / блоги по оптимизации Agner Fog .

9 голосов
/ 08 декабря 2011

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

Для небольших строк (может быть, до 16 байтов) выполнение этого вручную с помощью простых инструкций, вероятно, быстрее, поскольку это позволяет избежать затрат на настройку более сложных методов (а для строк фиксированного размера можно легко развернуть). Для строк среднего размера (возможно, от 16 байт до 4 КиБ) лучше всего подойдет что-то вроде «REP MOVSD» (с некоторыми инструкциями «MOVSB», если возможно смещение).

Для чего-то большего, чем это, некоторые люди будут испытывать желание перейти на SSE / AVX и предварительную выборку, и т. Д. Лучшая идея состоит в том, чтобы исправить вызывающие абоненты так, чтобы копирование (или strlen () или что-то еще) не требовалось на первом месте. Если вы будете стараться изо всех сил, вы почти всегда найдете способ. Примечание: Также будьте очень осторожны с «предполагаемыми» процедурами fast mempcy () - обычно они тестировались на массивных строках и не тестировались на гораздо более вероятных крошечных / маленьких / средних строках.

Также обратите внимание, что (с целью оптимизации, а не удобства) из-за всех этих различий (вероятная длина, выравнивание, фиксированный или переменный размер, тип процессора и т. Д.) Идея иметь один многоцелевой "memcpy ()" для всех очень разных случаев близоруко.

1 голос
/ 14 апреля 2016

Так как никто не дал вам никаких чисел, я дам вам некоторые, которые я нашел, сравнив мой сборщик мусора, который очень тяжелый для memcpy. Мои объекты, которые нужно скопировать, имеют длину 60% и 16 байт, а остальные 30% составляют 500 - 8000 байт или около того.

  • Условие: оба значения dst, src и n кратны 8.
  • Процессор: AMD Phenom (tm) II X6 1090T Процессор 64bit / linux

Вот мои три memcpy варианта:

Цикл цикла с ручным кодированием:

if (n == 16) {
    *dst++ = *src++;
    *dst++ = *src++;
} else {
    size_t n_ptrs = n / sizeof(ptr);
    ptr *end = dst + n_ptrs;
    while (dst < end) {
        *dst++ = *src++;
    }
}

(ptr - псевдоним uintptr_t). Время: 101,16%

rep movsb

if (n == 16) {
    *dst++ = *src++;
    *dst++ = *src++;
} else {
    asm volatile("cld\n\t"
                 "rep ; movsb"
                 : "=D" (dst), "=S" (src)
                 : "c" (n), "D" (dst), "S" (src)
                 : "memory");
}

Время: 103,22%

rep movsq

if (n == 16) {
    *dst++ = *src++;
    *dst++ = *src++;
} else {
    size_t n_ptrs = n / sizeof(ptr);
    asm volatile("cld\n\t"
                 "rep ; movsq"
                 : "=D" (dst), "=S" (src)
                 : "c" (n_ptrs), "D" (dst), "S" (src)
                 : "memory");
}

Время: 100,00%

req movsq выигрывает с небольшим отрывом.

...