¿Por qué la construcción de std::opcional es más cara que un par std::?


Considere estos dos enfoques que pueden representar una " opción int":

using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;

Dadas estas dos funciones...

auto get_std_optional_int() -> std_optional_int 
{
    return {42};
}

auto get_my_optional() -> my_optional_int 
{
    return {42, true};
}

...tanto g++ trunk como clang++ trunk (con -std=c++17 -Ofast -fno-exceptions -fno-rtti) producir el siguiente conjunto:

get_std_optional_int():
        mov     rax, rdi
        mov     DWORD PTR [rdi], 42
        mov     BYTE PTR [rdi+4], 1
        ret

get_my_optional():
        movabs  rax, 4294967338 // == 0x 0000 0001 0000 002a
        ret

Ejemplo en vivo encendido godbolt.org


¿Por qué get_std_optional_int() requiere tres mov instrucciones, mientras que get_my_optional() solo necesita una única movabs? ¿Es esto un problema de QoI, o hay algo en ¿la especificación de std::optional impide esta optimización?

También tenga en cuenta que los usuarios de las funciones podrían estar completamente optimizados sin importar:

volatile int a = 0;
volatile int b = 0;

int main()
{
    a = get_std_optional_int().value();
    b = get_my_optional().first;
}

...resultados en:

main:
        mov     DWORD PTR a[rip], 42
        xor     eax, eax
        mov     DWORD PTR b[rip], 42
        ret
Author: Martin Ba, 2017-10-03

4 answers

Libstdc++ aparentemente no implementa P0602 "variante y opcional debe propagar la trivialidad de copiar/mover". Puede verificar esto con:

static_assert(std::is_trivially_copyable_v<std::optional<int>>);

Que falla para libstdc++, y pasa para libc++ y la biblioteca estándar MSVC (que realmente necesita un nombre propio para que no tengamos que llamarlo "La implementación de MSVC de la biblioteca estándar de C++" o "El MSVC STL").

Por supuesto MSVC todavía no pasará un optional<int> en un registro porque el MS ABI.

EDITAR: Este problema se ha solucionado en la serie de versiones de GCC 8.

 39
Author: Casey,
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
2018-09-27 16:08:05

¿Por qué get_std_optional_int() requiere tres mov instrucciones, mientras que get_my_optional() solo necesita un solo movabs?

La causa directa es que optional se devuelve a través de un puntero oculto mientras que pair se devuelve en un registro. ¿Por qué es eso, sin embargo? La especificación SysV ABI, sección 3.2.3 Parámetro Pasando dice:

Si un objeto C++ tiene un constructor de copia no trivial o un destructor no trivial, se pasa por invisible referencia.

Ordenar el desorden de C++ que es optional no es fácil, pero parece que hay un constructor de copia no trivial al menos en la clase optional_base de la implementación que revisé.

 17
Author: Jester,
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-10-03 14:21:32

En Llamando a convenciones para diferentes compiladores de C++ y sistemas operativos por Agner Fog se dice que un constructor de copia o destructor impide devolver una estructura en registros. Esto explica por qué optional no se devuelve en los registros.

Tiene que haber algo más que impida que el compilador realice la fusión de almacenes ( fusiona almacenes contiguos de valores inmediatos más estrechos que una palabra en menos almacenes más amplios para reducir el número de instrucciones)... Actualizar: gcc bug 82434 - - fstore-merging no funciona de forma fiable.

 15
Author: Maxim Egorushkin,
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-10-05 12:52:47

La optimización está técnicamente permitida , incluso con std::is_trivially_copyable_v<std::optional<int>> siendo false. Sin embargo, puede requerir un grado irrazonable de "inteligencia" para que el compilador encuentre. Además, para el caso específico de usar std::optional como el tipo de retorno de una función, la optimización puede necesitar hacerse en tiempo de enlace en lugar de en tiempo de compilación.

Realizar esta optimización no tendría ningún efecto en el comportamiento observable de ningún programa (bien definido),* y por lo tanto está permitido implícitamente bajo el como si la regla. Sin embargo, por razones que se explican en otras respuestas, el compilador no ha sido explícitamente consciente de ese hecho y tendría que inferirlo desde cero. El análisis estático del comportamiento es inherentemente difícil , por lo que el compilador puede no ser capaz de demostrar que esta optimización es segura en todas las circunstancias.

Suponiendo que el compilador puede encontrar esta optimización, entonces tendría que alterar la convención de llamada de esta función (es decir, cambiar cómo función devuelve un valor dado), que normalmente debe hacerse en el momento del enlace porque la convención de llamada afecta a todos los sitios de llamada. Alternativamente, el compilador podría insertar la función completamente, lo que puede o no ser posible hacer en tiempo de compilación. Estos pasos no serían necesarios con un objeto trivialmente copiable, por lo que en este sentido el estándar inhibe y complica la optimización.

std::is_trivially_copyable_v<std::optional<int>> debería ser verdad. Si fuera cierto, sería mucho más fácil para los compiladores para descubrir y realizar esta optimización. Entonces, para responder a su pregunta:

¿Es esto un problema de QoI, o hay algo en la especificación de std::optional que impide esta optimización?

Es ambas cosas. La especificación hace que la optimización sea sustancialmente más difícil de encontrar, y la implementación no es lo suficientemente inteligente como para encontrarla bajo esas restricciones.


* Asumiendo que no has hecho algo realmente raro, como #define int something_else.

 4
Author: Kevin,
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-10-08 19:44:02