GCC optimiza el bucle for basado en rango fijo como si tuviera una longitud variable más larga


Tengo una matriz de estructuras POD y estoy tratando de sumar a través de un campo. He aquí un ejemplo mínimo:

struct Item
{
    int x = 0;
    int y = 0;
};

typedef Item Items[2];

struct ItemArray
{
    Items items;

    int sum_x1() const;
    int sum_x2() const;
};

int ItemArray::sum_x1() const
{
    int total = 0;
    for (unsigned ii = 0; ii < 2; ++ii)
    {
        total += items[ii].x;
    }
    return total;
}

int ItemArray::sum_x2() const
{
    int total = 0;
    for (const Item& item : items)
    {
        total += item.x;
    }
    return total;
}

Las dos funciones sum hacen lo mismo. Clang los compila de forma idéntica. Pero GCC 6 con -O3 en x86_64 no lo hace. Aquí está sum_x1(), se ve bien:

  mov eax, DWORD PTR [rdi+8]
  add eax, DWORD PTR [rdi]
  ret

Ahora mira sum_x2():

  lea rdx, [rdi+16]
  lea rcx, [rdi+8]
  xor eax, eax
  add eax, DWORD PTR [rdi]
  cmp rdx, rcx
  je .L12
  lea rcx, [rdi+16]
  add eax, DWORD PTR [rdi+8]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+24]
  add eax, DWORD PTR [rdi+16]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+32]
  add eax, DWORD PTR [rdi+24]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+40]
  add eax, DWORD PTR [rdi+32]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+48]
  add eax, DWORD PTR [rdi+40]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+56]
  add eax, DWORD PTR [rdi+48]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+64]
  add eax, DWORD PTR [rdi+56]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+72]
  add eax, DWORD PTR [rdi+64]
  cmp rdx, rcx
  je .L2
  add eax, DWORD PTR [rdi+72]
  ret
.L2:
  rep ret
.L12:
  rep ret

¿Por qué GCC emite un bucle desenrollado de longitud variable hasta 10 cuando hay la longitud del bucle se fija en 2? Solo hace esto en una función miembro making haciendo sum_x2 un la función gratuita lo arregla.

ICC también optimiza sum_x2() de manera muy extraña, aunque el código generado es totalmente diferente. A diferencia de GCC, no importa si sum_x2() es una función miembro o una función libre both ambas son malas.

Estoy usando GCC 6, pero todas las versiones de GCC parecen tener problemas con este código. Agregar -march=haswell lo hace aún peor, agregando iteraciones para hasta 15 elementos en la matriz de tamaño 2. GCC 5 y 7 generan código aún más complejo, añadiendo SIMD instrucción.

Me gustaría identificar la causa exacta de este problema, para que pueda localizar y corregir ocurrencias similares en mi código. Entender qué desencadena este comportamiento en GCC 6 sería muy útil. Tengo un montón de bucles for basados en rango en mi código, y no estoy demasiado entusiasmado con la perspectiva de eliminarlos, pero si GCC no puede generar código razonable no tendré opción.

Pruébalo: https://godbolt.org/g/9GK4jy

Más relacionados locura: https://godbolt.org/g/BGYggD (el código óptimo es 3 instrucciones; GCC 6 produce 8 instrucciones; GCC 7 produce 130 instrucciones)

Author: John Zwinck, 2017-08-04

1 answers

Como lo describió Richard Biener en mi informe de error , el problema parece ser que GCC antes de la versión 8 no entendía que un campo de una clase o estructura estaba sujeto a las mismas optimizaciones (por ejemplo, conteo de bucle constante) que una variable regular. Por lo tanto, emitiría todo tipo de código de fantasía para realizar un bucle óptimo en un número desconocido de veces, incluso cuando se conocía en tiempo de compilación, en el caso de que el contenedor fuera una variable miembro.

De la forma en que lo entiendo, este error probablemente afecta a un poco de código en el salvaje anywhere por ejemplo, en cualquier lugar un miembro pequeño array es el tema de un C++11 range-based for loop.

Gracias a Richard Biener por la pronta resolución (dirigida para GCC 8).

 2
Author: John Zwinck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-07 03:47:34