Ein Fiber ist 'Lightweight'-Thread. Bei Threads übernimmt das Betriebssystem das Scheduling, bei Fibers hingegen der Benutzer. Wird z.B.aus Dateien gelesen (ReadFile(...)) oder auf einem Mutex (WaitForSingleObject(...)) gewartet, wechselt das Betriebssystem den Thread. Dieser Wechsel vom User- in den Kernel- Space und anschließend zurück sowie einen Thread-Context-Switch sind relativ teuer und außerdem gibt der Thread seine verbleibende Rechenzeit ab.
Kurz gesagt: Ein Fiber ist so etwas wie ein Thread im Thread. Anstatt den Thread nun zu blockieren, bis dieser weiterlaufen kann, kann der Benutzer dem Thread eine neue Fiber zuweisen, welche von an nun auf diesem weiterläuft und bei Gelegenheit wieder zur urspünglichen Fiber zurückspringt (z.B. mittels asynchronem IO und eigenem Mutex, was einen eigenen Fiber-Scheduler voraussetzt).
Es gibt eine
Fiber-Pool-Bibliothek welche solch einen Scheduler implementiert und bestimmte Anwendungen wie Dateikomprimierung (+30%) oder mp3-Codierung (+100%) durch bessere Prozessor- und Speicherauslastung sowie Userspacescheduling nicht ganz unerheblich beschleunigt.
Ein Fiber-Switch ist sehr günstig, da ausschließlich die aktuellen Register gesichert werden und dann der Stackpointer (esp-Register) gewechselt wird und die Register des Fibers wiederhergestellt werden. Der Cache ist wie beim Thread-Context-Switch allerdings nicht mehr warm.
Hierzu eine experimentelle boost-library:
|
Quellcode
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#define BOOST_COROUTINE_swapcontext(name) \
asm volatile ( \
".text \n\t" \
".global " #name "\n\t" \
".type " #name "@function\n\t" \
".align 16\n\t" \
#name":\n\t" \
"movl 16(%edx), %ecx\n\t" \
"pushl %ebp\n\t" \
"pushl %ebx\n\t" \
"pushl %esi\n\t" \
"pushl %edi\n\t" \
"movl %esp, (%eax)\n\t" \
"movl %edx, %esp\n\r" \
"popl %edi\n\t" \
"popl %esi\n\t" \
"popl %ebx\n\t" \
"popl %ebp\n\t" \
"add $4, %esp\n\t" \
"jmp *%ecx\n\t" \
"ud2\n\t") \
/**/
|