Woy schreef op dinsdag 06 december 2011 @ 10:30:
std::string is natuurlijk geen onderdeel van de compiler, maar van de library. Voor hetzelfde geld link je wel tegen een andere implementatie van de std lib, en dus kan de compiler die aanname niet maken.
Ik ben geen C++-expert, maar is het niet zo dat standaardfuncties door de compiler als built-ins behandeld mogen worden?
Voor ANSI C betekent dat dat de compiler in een lus als deze:
C:
1
2
3
4
5
6
| void foo(char *s)
{
for (size_t n = 0; n < strlen(s); ++n) {
/* blabla */
}
} |
.. de call naar strlen() buiten de lus mag halen omdat 'ie er vanuit mag gaan dat die geen side effects heeft, hoewel dat uit het standaardprototype van strlen() niet valt af te leiden.
(Dat is ook de reden dat calls naar goniometrische functies als sin(), cos() enzovoorts geen function calls opleveren maar direct omgeschreven worden naar instructires als fsin, fcos, enzovoorts. Dat mag de compiler niet doen voor user-defined functies.)
Nogmaals, ik weet niet zeker of datzelfde ook geldt voor C++, maar in dat geval zou de compiler wel de aanname mogen doen dat een std::string constructor geen side effects heeft. (Al betekent dat niet per se dat 'ie de constructor weg kan optimaliseren, natuurlijk.)
Dit is waar, maar de C++ standaard (in ieder geval die uit 1998 -- ik ben nog niet helemaal bij wat betreft de laatste versie) geeft sowieso weinig garanties wat betreft de implementatie en performancekarakteristieken van std::string. (Ik meen dat niet eens gegarandeerd is dat de karakters achter elkaar in het geheugen staan?)
De GCC implementatie is overigens niet single threaded; hij doet locking, maar alleen met atomaire operaties op een integer (de reference count). Als ik het juist heb (maar ik heb er maar heel kort naar gekeken) wordt bij het lezen daarvan helemaal niet gesynchroniseert en bij het schrijven alleen een bus lock gedaan; de overhead daarvan valt erg mee en het geheel is sowieso lockfree. Ik denk dat de performance dus wel in orde is.
Voor korte strings is de in-place representatie van MSVC wel handig natuurlijk, maar bij grotere strings levert dat alleen maar overhead op. Het zal een beetje verschillen per applicatie of je daar feitelijk veel profijt van hebt, en dat hangt ook weer samen met de efficiëntie van de heap allocator. (Ik vermoed dat het in het gros van de applicaties weinig nut heeft.)
De moraal van het verhaal is dat als je voorspelbaar geheugenmanagement wil, je beter af bent met een std::vector<char> dan met std::string, zeker als je niet van non-standaard features gebruik wil maken.
edit: met "niet single-threaded" bedoel ik hier dat meerdere threads op verschillende container objecten mogen werken. Verschillende threads op dezelfde container laten werken wordt v.z.i.w. niet ondersteund (in in MSVC ook niet, vermoed ik, aangezien het me onmogelijk lijkt dat te implementeren zonder locking).
Ik heb het even getest, en als ik met GC 4.5.3 deze code compileer:
C++:
1
2
| const std::string &bar();
bool f() { return bar() == "baz"; } |
Dan levert dat deze gegenereerde code op:
GAS:
1
2
3
4
5
6
7
8
9
10
11
12
13
| .LC0:
.string "baz"
foo():
subq $8, %rsp
call bar()
movl $.LC0, %esi
movq %rax, %rdi
call std::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(char const*) const
testl %eax, %eax
sete %al
addq $8, %rsp
ret |
Met andere woorden: er wordt geen std::string geconstrueerd en de vergelijking wordt omgeschreven naar een call naar std::string::compare() (waarschijnlijk door de operator== die je noemt te inlinen). Ik snap dus sowieso niet waarom de TS dit probleem zou hebben, tenzij hij de std::string van bar() niet by reference ontvangt, en er dus een temporary geconstruct wordt.
(Ongerelateerd: ik snap niet waarom de compiler hier 8 bytes op de stack alloceert terwijl die ruimte compleet niet gebruikt te worden. Wat mis ik?)
offtopic:
Dit is echt een situatie waarin ik graag zou mogen multiposten aangezien ik feitelijk drie verschillende reacties aan het typen ben.
[
Voor 61% gewijzigd door
Soultaker op 07-12-2011 17:47
]