Vor Jahren hatte ich ein Raspberry Pi 1B als Server für ein paar kleine Dienste im Einsatz. Ersetzt habe ich das Teil aber später durch einen Fujitsu Futro s920, ein Thin-Client mit einer AMD G-Serie Quad-Core CPU (GX-415 GA). Die AMD G-Serie ist für embedded Computer entwickelt und daher hat der Fujitsu Thinclient auch eine recht niedrige Leistungsaufnahme. Zumindestens im Idle ist diese nicht viel höher als beim Raspberry Pi, aber dafür mit deutlich mehr Rechenleistung.
Ein Raspberry Pi 4 bietet ebenfalls eine Quadcore-CPU mit ähnlichem Takt. Daher hatte ich die Idee, mal ein bisschen die Hardware zu benchmarken. Und aus Spaß vergleiche ich das noch mit einer Sun Ultra 45 Workstation, die zumindestens einen ähnlichen CPU-Takt hat. Diese hat zwei UltraSPARC IIIi CPUs, ich glaube aus dem Jahr 2003.
Test-Hardware
- Raspberry Pi 1 Model B: CPUs (ARMv6): 1 x 700 MHz
- Raspberry Pi 4 Model B: CPUs (ARMv8): 4 x 1500 MHz
- Sun Ultra 45: CPUs (spar4u): 2 x 1600 MHz
- Fujitsu S920: CPUs (amd64): 4 x 1500 MHz
Sowohl beim Raspberry Pi 1 als auch 4 kommt ein 32bit Raspbian zum Einsatz. Vielleicht nicht optimal für die Performance, auf grund des wenigen RAMs (2 GB) aber praktisch vielleicht die bessere Wahl für den Produktivbetrieb.
Benchmarks
Als Integer-Benchmark wird die Schach-Engine stockfish benutzt. Stockfish bietet sehr gute Optimierung für x86 und auch ARM, aber nicht für sparc. Daher habe ich das ganze ohne spezielle Architekturoptimierungen kompiliert, also nur mit normaler Compiler-Optimierung.
stockfish bench 128 <num-cpus>
Als Floatingpoint-Benchmark kommt c-ray zum Einsatz. Dies ist ein einfacher in C geschriebener Raytracer.
c-ray-mt -t <num-cpus> -s 1000x1000 -r 10 -i scene -o out.ppm
Ergebnisse Stockfish
Die Ausgabe von stockfish bench
sieht so aus:
Total time (ms) : 14304
Nodes searched : 5607358
Nodes/second : 392013
Für den Vergleich nehm ich nur Nodes/second. Je größer hier der Wert ist, desto besser:
rpi1 u45-1cpu u45-2cpu rpi4-1cpu rpi4-4cpu s920-1cpu s920-4cpu
----------------------------------------------------------------------------
26503 277715 559029 392013 1421621 403146 1595700
26655 277386 559604 393222 1402173 402682 1530797
26623 277002 549077 385544 1424797 403261 1580684
26303 277152 566223 391739 1458121 402191 1551561
26926 277317 548266 391411 1435041 403146 1503410
Mittelwert mit Standardabweichung:
rpi1 26.602 ± 203,68
u45-1cpu 277.314,4 ± 240,66
u45-2cpu 556.439,8 ± 6.832,99
rpi4-1cpu 390.785,8 ± 2.691,36
rpi4-4cpu 1.428.350,6 ± 18.298,75
s920-1cpu 402.885,2 ± 400,12
s920-4cpu 1.552.430,4 ± 3.293,49
Ergebnisse C-Ray
C-Ray liefert am Ende die Anzahl der Millisekunden, die das Rendern gedauert hat.
rpi1 u45-1cpu u45-2cpu rpi4-1cpu rpi4-4cpu s920-1cpu s920-4cpu
----------------------------------------------------------------------------
88444 29585 16133 10015 3128 13699 4282
86406 29013 16112 10028 3118 13698 4189
87455 29014 16134 10032 3138 14068 4186
86456 29000 16106 10032 3119 14058 4189
86676 28998 16033 10004 3110 13701 4185
Mittelwert mit Standardabweichung:
rpi1 87.087,4 ± 775,59
u45-1cpu 29.122 ± 231,59
u45-2cpu 16.103,6 ± 37,01
rpi4-1cpu 10.022,2 ± 11,03
rpi4-4cpu 3.122,6 ± 9,58
s920-1cpu 13.844,8 ± 178,19
s920-4cpu 4.206,2 ± 37,93
Auswertung
Der Unterschied zwischen Raspberry Pi 1 und 4 ist gigantisch. Allein im Single-Core Benchmark ist ein rpi4 14 mal schneller als ein rpi1. Dafür bietet ein rpi4 aber statt einem Core gleich 4. Auch die FP-Geschwindigkeit ist deutlich höher. Aber das war auch nicht anders zu erwarten.
Spannender ist der Kampf zwischen rpi4 und Fujitsu s920. Diese sind in etwa auf Augenhöhe. Im Integer-Benchmark führt die AMD-CPU, im FP-Benchmark die ARM-CPU. Interessanterweise ist beim Stockfish-Benchmark die Standardabweichung beim s920 deutlich geringer als beim rpi4. Eine mögliche Ursache könnten die thermischen Probleme beim rpi4 sein, die zu einer Reduzierung des CPU-Takts führen könnten. Doch bei C-Ray ist es genau umgekehrt. Ich habe noch keine Erklärung dafür, wieso beim rpi4 hier die Werte so viel stabiler sind, als bei den anderen Testkandidaten.
Etwas schwach finde ich die Resultate bei der Ultra 45. Die Hardware ist natürlich schon etwas in die Jahre gekommen, aber da es sich hier nicht um kleine embedded CPUs handelt, hätte ich doch mehr erwartet. Eine Ursache könnte aber auch sein, dass ich auf der Ultra 45 ein recht altes OS (Solaris 10) betreibe und zum kompilieren hatte ich nur gcc 5.5 zur Verfügung. Auf dem rpi4 z.B. kam gcc 8.3 zum Einsatz.
Das war jetzt ein einfacher CPU-Benchmark. Interessant wäre ein IO-lastiger Benchmark. Die Ultra 45 wird nicht kampflos aufgeben. Gerade die Bus-Architektur ist beim rpi4 immer noch Schrott, daher könnte die Ultra 45 hier punkten. Fortsetzung folgt (irgendwann).
Wenn man ein Verzeichnis liest und von allen enthaltenen Dateien die Extended Attributes erhalten will, gibt es zwei Möglichkeiten:
- Man fügt zum Verzeichnispfad den Dateinamen hinzu und nutzt den neu erhaltenen Pfad mit den Syscalls
listxattr
oder getxattr
.
- Mit dem Filedescriptor des Verzeichnisses und
openat
öffnet man die Dateien und nutzt dann flistxattr
und getxattr
.
Ich hab mich gefragt was schneller ist. Dazu habe ich ein kleines Testprogramm geschrieben. Dieses kann mit unterschiedlichen Preprocessor-Optionen kompiliert werden. So habe ich 4 Testprogramme erstellt. Für getxattr
und fgetxattr
jeweils ein Programm, das ein Attribut liest und eines das 32 Attribute liest.
Bei einem Verzeichnis mit 128.000 Dateien hab ich folgende Werte erhalten:
getxattr:1 getxattr:32 fgetattr:1 fgetattr:32
------------------------------------------------
246100055 654704421 456172044 749849574
230183311 663632162 457183706 769223423
247109480 654775136 440397212 743349119
Die Datei erst zu öffnen um dann fgetxattr
zu nutzen ist also langsamer. Erst als ich das Programm so modifiziert habe, dass es mehrere hundert Attribute liest, war es etwas schneller. Das ist jedoch ein eher unrealistisches Szenario. Allerdings war das ganze generell sehr schnell, so dass es eigentlich egal ist, welche Methode man anwendet.
Ein einfaches Tool für Benchmarks von Festplatten bzw. Dateisystemen ist bonnie++. Man startet es einfach und es werden im aktuellen Arbeitsverzeichnis verschiedene Tests durchgeführt.
Hier mal ein Beispiel, auch wieder auf einem Raspberry Pi:
$ bonnie++
Writing with putc()...done
Writing intelligently...done
Rewriting...done
Reading with getc()...done
Reading intelligently...done
start 'em...done...done...done...
Create files in sequential order...done.
Stat files in sequential order...done.
Delete files in sequential order...done.
Create files in random order...done.
Stat files in random order...done.
Delete files in random order...done.
Version 1.03e ------Sequential Output------ --Sequential Input- --Random-
-Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP /sec %CP
raspi.local 1G 3565 95 20890 44 9180 19 3822 97 24020 27 1060 23
------Sequential Create------ --------Random Create--------
-Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
files /sec %CP /sec %CP /sec %CP /sec %CP /sec %CP /sec %CP
16 3930 96 +++++ +++ 6338 106 3920 94 +++++ +++ 5559 96
raspi.local,1G,3565,95,20890,44,9180,19,3822,97,24020,27,1059.6,23,16,3930,96,+++++,+++,6338,106,3920,94,+++++,+++,5559,96
Die Tests mit putc()
und getc()
sind recht CPU-lastig, da hier Zeichen einzelnd geschrieben oder gelesen werden. Die block-basierten Tests hingegen zeigen gut den IO-Durchsatz.
Was auch auffällt, bei den Create-Tests fehlen die Werte für Read. Dies liegt daran, das in meinem Fall die Tests zu schnell fertig waren und dadurch keine genauen Ergebnisse berechnet werden können. Bonnie++ zeigt daher lieber keinen Wert an.
Mit iperf kann man sehr einfach sein Netzwerk benchmarken. Dazu startet man auf einem Computer einen iperf-Server:
$ iperf -s
------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------
Und auf einem anderen Computer startet man den iperf-Client und gibt den Server an (in diesem Fall rpi):
$ iperf -c rpi
------------------------------------------------------------
Client connecting to rpi, TCP port 5001
TCP window size: 85.0 KByte (default)
------------------------------------------------------------
[ 3] local 192.168.178.170 port 39890 connected with 192.168.178.28 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 114 MBytes 95.5 Mbits/sec
Man sieht, ein Raspberry Pi 1 kann sein 100 Mbit/s LAN gut auslasten. Allerdings hat das bei mir auch zu 75% CPU-Last geführt.
Der Syscall stat wird benutzt um an Informationen wie Dateigröße oder Änderungsdatum einer Datei zu gelangen. Die Funktion erwartet als Argumente nur einen Dateipfad und einen Buffer. Daneben gibt es noch die Variante fstat, die statt eines Dateipfades einen Filedescriptor erwartet, und fstatat, die den Filedescriptor eines geöffneten Verzeichnisses und einen Pfad relativ zu dem Verzeichnis erwartet.
int stat(const char *restrict path, struct stat *restrict buf);
int fstat(int fildes, struct stat *buf);
int fstatat(int fd, const char *restrict path,
struct stat *restrict buf, int flag);
Mich hat jetzt ein Performancevergleich zwischen den Funktionen interessiert. Zum einen für den Fall, dass man für jede Datei in einem Verzeichnis stat
aufrufen will. Hier würde sich der Einsatz von fstatat
anbieten. Der andere Fall wäre, wenn man eine Datei öffnen und stat-en will. Um dies zu testen hab ich ein primitives Programm geschrieben, dass man hier findet. Das Programm erwartet als Argument einen Pfad zu einem Verzeichnis, welches es zunächst ließt. Danach führt es 4 Tests durch und misst für jeden die Zeit:
- test_stat: Für jede Datei des Verzeichnisses wird
stat
aufgerufen.
- test_fstatat: Für jede Datei wird
fstatat
aufgerufen.
- test_open_stat: Jede Datei wird mit
open
geöffnet und danach noch stat
aufgerufen.
- test_fstat: Jede Datei wird geöffnet und
fstat
aufgerufen.
Ein kleiner Test unter Linux ergab:
test 195 files:
test_stat
time: 496386 ns
test_fstatat
time: 246039 ns
----------------------------------------
test_open_stat
time: 1106593 ns
test_open_fstat
time: 885549 ns
Ähnliche Ergebnisse konnte ich auch unter FreeBSD und Solaris, mit Festplatten und SSDs, reproduzieren. Der Vergleich zwischen test_stat und test_fstatat zeigt, dass fstatat
deutlich schneller ist. In beiden Tests wird auch nur pro Datei jeweils ein Syscall aufgerufen. Bei test_open_stat und test_open_fstat wird in beiden Fällen zunächst open
benutzt um die Datei zu öffnen und es zeigt sich, dass in diesem Fall fstat
auch schneller ist als stat
, allerdings fällt der Unterschied hier nicht so sehr ins Gewicht.