Переменные структуры Memset отдельно против всей структуры memset, что быстрее? - PullRequest
0 голосов
/ 24 апреля 2020

Скажем, у меня есть такая структура:

struct tmp {
    unsigned char arr1[10];
    unsigned char arr2[10];
    int  i1;
    int  i2;
    unsigned char arr3[10];
    unsigned char arr4[10];
};

Что из этого будет быстрее?

(1) Memset устанавливает всю структуру в 0, а затем заполняет элементы как:

struct tmp t1;
memset(&t1, 0, sizeof(struct tmp));

t1.i1 = 10;
t1.i2 = 20;
memcpy(t1.arr1, "ab", sizeof("ab"));
// arr2, arr3 and arr4 will be filled later.

ИЛИ

(2) Отдельные переменные Memset:

struct tmp t1;
t1.i1 = 10;
t1.i2 = 20;
memcpy(t1.arr1, "ab", sizeof("ab"));

memset(t1.arr2, 0, sizeof(t1.arr2); // will be filled later
memset(t2.arr3, 0, sizeof(t1.arr3); // will be filled later
memset(t2.arr4, 0, sizeof(t1.arr4); // will be filled later

С точки зрения производительности, несколько вызовов в memset быстрее (для отдельных элементов структуры) быстрее / медленнее, чем один вызов memset (для всей структуры).

1 Ответ

2 голосов
/ 24 апреля 2020

Не имеет смысла обсуждать это без конкретной системы c, и при этом не стоит задумываться над этим, если у вас нет проблем с производительностью. Я могу попробовать еще.

Для "обычного компьютера" вы должны учитывать:

  • Выровненный доступ
    Доступ к фрагменту данных в одном go обычно лучше. В случае возможного смещения, служебный код для обработки примерно одинаков, независимо от размера данных. Предполагая теоретически, что весь доступ в этом коде оказывается неправильно выровненным, тогда 1 вызов memset лучше, чем 3.

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

    Ваша структура была объявлена ​​без учета выравнивания, поэтому это будет проблемой здесь - компилятор вставит много отступов.

    (С другой стороны, memset во всей структуре будет также перезаписывать байты заполнения, что является небольшим количеством служебного кода.)

  • Данные использование кеша
    Доступ к области смежной памяти сверху вниз намного более «дружественен к кешу», чем доступ к ее фрагментам из нескольких мест в вашем коде. Последующий доступ к непрерывной памяти означает, что компьютер может загружать много данных в кэш, а не извлекать их из ОЗУ, что медленнее.

  • Использование кэша инструкций и предсказание переходов
    Не очень уместно в этом случае, так как код в основном просто делает необработанные копии и делает это без ветвления.

  • Количество генерируемых машинных инструкций
    Это всегда хороший, грубый показатель того, насколько быстрым является код. Очевидно, что некоторые инструкции работают намного медленнее, чем другие и c, но меньшее количество инструкций очень часто означает более быстрый код. Разбирая ваши две функции с помощью g cc x86_64 -O3, я получаю следующее:

    func1:
        movabs  rax, 85899345930
        pxor    xmm0, xmm0
        movups  XMMWORD PTR [rdi+16], xmm0
        mov     QWORD PTR [rdi+20], rax
        mov     eax, 25185
        movups  XMMWORD PTR [rdi], xmm0
        movups  XMMWORD PTR [rdi+32], xmm0
        mov     WORD PTR [rdi], ax
        ret
    
    func2:
        movabs  rax, 85899345930
        xor     edx, edx
        xor     ecx, ecx
        xor     esi, esi
        mov     QWORD PTR [rdi+20], rax
        mov     eax, 25185
        mov     WORD PTR [rdi], ax
        mov     BYTE PTR [rdi+2], 0
        mov     QWORD PTR [rdi+10], 0
        mov     WORD PTR [rdi+18], dx
        mov     QWORD PTR [rdi+28], 0
        mov     WORD PTR [rdi+36], cx
        mov     QWORD PTR [rdi+38], 0
        mov     WORD PTR [rdi+46], si
        ret
    

    Это довольно хороший показатель того, что предыдущий код более эффективен, и он также должен быть более дружественным к кешу данных. , поэтому было бы удивительно, если бы (1) не был значительно быстрее.

Также обратите внимание, что если вы объявите эту структуру со сроком хранения stati c, вы "перенесете" Обнулить часть CRT установки программы .bss и выполнить до того, как main () будет вызван. Тогда ни один из этих memset не понадобится. При более медленном запуске, но в целом более быстрой программе.

...