C
NEO.PIXEL schrieb:
Was ist jetzt der Unterschied von swap_rename zu swap_mov und swap_xchg?
Was passiert da genau?
Ich habe mal den Testcode etwas geändert, und mit einer realistischeren Anwendung (bubble-sort) kombiniert. Außerdem das clobber-Register für swap_mov hinzugefügt:
#include <iostream>
#include <algorithm>
#include <ctime>
#ifdef NOINLINE
void swap_cpp(int& a, int& b) __attribute__ ((noinline));
void swap_mov(int& a, int& b) __attribute__ ((noinline));
void swap_xchg(int& a, int& b) __attribute__ ((noinline));
void swap_rename(int& a, int& b) __attribute__ ((noinline));
#endif
void swap_cpp(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
void swap_mov(int& a, int& b)
{
asm (
"movl %%eax, %%ecx\n\t"
"movl %%edx, %%eax\n\t"
"movl %%ecx, %%edx\n\t"
: "+a"(a), "+d"(b)
:: "%ecx" );
}
void swap_xchg(int& a, int& b)
{
asm (
"xchgl %0, %1"
: "+r"(a), "+r"(b) );
}
void swap_rename(int& a, int& b)
{
asm (
"## foo\n\t## bar"
: "=rm"(a), "=rm"(b)
: "1"(a), "0"(b) );
}
inline void show(const char* c, clock_t& s, clock_t& e)
{
std::cout << c << ":\t\t" << (e-s)/(0.001*CLOCKS_PER_SEC) << " ms\n";
}
#define SORT(begin, end,f) \
for ( int* p = end; p-- != begin; ) \
{ \
for ( int* q = begin; q < p; ++q ) \
{ \
if ( q[ 1 ] < q[ 0 ] ) \
f( q[ 0 ], q[ 1 ] ); \
} \
} \
void fill(int* begin, int* end)
{
for ( int i = end - begin ; begin != end; )
*begin++ = --i;
}
int main()
{
const unsigned L = 5e4;
int* begin = new int[L];
int* end = begin + L;
clock_t c_start, c_end;
#define MEASURE(f) \
fill( begin, end ); \
c_start = clock(); \
SORT( begin, end, f ); \
c_end = clock(); \
show( #f, c_start, c_end );
MEASURE( std::swap )
MEASURE( swap_cpp )
MEASURE( swap_mov )
MEASURE( swap_xchg )
MEASURE( swap_rename )
MEASURE( std::swap )
delete [] begin;
return 0;
}
Mit
g++ test.cpp -O3 --unroll-loops -march=atom -DNOINLINE -save-temps
übersetzt (4.5.2 gentoo 64-bit), sehen die Swapfunktionen so aus:
swap_cpp swap_mov swap_xchg swap_rename
.cfi_startproc .cfi_startproc .cfi_startproc .cfi_startproc
movl (%rdi), %eax movl (%rdi), %eax movl (%rdi), %edx movl (%rdi), %eax
movl (%rsi), %edx movl (%rsi), %edx movl (%rsi), %eax movl (%rsi), %edx
# 26 "test.cpp" 1 # 33 "test.cpp" 1 # 41 "test.cpp" 1
movl %eax, %ecx xchgl %edx, %eax ## foo
movl %edx, %eax ## bar
movl %ecx, %edx
# 0 "" 2 # 0 "" 2 # 0 "" 2
movl %edx, (%rdi) movl %eax, (%rdi) movl %edx, (%rdi) movl %edx, (%rsi)
movl %eax, (%rsi) movl %edx, (%rsi) movl %eax, (%rsi) movl %eax, (%rdi)
ret ret ret ret
.cfi_endproc .cfi_endproc .cfi_endproc .cfi_endproc
Wie man leicht erkennt, erzeugen swap_cpp und swap_rename hier optimalen Code. Der Grund dafür liegt auf der Hand: sofern nicht die gesamte Assembleranweisung wegoptimiert wird, erzeugt sie immer zweierlei Art von Code: einmal den eigentlich explizit spezifizierten, als auch einen Prolog/Epilog um die angegebenen Ein-/Ausgabeoperanden in das Programm zu integrieren. Letzteres kann durch den Compiler optimiert werden (der weiß was dieser Code tut), zudem wird er möglicherweise kürzer oder ganz überflüssig, je weniger streng die angegeben constraints sind.
Liegt etwa ein Eingabeargument ohnehin zunächst einmal irgendwo im Speicher vor und ist ein Speicheroperand zulässig, so können irgenwelche mov-Befehle bzgl. dieses Operanden ganz vermieden werden. Dieser zusätzliche vom Compiler generierte Code is prinzipiell nicht zu vermeiden, und je kürzer der eigentliche Assemblercode ist, umso mehr fällt dieser Overhead ins Gewicht. Das ist einer der Hauptgründe, weshalb die Verwendung von inline-Assembler für elementare Funktionen in aller Regel keinen Gewinn bringt.
Bei anderen Formen von inline-Assembler (ms vc++ u.ä.) können keine Constraints angegeben werden und der Compiler hat ein rudimentäres Verständis für die verwendeten Assemblerbefehle. Das hat zwei Effekte: der zusätzlich erzeugte Code beschränkt sich im Grunde auf das Sichern und Wiederherstellen von Registerinhalten (daraus folgt dann auch, dass diesbzgl. kaum Optimierungspotential existiert) und der selbst geschriebene Code, soweit er von irgendwelchen Parametern abhängt, ist regelmäßig weniger effizient.