Помогает clang выполнять лучшую работу без изменения кода

#c #optimization #clang #c11

#c #оптимизация #clang #c11

Вопрос:

Я тестирую различные варианты с clang 7/8 и c11.

Я реализовал некоторые функции сортировки и начал оптимизировать некоторые из них.

Функция сетевой сортировки — это уже очень простые части кода, но я обнаружил, что некоторое переписывание может обеспечить лучшую производительность на платформе x64 (не arm).

https://godbolt.org/z/uD7_DM

Если мы сравним обе сборки. Единственными заметными отличиями являются количество команд push и структура команд first mov :

 network_sort_08_a:
    push    rbp
    push    r14
    push    rbx
    mov     esi, dword ptr [rdi]
    mov     eax, dword ptr [rdi   4]
    mov     edx, dword ptr [rdi   8]
    mov     r9d, dword ptr [rdi   12]
    mov     ebx, dword ptr [rdi   16]
    cmp     esi, eax
    mov     ecx, eax
    cmovle  ecx, esi
  

Вместо :

 network_sort_8_b:
    push    rbp
    push    r15
    push    r14
    push    rbx
    mov     eax, dword ptr [rdi]
    mov     edx, dword ptr [rdi   4]
    cmp     eax, edx
    mov     ecx, edx
    cmovle  ecx, eax
    cmovge  edx, eax
    mov     eax, dword ptr [rdi   8]
  

Я хочу вывести первую версию без перезаписи функции.

Есть идеи, как этого добиться?

С наилучшими пожеланиями

 __attribute__((always_inline))
inline int min(const int a, const int b)
{
  return a < b ? a : b;
}

__attribute__((always_inline))
inline int max(const int a, const int b)
{
  return a > b ? a : b;
}

__attribute__((always_inline))
inline void network_swap(int* keys, const int key_index0, const int key_index1)
{
  const int key0 = keys[key_index0];
  const int key1 = keys[key_index1];
  const int min_key = min(key0, key1);
  const int max_key = max(key0, key1);
  keys[key_index0] = min_key;
  keys[key_index1] = max_key;
}

void network_sort_08_a(int* keys)
{
  // Resorted by dependencies
  // Serialize first reads
  const int key0 = keys[0];
  const int key1 = keys[1];
  const int key2 = keys[2];
  const int key3 = keys[3];
  const int key4 = keys[4];
  const int key5 = keys[5];
  const int key6 = keys[6];
  const int key7 = keys[7];
  keys[0] = min(key0, key1);
  keys[1] = max(key0, key1);
  keys[2] = min(key2, key3);
  keys[3] = max(key2, key3);
  keys[4] = min(key4, key5);
  keys[5] = max(key4, key5);
  keys[6] = min(key6, key7);
  keys[7] = max(key6, key7);
  network_swap(keys, 0, 2);
  network_swap(keys, 1, 3);
  network_swap(keys, 4, 6);
  network_swap(keys, 5, 7);
  network_swap(keys, 1, 2);
  network_swap(keys, 5, 6);
  network_swap(keys, 0, 4);
  network_swap(keys, 3, 7);
  network_swap(keys, 1, 5);
  network_swap(keys, 1, 4);
  network_swap(keys, 2, 6);
  network_swap(keys, 3, 6);
  network_swap(keys, 2, 4);
  network_swap(keys, 3, 5);
  network_swap(keys, 3, 4);
}

void network_sort_8_b(int* restrict keys)
{
  network_swap(keys, 0, 1);
  network_swap(keys, 2, 3);
  network_swap(keys, 0, 2);
  network_swap(keys, 1, 3);
  network_swap(keys, 1, 2);
  network_swap(keys, 4, 5);
  network_swap(keys, 6, 7);
  network_swap(keys, 4, 6);
  network_swap(keys, 5, 7);
  network_swap(keys, 5, 6);
  network_swap(keys, 0, 4);
  network_swap(keys, 1, 5);
  network_swap(keys, 1, 4);
  network_swap(keys, 2, 6);
  network_swap(keys, 3, 7);
  network_swap(keys, 3, 6);
  network_swap(keys, 2, 4);
  network_swap(keys, 3, 5);
  network_swap(keys, 3, 4);
}
  

Комментарии:

1. Пожалуйста, укажите код, с которым вы пытаетесь работать, в вопросе. Ссылки на Godbolt могут со временем исчезнуть.

2. примечание: вы на самом деле профилировали и сравнивали, чтобы убедиться, что это медленно? В большинстве случаев компилятор выполняет довольно хорошую работу.

3. Я профилировал это на AMD Ryzen, и был фактический выигрыш (от 5 до 10%).

4. Если вы используете ubuntu или mingw, не стесняйтесь сравнивать github.com/meuns/sort-things ветви master и network_rewrite.