Recientemente encontré algo similar a las siguientes líneas:
#include <string>
// test if the extension is either .bar or .foo
bool test_extension(const std::string& ext) {
return ext == ".bar" || ".foo";
// it obviously should be
// return ext == ".bar" || ext == ".foo";
}
La función obviamente no hace lo que sugiere el comentario. Pero ese no es el punto aquí. Tenga en cuenta que esto no es un duplicado de ¿Puede usar 2 o más condiciones O en una declaración if? ¡ya que soy completamente consciente de cómo escribirías la función correctamente!
Empecé a preguntarme cómo un compilador podría tratar este fragmento. Mi primera intuición habría sido que esto se compilaría return true;
básicamente. Al enchufar el ejemplo en godbolt , se demostró que ni GCC 9.2 ni clang 9 realizan esta optimización con optimización -O2
.
Sin embargo, cambiando el código a 1
#include <string>
using namespace std::string_literals;
bool test_extension(const std::string& ext) {
return ext == ".bar"s || ".foo";
}
parece hacer el truco ya que el ensamblaje ahora es en esencia:
mov eax, 1
ret
Entonces, mi pregunta principal es: ¿Hay algo que me haya perdido y que no permita que un compilador realice la misma optimización en el primer fragmento?
1 Con ".foo"s
esto ni siquiera se compilaría, ya que el compilador no quiere convertir un std::string
a bool
;-)
Editar
El siguiente fragmento de código también se optimiza "correctamente" para return true;
:
#include <string>
bool test_extension(const std::string& ext) {
return ".foo" || ext == ".bar";
}
string::compare(const char*)
tiene algunos efectos secundarios que el compilador no eliminará (queoperator==(string, string)
no tiene)? Parece poco probable, pero el compilador ya determinó que el resultado siempre es verdadero (también lo tienemov eax, 1
ret
) incluso para el primer fragmento.operator==(string const&, string const&)
esnoexcept
mientrasoperator==(string const&, char const*)
que no es? No tengo tiempo para profundizar más ahora.foo || ext == ".bar"
, la llamada se optimiza (ver edición). ¿Eso contradice tu teoría?a || b
significa "evaluar la expresiónb
solo si la expresióna
esfalse
". Es ortogonal al tiempo de ejecución o al tiempo de compilación.true || foo()
puede optimizarsetrue
, incluso sifoo()
tiene efectos secundarios, porque (sin importar si está optimizado o no) el lado derecho nunca se evalúa. Perofoo() || true
no se puede optimizar atrue
menos que el compilador pueda probar que las llamadasfoo()
no tienen efectos secundarios observables.xor eax,eax
aunque sin esa opción llama a la función de comparación de cadenas. No tengo idea de qué hacer con eso.Respuestas:
Esto te aturdirá aún más: ¿qué sucede si creamos un tipo de char personalizado
MyCharT
y lo usamos para hacer nuestro propio personalizadostd::basic_string
?Ciertamente, un tipo personalizado no se puede optimizar más fácilmente que un simple
char
, ¿verdad?Aquí está el ensamblaje resultante:
¡Véalo en vivo!
Entonces, ¿cuál es la diferencia entre mi tipo de cadena "personalizada" y
std::string
?Optimización de cuerdas pequeñas
Al menos en GCC, Small String Optimization se compila en el binario para libstdc ++. Esto significa que, durante la compilación de su función, el compilador no tiene acceso a esta implementación, por lo tanto, no puede saber si hay algún efecto secundario. Debido a esto, no puede optimizar la llamada a
compare(char const*)
distancia. Nuestra clase "personalizada" no tiene este problema porque el SSO se implementa solo de forma simplestd::string
.Por cierto, si compila con
-std=c++2a
, el compilador lo optimiza . Lamentablemente, no soy lo suficientemente inteligente en C ++ 20 aún para saber qué cambios hicieron esto posible.fuente