Копирование 80 байтов как можно быстрее

Я выполняю математическое вычисление, которое затрачивает значительное количество времени на выполнение memcpy, всегда копируя 80 байт из одного места в другое, массив из 20 32-разрядных int s. Общее вычисление занимает около 4-5 дней, используя оба ядра моего i7, поэтому даже ускорение на 1% приводит к сохранению около часа.

Используя memcpy в этой статье от Intel , Я смог ускорить примерно на 25%, а также сбросить аргумент размера и просто объявить внутри, кажется, имеет небольшой эффект. Однако я чувствую, что не использую тот факт, что мои операции копирования всегда одного размера. Тем не менее, я не могу придумать лучший способ.

void *memcpyi80(void* __restrict b, const void* __restrict a){
    size_t n = 80;
    char *s1 = b;
    const char *s2 = a;
    for(; 0<n; --n)*s1++ = *s2++;
    return b;
}

Некоторые другие вещи, которые могут быть полезны для оптимизации:

  1. Я использую Intel Core i7-2620M, основанный на Sandy Bridge. Я вообще не забочусь о переносимости.

  2. Мне нужно только 16 младших значащих бит каждого int. Остальные 16 бесполезны для меня и постоянно обнуляются.

  3. Несмотря на то, что я копирую 20 32-битных ints для вызова memcpy, мне все равно, о первом 17. Я добавил 3, поскольку это помогает с выравниванием и, следовательно, скоростью.

  4. Я использую GCC 4.6 для Windows 7.

Любые идеи?

UPDATE:

Я думаю, что это сборка (никогда не делала этого раньше, может быть больше, чем вам нужно):

memcpyi80:
    pushq   %r12
    .seh_pushreg    %r12
    pushq   %rbp
    .seh_pushreg    %rbp
    pushq   %rdi
    .seh_pushreg    %rdi
    pushq   %rsi
    .seh_pushreg    %rsi
    pushq   %rbx
    .seh_pushreg    %rbx
    .seh_endprologue
    movq    %rdx, %r9
    movq    %rcx, %rax
    negq    %r9
    andl    $15, %r9d
    je  .L165
    movzbl  (%rdx), %ecx
    leaq    -1(%r9), %r10
    movl    $79, %esi
    andl    $7, %r10d
    cmpq    $1, %r9
    movl    $79, %ebx
    leaq    1(%rdx), %r8
    movl    $1, %r11d
    movb    %cl, (%rax)
    leaq    1(%rax), %rcx
    jbe .L159
    testq   %r10, %r10
    je  .L160
    cmpq    $1, %r10
    je  .L250
    cmpq    $2, %r10
    je  .L251
    cmpq    $3, %r10
    je  .L252
    cmpq    $4, %r10
    je  .L253
    cmpq    $5, %r10
    je  .L254
    cmpq    $6, %r10
    je  .L255
    movzbl  (%r8), %r8d
    movl    $2, %r11d
    movb    %r8b, (%rcx)
    leaq    2(%rax), %rcx
    leaq    2(%rdx), %r8
.L255:
    movzbl  (%r8), %ebx
    addq    $1, %r11
    addq    $1, %r8
    movb    %bl, (%rcx)
    addq    $1, %rcx
.L254:
    movzbl  (%r8), %r10d
    addq    $1, %r11
    addq    $1, %r8
    movb    %r10b, (%rcx)
    addq    $1, %rcx
.L253:
    movzbl  (%r8), %edi
    addq    $1, %r11
    addq    $1, %r8
    movb    %dil, (%rcx)
    addq    $1, %rcx
.L252:
    movzbl  (%r8), %ebp
    addq    $1, %r11
    addq    $1, %r8
    movb    %bpl, (%rcx)
    addq    $1, %rcx
.L251:
    movzbl  (%r8), %r12d
    addq    $1, %r11
    addq    $1, %r8
    movb    %r12b, (%rcx)
    addq    $1, %rcx
.L250:
    movzbl  (%r8), %ebx
    addq    $1, %r8
    movb    %bl, (%rcx)
    movq    %rsi, %rbx
    addq    $1, %rcx
    subq    %r11, %rbx
    addq    $1, %r11
    cmpq    %r11, %r9
    jbe .L159
    .p2align 4,,10
.L160:
    movzbl  (%r8), %r12d
    movb    %r12b, (%rcx)
    movzbl  1(%r8), %ebp
    movb    %bpl, 1(%rcx)
    movzbl  2(%r8), %edi
    movb    %dil, 2(%rcx)
    movzbl  3(%r8), %ebx
    movb    %bl, 3(%rcx)
    leaq    7(%r11), %rbx
    addq    $8, %r11
    movzbl  4(%r8), %r10d
    movb    %r10b, 4(%rcx)
    movq    %rsi, %r10
    movzbl  5(%r8), %r12d
    subq    %rbx, %r10
    movq    %r10, %rbx
    movb    %r12b, 5(%rcx)
    movzbl  6(%r8), %ebp
    movb    %bpl, 6(%rcx)
    movzbl  7(%r8), %edi
    addq    $8, %r8
    movb    %dil, 7(%rcx)
    addq    $8, %rcx
    cmpq    %r11, %r9
    ja  .L160
.L159:
    movl    $80, %r12d
    subq    %r9, %r12
    movq    %r12, %rsi
    shrq    $4, %rsi
    movq    %rsi, %rbp
    salq    $4, %rbp
    testq   %rbp, %rbp
    je  .L161
    leaq    (%rdx,%r9), %r10
    addq    %rax, %r9
    movl    $1, %r11d
    leaq    -1(%rsi), %rdi
    vmovdqa (%r10), %xmm0
    movl    $16, %edx
    andl    $7, %edi
    cmpq    $1, %rsi
    vmovdqu %xmm0, (%r9)
    jbe .L256
    testq   %rdi, %rdi
    je  .L162
    cmpq    $1, %rdi
    je  .L244
    cmpq    $2, %rdi
    je  .L245
    cmpq    $3, %rdi
    je  .L246
    cmpq    $4, %rdi
    je  .L247
    cmpq    $5, %rdi
    je  .L248
    cmpq    $6, %rdi
    je  .L249
    vmovdqa 16(%r10), %xmm3
    movl    $2, %r11d
    movl    $32, %edx
    vmovdqu %xmm3, 16(%r9)
.L249:
    vmovdqa (%r10,%rdx), %xmm4
    addq    $1, %r11
    vmovdqu %xmm4, (%r9,%rdx)
    addq    $16, %rdx
.L248:
    vmovdqa (%r10,%rdx), %xmm5
    addq    $1, %r11
    vmovdqu %xmm5, (%r9,%rdx)
    addq    $16, %rdx
.L247:
    vmovdqa (%r10,%rdx), %xmm0
    addq    $1, %r11
    vmovdqu %xmm0, (%r9,%rdx)
    addq    $16, %rdx
.L246:
    vmovdqa (%r10,%rdx), %xmm1
    addq    $1, %r11
    vmovdqu %xmm1, (%r9,%rdx)
    addq    $16, %rdx
.L245:
    vmovdqa (%r10,%rdx), %xmm2
    addq    $1, %r11
    vmovdqu %xmm2, (%r9,%rdx)
    addq    $16, %rdx
.L244:
    vmovdqa (%r10,%rdx), %xmm3
    addq    $1, %r11
    vmovdqu %xmm3, (%r9,%rdx)
    addq    $16, %rdx
    cmpq    %r11, %rsi
    jbe .L256
    .p2align 4,,10
.L162:
    vmovdqa (%r10,%rdx), %xmm2
    addq    $8, %r11
    vmovdqu %xmm2, (%r9,%rdx)
    vmovdqa 16(%r10,%rdx), %xmm1
    vmovdqu %xmm1, 16(%r9,%rdx)
    vmovdqa 32(%r10,%rdx), %xmm0
    vmovdqu %xmm0, 32(%r9,%rdx)
    vmovdqa 48(%r10,%rdx), %xmm5
    vmovdqu %xmm5, 48(%r9,%rdx)
    vmovdqa 64(%r10,%rdx), %xmm4
    vmovdqu %xmm4, 64(%r9,%rdx)
    vmovdqa 80(%r10,%rdx), %xmm3
    vmovdqu %xmm3, 80(%r9,%rdx)
    vmovdqa 96(%r10,%rdx), %xmm2
    vmovdqu %xmm2, 96(%r9,%rdx)
    vmovdqa 112(%r10,%rdx), %xmm1
    vmovdqu %xmm1, 112(%r9,%rdx)
    subq    $-128, %rdx
    cmpq    %r11, %rsi
    ja  .L162
.L256:
    addq    %rbp, %rcx
    addq    %rbp, %r8
    subq    %rbp, %rbx
    cmpq    %rbp, %r12
    je  .L163
.L161:
    movzbl  (%r8), %edx
    leaq    -1(%rbx), %r9
    andl    $7, %r9d
    movb    %dl, (%rcx)
    movl    $1, %edx
    cmpq    %rbx, %rdx
    je  .L163
    testq   %r9, %r9
    je  .L164
    cmpq    $1, %r9
    je  .L238
    cmpq    $2, %r9
    je  .L239
    cmpq    $3, %r9
    je  .L240
    cmpq    $4, %r9
    je  .L241
    cmpq    $5, %r9
    je  .L242
    cmpq    $6, %r9
    je  .L243
    movzbl  1(%r8), %edx
    movb    %dl, 1(%rcx)
    movl    $2, %edx
.L243:
    movzbl  (%r8,%rdx), %esi
    movb    %sil, (%rcx,%rdx)
    addq    $1, %rdx
.L242:
    movzbl  (%r8,%rdx), %r11d
    movb    %r11b, (%rcx,%rdx)
    addq    $1, %rdx
.L241:
    movzbl  (%r8,%rdx), %r10d
    movb    %r10b, (%rcx,%rdx)
    addq    $1, %rdx
.L240:
    movzbl  (%r8,%rdx), %edi
    movb    %dil, (%rcx,%rdx)
    addq    $1, %rdx
.L239:
    movzbl  (%r8,%rdx), %ebp
    movb    %bpl, (%rcx,%rdx)
    addq    $1, %rdx
.L238:
    movzbl  (%r8,%rdx), %r12d
    movb    %r12b, (%rcx,%rdx)
    addq    $1, %rdx
    cmpq    %rbx, %rdx
    je  .L163
    .p2align 4,,10
.L164:
    movzbl  (%r8,%rdx), %r9d
    movb    %r9b, (%rcx,%rdx)
    movzbl  1(%r8,%rdx), %r12d
    movb    %r12b, 1(%rcx,%rdx)
    movzbl  2(%r8,%rdx), %ebp
    movb    %bpl, 2(%rcx,%rdx)
    movzbl  3(%r8,%rdx), %edi
    movb    %dil, 3(%rcx,%rdx)
    movzbl  4(%r8,%rdx), %r10d
    movb    %r10b, 4(%rcx,%rdx)
    movzbl  5(%r8,%rdx), %r11d
    movb    %r11b, 5(%rcx,%rdx)
    movzbl  6(%r8,%rdx), %esi
    movb    %sil, 6(%rcx,%rdx)
    movzbl  7(%r8,%rdx), %r9d
    movb    %r9b, 7(%rcx,%rdx)
    addq    $8, %rdx
    cmpq    %rbx, %rdx
    jne .L164
.L163:
    popq    %rbx
    popq    %rsi
    popq    %rdi
    popq    %rbp
    popq    %r12
    ret
.L165:
    movq    %rdx, %r8
    movl    $80, %ebx
    jmp .L159
    .seh_endproc
    .p2align 4,,15
    .globl  memcpyi
    .def    memcpyi;    .scl    2;  .type   32; .endef
    .seh_proc   memcpyi

UPDATE:

Основываясь на решении Питера Александера и объединяя его с идеями из нитей, я создал это:

void memcpyi80(void* __restrict b, const void* __restrict a){
    __m128 *s1 = b;
    const __m128 *s2 = a;
    *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; 
}

Ускорение небольшое, но измеримое (около 1%). Теперь я думаю, что мой следующий соблазн заключается в том, чтобы найти, как использовать __m256 AVX-типы, поэтому я могу сделать это за 3 шага, а не 5.

UPDATE:

Тип __m256 требует выравнивания по 32-битовому барьеру, что делает вещи медленнее, поэтому кажется, что __m128 - сладкое пятно.

36 голосов | спросил Alexandros Marinos 22 +04002011-10-22T13:35:44+04:00312011bEurope/MoscowSat, 22 Oct 2011 13:35:44 +0400 2011, 13:35:44

9 ответов


26

Самый быстрый способ сделать это - выровнять данные по 16-байтовым границам, тогда вся копия просто станет 5 копиями через регистры XMM.

Это более в два раза быстрее как ваша версия на моей машине.

Сохраните свои данные следующим образом:

#include <xmmintrin.h>
struct Data
{
    union
    {
        int i[20];
        __m128 v[5];
    };
};

Тогда функция копирования справедлива:

void memcpyv5(__m128* __restrict b, const __m128* __restrict a)
{
    __m128 t0 = a[0];
    __m128 t1 = a[1];
    __m128 t2 = a[2];
    __m128 t3 = a[3];
    __m128 t4 = a[4];
    b[0] = t0;
    b[1] = t1;
    b[2] = t2;
    b[3] = t3;
    b[4] = t4;
}

// Example
Data dst, src;
memcpyv5(dst.v, src.v);

Сборка:

__Z8memcpyv5PU8__vectorfPKS_:
LFB493:
    pushq   %rbp
LCFI2:
    movq    %rsp, %rbp
LCFI3:
    movaps  16(%rsi), %xmm3
    movaps  32(%rsi), %xmm2
    movaps  48(%rsi), %xmm1
    movaps  64(%rsi), %xmm0
    movaps  (%rsi), %xmm4
    movaps  %xmm4, (%rdi)
    movaps  %xmm3, 16(%rdi)
    movaps  %xmm2, 32(%rdi)
    movaps  %xmm1, 48(%rdi)
    movaps  %xmm0, 64(%rdi)
    leave
    ret
ответил Peter Alexander 23 +04002011-10-23T01:53:00+04:00312011bEurope/MoscowSun, 23 Oct 2011 01:53:00 +0400 2011, 01:53:00
5

Использование преимуществ механизма выполнения вне очереди

Вы также можете прочитать о движке Ex-of-Order Execution Engine в «Справочном руководстве по оптимизации архитектуры Intel® 64 и IA-32» http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf раздел 2.1.2 , и воспользоваться его преимуществами.

Например, в серии процессоров Intel SkyLake (запущен в 2015 году):

  • 4 исполнительных блока для Арифметической логической единицы (ALU) (add, и, cmp или, test, xor, movzx, movsx, mov, (v) movdqu, (v) movdqa, (v) movap *, ( v) movup),
  • 3 исполнительных блока для Vector ALU ((v) pand, (v) por, (v) pxor, (v) movq, (v) movq, (v) movap *, (v) movup *, (v) (v) orp *, (v) paddb /w /d /q, (v) blendv *, (v) blendp *, (v) pblendd)

Таким образом, мы можем занимать выше единицы (3 + 4) параллельно, если мы используем операции только для регистров. Мы не можем использовать 3 + 4 инструкции параллельно для копирования памяти. Мы можем использовать одновременно максимум две 32-байтные инструкции для загрузки из памяти и одну 32-байтную инструкцию для хранения из памяти и даже если мы работаем с кешем уровня 1.

См. руководство Intel снова, чтобы понять, как выполнить самую быструю реализацию memcpy: http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64- IA-32-архитектуры оптимизация-manual.pdf

Раздел 2.2.2 (Механизм внезадачи на микроархитектуре Хасуэлла): «Планировщик контролирует отправку микроопераций в порты диспетчеризации. Существует восемь диспетчерских портов для поддержки внеочередных портов, Четыре из восьми портов предоставили ресурсы выполнения для вычислительных операций. Остальные 4 порта поддерживают операции памяти до двух 256-битной нагрузки и одну операцию хранения в 256 бит в цикле. "

В разделе 2.2.4 (Подсистема кэша и памяти) указано следующее: «Кэш данных первого уровня поддерживает два байта нагрузки каждый цикл, каждый микрооператор может извлекать до 32 байтов данных».

Раздел 2.2.4.1 (Расширенные операции загрузки и сохранения) имеет следующую информацию: Кэш данных L1 может обрабатывать две 256-разрядные (32 байта) нагрузки и одну 256-битную (32 байта) операции хранения каждого цикла. Унифицированный L2 может обслуживать один цикл кэша (64 байта) каждого цикла. Кроме того, доступно 72 буфера нагрузки и 42 буфера хранения для поддержки выполнения микроопераций в полете.

Другие разделы (2.3 и т. д., посвященные Sandy Bridge и другим микроархитектурам) в основном повторяют вышеприведенную информацию.

В разделе 2.3.4 («Ядро выполнения») приводятся дополнительные сведения.

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

  • Порт 0: ALU, Shift, Mul, STTNI, Int-Div, 128b-Mov, Blend, 256b-Mov
  • Порт 1: ALU, Fast LEA, Slow LEA, MUL, Shuf, Blend, 128bMov, Add, CVT
  • Порт 2 & Порт 3: Load_Addr, Store_addr
  • Порт 4: Store_data ​​li>
  • Порт 5: ALU, Shift, Branch, Fast LEA, Shuf, Blend, 128b-Mov, 256b-Mov

Раздел 2.3.5.1 (Обзор операции загрузки и хранения) также может быть полезен для понимания того, как сделать быструю копию памяти, а также в разделе 2.4.4.1 (Загрузка и хранение).

Для других архитектур процессоров это снова - два блока нагрузки и один блок хранения. Таблица 2-4 (Параметры кэша микроархитектуры Skylake) содержит следующую информацию:

Пиковая пропускная способность (байты /цикл):

  • Кэш данных первого уровня: 96 байт (2x32B Load + 1 * 32B Store)
  • Кэш второго уровня: 64 байта
  • Третий уровень: 32 байта.

Я также провел тесты скорости на своем процессоре Intel Core i5 6600 (Skylake, 14 нм, выпущенном в сентябре 2015 года) с памятью DDR4, и это подтвердило теорию. Например, мои тесты показали, что использование общих 64-разрядных регистров для копирования памяти, даже много регистров параллельно, снижает производительность. Кроме того, достаточно использовать только 2 регистра XMM - добавление третьего не повышает производительность.

Если ваш процессор имеет бит AVID CPUID, вы можете воспользоваться преимуществами больших, 256-битных (32 байта) регистров YMM для копирования памяти, чтобы заняться двумя блоками полной нагрузки. Поддержка AVX была впервые представлена ​​Intel с процессорами Sandy Bridge, которые были отправлены в первом квартале 2011 года, а затем AMD с процессором Bulldozer в третьем квартале 2011 года.

// first cycle - use two load  units
vmovdqa  ymm0, ymmword ptr [esi+0]       // load first part (32 bytes)
vmovdqa  ymm1, ymmword ptr [esi+32]      // load 2nd part (32 bytes)

// second cycle - use one load unit and one store unit
vmovdqa  xmm2, xmmword ptr [esi+64]      // load 3rd part (16 bytes)
vmovdqa  ymmword ptr [edi+0],  ymm0      // store first part

// third cycle - use one store unit
vmovdqa  ymmword ptr [edi+32], ymm1      // store 2nd part

// fourth cycle - use one store unit
vmovdqa  xmmword ptr [edi+64], xmm2      // store 3rd part

Просто убедитесь, что ваши данные выровнены по 16 байт (для регистров XMM) и на 32 байта (для регистров YMM), иначе будет ошибка нарушения доступа. Если данные не выровнены, используйте команды без команды: vmovdqu и movups соответственно.

Если вам повезло с процессором AVX-512, вы можете скопировать 80 байтов всего за четыре команды:

vmovdqu64   zmm0, [esi]
vmovdqu     xmm1, [esi+64]       
vmovdqu64   [edi], zmm0
vmovdqu     [edi+64], xmm1       

Дальнейшее чтение - ERMSB (не требуется копировать ровно 80 байт, но для гораздо больших блоков)

Если ваш процессор имеет бит CPUID ERMSB (Enhanced REP MOVSB), команда rep movsb выполняется иначе, чем на старых процессорах. Это быстрее, чем REP MOVSD (MOVSQ), но только на больших блоках.

REP MOVSB ​​быстрее, чем простой простой «MOV RAX in loop» копирует только начальную форму 256-байтовых блоков и быстрее, чем AVX-копию, начиная с 2048 байтов-блоков.

Итак, поскольку размер вашего блока составляет только 80 байт, ERMSB не даст вам никакой пользы.

Получите Microsoft Visual Studio и найдите memcpy.asm - он имеет разные сценарии для разных процессов и разных размеров блоков, поэтому вы сможете выяснить, какой метод лучше всего использовать для вашего процессора и размер вашего блока.

В то же время я могу считать Intel ERMSB «полупеленой», потому что в ERMSB существует высокий внутренний запуск - около 35 циклов и из-за других ограничений.

См. Руководство Intel по оптимизации, раздел 3.7.6. Расширенные операции REP MOVSB ​​и STOSB (ERMSB) http://www.intel.com/content/dam/www/public/us/en/documents/manuals /64-ia-32-architectures-optimization-manual.pdf

  • стоимость запуска составляет 35 циклов;
  • как исходный, так и целевой адреса должны быть выровнены с 16-байтовой границей;
  • область источника не должна пересекаться с областью назначения;
  • длина должна быть кратной 64 для повышения производительности;
  • направление должно быть направлено (CLD).

Я надеюсь, что в будущем Intel исключит такие высокие затраты на запуск.

ответил Maxim Masiutin 8 Maypm17 2017, 13:04:24
2

Если вам действительно нужна эта часть как можно быстрее, одним очевидным путем было бы написать ее на ассемблере. Язык ассемблера, который вы опубликовали, немного похож на эту задачу (по крайней мере, для меня). Учитывая фиксированный размер, очевидный маршрут будет выглядеть примерно так:

; warning: I haven't written a lot of assembly code recently -- I could have 
; some of the syntax a bit wrong.
;
memcpyi80 proc dest:ptr byte src:ptr byte
    mov esi, src
    mov edi, dest
    mov ecx, 20    ; 80/4
    rep movsd
memcpyi80 endp

То, что определенно открыто для улучшения, используя (для одного примера), перемещается через регистры SSE, но я оставлю это для других, чтобы играть. Улучшение довольно небольшое, хотя: у последних процессоров есть специальный путь специально для копий памяти, который он будет использовать, поэтому он довольно конкурентный, несмотря на его простоту.

Комментарий @Mike Dunlavey хорош, хотя: в большинстве случаев люди думают, что им нужна более быстрая копия памяти, им действительно нужно переосмыслить свой код, чтобы просто избежать необходимости.

ответил Jerry Coffin 22 +04002011-10-22T18:54:40+04:00312011bEurope/MoscowSat, 22 Oct 2011 18:54:40 +0400 2011, 18:54:40
0

Что генерируется сборка?

Помню, что использование structs может ускорить процесс:

typedef struct {
  int x[17] __attribute__ ((packed));
  int padding __attribute__ ((packed, unused));
} cbytes __attribute__ ((packed));


void *memcpyi80(cbytes* __restrict b, const cbytes* __restrict a){
    size_t n = 80 / sizeof(cbytes);
    cbytes *s1 = b;
    const cbytes *s2 = a;
    for(; 0<n; --n)*s1++ = *s2++;
    return b;
}
ответил Pubby 22 +04002011-10-22T14:21:34+04:00312011bEurope/MoscowSat, 22 Oct 2011 14:21:34 +0400 2011, 14:21:34
0

Вы знаете, что такое размер, и вы знаете, что это ints, так что сделайте небольшую инсайдерскую торговлю:

void myCopy(int* dest, int* src){
    dest[ 0] = src[ 0];
    dest[ 1] = src[ 1];
    dest[ 2] = src[ 2];
    ...
    dest[19] = src[19];
}
ответил Mike Dunlavey 22 +04002011-10-22T18:51:47+04:00312011bEurope/MoscowSat, 22 Oct 2011 18:51:47 +0400 2011, 18:51:47
0

Компилятор не может векторизовать вашу версию. Если вы просто измените цикл for, который будет индексироваться, а не разыменовываться, вы увидите огромное улучшение скорости. Я получаю> 10x ускорение для этого:

void *memcpyi80(void* __restrict b, const void* __restrict a) {
    size_t n = 80;
    char *s1 = b;
    const char *s2 = a;
    for(; 0 < n; --n) {
      s1[n] = s2[n];
    }
    return b;
}
ответил Lenny 22 +04002011-10-22T19:49:03+04:00312011bEurope/MoscowSat, 22 Oct 2011 19:49:03 +0400 2011, 19:49:03
0

Вы копируете byte by byte, поэтому было бы намного быстрее скопировать int вместо int. Также необходимо развернуть цикл:

void *memcpyi80(void* __restrict b, const void* __restrict a){
  int* s1 = b;
  int* s2 = a;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++;
  // *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  return b;
}

В C # я обнаружил, что разделение доступа и инкрементации выполняется быстрее, поэтому стоит попробовать:

void *memcpyi80(void* __restrict b, const void* __restrict a){
  int* s1 = b;
  int* s2 = a;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  // *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2;
  return b;
}
ответил Guffa 22 +04002011-10-22T22:47:02+04:00312011bEurope/MoscowSat, 22 Oct 2011 22:47:02 +0400 2011, 22:47:02
0

Код ниже оптимизирован:

 void *memcpyi72(void* __restrict b, const void * __restrict a)
{
  return memcpy(b,a, 18*sizeof(int));
}

GCC с -O3 генерирует ту же самую сборку для этой функции, что и для Код Pubby8 . Нет необходимости использовать структуры.

ответил Mariusz Ceier 22 +04002011-10-22T23:26:54+04:00312011bEurope/MoscowSat, 22 Oct 2011 23:26:54 +0400 2011, 23:26:54
-1

Нет никакого решения в c или c ++, может быть лучше сборки (если, конечно, это было ужасно написано). Ответ с языком ассемблера от Джерри Коффина выше ...

memcpyi80 proc dest:ptr byte src:ptr byte
    mov esi, src   ; load source address
    mov edi, dest  ; load destination address
    mov ecx, 20    ; initialize count register (80/4)
    rep movsd      ; perform transfer
memcpyi80 endp

не может быть улучшено, на мой взгляд, если только не возможно использовать более мелкие операнды большего числа. Естественно, адреса памяти должны быть правильно выровнены. Инструкция rep movsd является единственной частью кода, которая выполняет любую работу, автоматически увеличивая регистр счетчика до завершения операции.

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

ответил Fred 23 +04002011-10-23T13:02:55+04:00312011bEurope/MoscowSun, 23 Oct 2011 13:02:55 +0400 2011, 13:02:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132