Overweeg deze twee benaderingen die een “optionele int
” kunnen vertegenwoordigen:
using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;
Gezien deze twee functies…
auto get_std_optional_int() -> std_optional_int
{
return {42};
}
auto get_my_optional() -> my_optional_int
{
return {42, true};
}
…zowel g++ trunkals clang++ trunk(met -std=c++17 -Ofast -fno-exceptions -fno-rtti
)produceer de volgende assembly:
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
Waarom vereist get_std_optional_int()
drie mov
instructies, terwijl get_my_optional()
slechts één movabs
?Is dit een QoI-probleem, of is er iets in de specificatie van std::optional
dat deze optimalisatie verhindert?
Houd er ook rekening mee dat gebruikers van de functies volledig kunnen worden geoptimaliseerd, ongeacht:
volatile int a = 0;
volatile int b = 0;
int main()
{
a = get_std_optional_int().value();
b = get_my_optional().first;
}
…resulteert in:
main:
mov DWORD PTR a[rip], 42
xor eax, eax
mov DWORD PTR b[rip], 42
ret
Antwoord 1, autoriteit 100%
libstdc++ implementeert blijkbaar geen P0602 “variant en optioneel zou de trivialiteit van kopiëren/verplaatsen moeten propageren”. U kunt dit verifiëren met:
static_assert(std::is_trivially_copyable_v<std::optional<int>>);
die faalt voor libstdc++ en doorgaat voor libc++ en de MSVC-standaardbibliotheek(die echt een goede naam zodat we het niet “De MSVC-implementatie van de C++-standaardbibliotheek” of “De MSVC STL”) hoeven te noemen.
Natuurlijk zal MSVC noggeen optional<int>
doorgeven in een register omdat de MS ABI.
EDIT: dit probleem is opgelost in de GCC 8-releaseserie.
Antwoord 2, autoriteit 43%
Waarom vereist
get_std_optional_int()
driemov
instructies, terwijl
get_my_optional()
heeft maar éénmovabs
nodig?
De directe oorzaak is dat optional
wordt geretourneerd via een verborgen aanwijzer terwijl pair
wordt geretourneerd in een register. Waarom is dat echter? De SysV ABI-specificatie, sectie 3.2.3 Parameters doorgevenzegt:
Als een C++-object een niet-triviale kopie-constructor heeft of a
niet-triviale destructor, het wordt doorgegeven door onzichtbare referentie.
Het oplossen van de C++-rommel die optional
is, is niet eenvoudig, maar er lijkt een niet-triviale kopie-constructor in ieder geval in de klasse optional_base
van de implementatie die ik heb gecontroleerd.
Antwoord 3, autoriteit 34%
In Aanroepconventies voor verschillende C++-compilers en besturingssystemen van Agner Fogstaat dat een kopie-constructor of destructor verhindert dat een structuur in registers wordt geretourneerd. Dit verklaart waarom optional
niet wordt geretourneerd in registers.
Er moet iets anders zijn dat de compiler ervan weerhoudt om store merging uit te voeren (voegt aaneengesloten winkels met directe waarden die kleiner zijn dan een woord samen in minder bredere winkels om het aantal instructies te verminderen)… Update:gcc bug 82434 – -fstore-merging werkt niet betrouwbaar.
Antwoord 4, autoriteit 7%
De optimalisatie is technisch toegestaan, zelfs als std::is_trivially_copyable_v<std::optional<int>>
false is. Het kan echter een onredelijke mate van “slimheid” vereisen voor de compiler om te vinden. Voor het specifieke geval van het gebruik van std::optional
als het retourtype van een functie, moet de optimalisatie mogelijk worden uitgevoerd tijdens de koppelingstijd in plaats van tijdens het compileren.
Het uitvoeren van deze optimalisatie zou geen effect hebben op het waarneembare gedrag van een (goed gedefinieerd) programma,* en is daarom impliciet toegestaan onder de als -if regel. Om redenen die in andere antwoorden worden uitgelegd, is de compiler echter niet expliciet op de hoogte gebracht van dat feit en zou hij het vanaf het begin moeten afleiden. Gedragsstatische analyse is inherent moeilijk, dus de compiler kan mogelijk niet bewijzen dat dit optimalisatie is onder alle omstandigheden veilig.
Ervan uitgaande dat de compiler deze optimalisatie kan vinden, zou hij de aanroepconventie van deze functie moeten wijzigen (dwz veranderen hoe de functie een bepaalde waarde retourneert), wat normaal gesproken moet worden gedaan tijdens de koppeling, omdat de aanroepconventie van invloed is op alle bel sites. Als alternatief kan de compiler de functie volledig inline maken, wat al dan niet mogelijk is tijdens het compileren. Deze stappen zouden niet nodig zijn met een triviaal kopieerbaar object, dus in die zin remt en compliceert de standaard de optimalisatie.
std::is_trivially_copyable_v<std::optional<int>>
zou waar moeten zijn. Als het waar zou zijn, zou het voor compilers veel gemakkelijker zijn om deze optimalisatie te ontdekken en uit te voeren. Dus, om je vraag te beantwoorden:
Is dit een QoI-probleem, of is er iets in de specificatie van
std::optional
dat deze optimalisatie verhindert?
Het is allebei. De specificatie maakt de optimalisatie aanzienlijk moeilijker te vinden en de implementatie is niet “slim” genoeg om deze onder die beperkingen te vinden.
* Ervan uitgaande dat je niet iets heel raars hebt gedaan, zoals #define int something_else
.