Seit 2015 schreibe ich alles im Blog auf Deutsch und Englisch. Das ist sehr zeitaufwändig und hat fragwürdigen Nutzen.
Bis auf Weiteres geht es hier auf Englisch weiter:
Falls es Dinge aus der Rubrik „Lokales“ geben sollte, dann landen die natürlich weiterhin hier.
Mein derzeitiger Intel i7-3770 unterstützt „x86-64“. Wenn ich also Software installieren will, muss ich das im Hinterkopf behalten. In der Linux-Welt ist das aber meistens nur für das Betriebssystem selbst relevant.
Der Haken an der Sache ist, dass „x86-64“ etwa 20 Jahre alt ist. Seit damals gab es so einige Erweiterungen. Wie immer ist das ziemlich chaotisch: Manche CPUs unterstützen jene Features, andere andere. Wenn man also heute eine CPU mit „x86-64“ kauft, dann ist das nicht mehr so wirklich dasselbe wie eine mit „x86-64“ von 2005 oder auch nur 2015. Software, die also heute für eine moderne CPU kompiliert wird, läuft vielleicht auf einer älteren nicht, obwohl beide dieselbe „Architektur“ haben.
Präziser: Alle CPUs haben dieselbe Grundmenge an Instruktionen – und neuere CPUs haben zusätzliche Instruktionen. Wenn man eine dieser neueren Instruktionen auf einer alten CPU ausführt, kriegt man ein SIGILL. Und das Problem ist jetzt, dass niemand jemals so wirklich definiert hat: „Wenn du die Grundmenge und dann noch dies und das und jenes unterstützt, dann darfst du es als ‚x86-64-foobar‘-CPU bezeichnen.“
Etwas ähnliches ist damals in der 32-Bit-PC-Welt passiert: i386, i486, i586, i686, … Ich meine mich zu erinnern, dass es zwischen i386 und i486 eine klare Trennung gab (vielleicht irre ich mich auch, ist lange her), danach wurde es aber auch hier verwirrend. „i586“ war nicht so wirklich beliebt im Marketing, also nannten sie das Ding lieber … „Pentium“. Das „i686“ danach war auch ziemlich schwammig und wie „i586“ auch kein offizieller Intel-Name.
Zurück zu „x86-64“.
Arch Linux hat mal „Marketing“ betrieben als „optimierte“ Distribution. Damals war damit i686 gemeint. Irgendwann kam dann x86-64 und Ende 2017 viel i686 ganz weg, seitdem war’s nicht mehr „optimiert“. Es war einfach x86-64, weil das ohnehin modern genug war. Aber wie oben erwähnt, gab es seit dem originalen x86-64 Neuerungen und daher fragen sich einige Arch-Entwickler, ob man nicht mal wieder „optimiert“ sein sollte. (Dabei geht es nicht um Marketing, sondern darum, seine CPU möglichst gut auszunutzen. Arch ist ein Community-Projekt, niemand profitiert von Millionen neuer User.)
Aber optimiert für was? Es gibt kein „i786“ oder „i886“ oder sowas in der Richtung. Bis sich dann wohl kürzlich an dieser Situation etwas änderte:
https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst
Das ist ein Arch-interner RFC und er erwähnt, dass es nun „x86-64-v2“ bis „x86-64-v4“ gibt. Ich kopiere den relevanten Teil mal hier her, falls irgendwann die obige URL nicht mehr funktioniert:
The first of these microarchitecture levels, x86-64-v2, assumes the following on top of base level x86_64 instructions:
CMPXCHG16B, LAHF-SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3.
This basically raises the processor feature level requirement to around Intel Nehalem, and supports any x86_64 processor made in the last decade.
The x86-64-v3 microarchitecture requires the following instruction sets:
AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, XSAVE.
That is close to a Haswell processor, but does exclude some recent low end Intel CPU that removed AVX support.
Finally, x86-64-v4 requires:
AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL
Interessant daran ist, dass das keine Sachen sind, die sich die Arch-Entwickler ausgedacht haben, sondern das kommt von „AMD, Intel, Red Hat und SUSE“. Also können wir darauf hoffen, dass diese Kategorien zum neuen „Standard“ werden, vielleicht so ähnlich wie damals mit i386 und i486. Die Wikipedia nennt diese E-Mail von Florian Weimer auf der LLVM-Mailingliste als die Quelle (oder eher den Ursprung der Diskussion).
In der glibc kann man das Microarchitecture-Level wie folgt ausgeben
lassen, sodass man nicht manuell in /proc/cpuinfo
die Flags abgrasen
muss:
$ /lib/ld-linux-x86-64.so.2 --help
Subdirectories of glibc-hwcaps directories, in priority order:
x86-64-v4
x86-64-v3
x86-64-v2 (supported, searched)
In meinem Fall habe ich also x86-64-v2. Tja, die Kiste ist auch schon 9 Jahre alt. Mein NUC von 2017 mit seinem Celeron N3050 ist auch nur auf x86-64-v2. Mein Netbook von 2011 mit seinem Intel Atom N455 ist noch nicht einmal x86-64-v2, sondern einfach nur x86-64. Auf der anderen Seite steht mein Notebook von 2014 mit seinem i7-4710MQ schon auf x86-64-v3. Aber nicht einmal mein Notebook auf der Arbeit mit seinem i7-8665U von 2019 hat x86-64-v4.
Für den Moment denkt man bei Arch nur darüber nach, x86-64-v3 als zusätzliches Level zu unterstützen. Ich würde auch davon ausgehen, dass der x86-64-v3-Port dann lange parallel zum „alten“ x86-64 existieren wird. Es gab im Thread auf der Mailingliste in 2020 starken Widerstand gegen die vollständige Entfernung. Früher oder später wird das natürlich passieren, i386 gibt es in den verschiedenen Distributionen auch kaum mehr, aber so weit sind wir hier noch nicht.
Via The Gopher Times – Opus 3:
The computer built to last 50 years von Ploum. Eine sehr ansprechende Idee.
master
→ main
in meinen Git-Repos ab 2022-06-01
master
→ main
in meinen Git-Repos ab 2022-06-01Ende letzten Jahres habe ich einmal alle meine Git-Repos geändert. Der
Haupt-Branch heißt jetzt main
. Die alten master
-Branches habe ich
erstmal beibehalten und einfach immer auf denselben Stand wie main
zeigen lassen aus Kompatibilitätsgründen.
Am 2022-06-01 werden die master
-Branches entfernt werden. Ab dem
Zeitpunkt geht dann nur noch main
.
Falls du ein Repo von mir geklont hast und auf den neuen Branch wechseln willst, dann klone es entweder komplett neu oder führe diese Schritte aus:
$ git checkout master
$ git branch -m main
$ git fetch
$ git branch -u origin/main
$ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
Es ist sehr einfach für mein Git-Tooling, die alten master
beizubehalten, daher die lange Übergangsphase von 1.5 Jahren. Aber
irgendwann muss mal aufgeräumt werden.
Vorheriger Blogpost zu diesem Thema.
twtxt ist ein einfaches Format für einen „Microblog“. So ein bisschen wie Twitter oder RSS/Atom-Feeds, aber auf das absolute Minimum reduziert. Seit es in 2016 das Licht der Welt erblickte, hat es sich ein bisschen verändert – hauptsächlich durch Threading, das 2020 von Yarn.social (früher twtxt.net) eingeführt wurde. Es gab in 2016 und den Folgejahren schon so einige twtxt-Clients, aber nach meinem Kenntnisstand wurde keiner von denen dann angepasst, um diese Erweiterung zu unterstützen. Was macht man heutzutage also, wenn man eine hübsche Konsolenexperience mit twtxt haben will?
Eine Möglichkeit ist jenny, was ich selbst auch benutze.
In jennys README sind Einrichtungsanweisungen enthalten, aber ich mache jetzt trotzdem mal ein Walkthrough mit Screenshots dafür.
jenny soll eigentlich auf dem lokalen Rechner laufen und nicht auf deinem Server im Internet, also machst du lokal das hier:
$ git clone https://uninformativ.de/git/jenny.git
$ cd jenny
$ git checkout v22.01
Damit bist du auf der Version, die zum Zeitpunkt des Schreibens dieses
Blogposts aktuell war. In der Zwischenzeit kann es natürlich Änderungen
gegeben haben. Wenn du möchtest, kannst du selbstverständlich auch
neuere Releases oder den aktuellen main
-Stand benutzen, aber dann lies
bitte in CHANGES
nach, was sich seitdem getan hat. Dieser Blogpost
wird nicht an neuere Releases angepasst werden.
Dann die Abhängigkeiten über die Distribution installieren:
# pacman -Syu python-dateutil python-requests
Das sollte jetzt funktionieren:
$ ./jenny --help
Oder man macht irgendwas Verschnörkeltes mit virtualenv oder so.
Man fange mit einer leeren Textdatei auf dem lokalen Rechner an. Nagut, ganz leer ist sie nicht, sondern sie muss mindestens zwei Zeilen an Metadaten enthalten:
# nick = cathy
# url = https://my.server.com/twtxt.txt
Unter der Annahme, dass lokal auf dem Rechner in ~/web/my.server.com
die gesamte eigene Webseite liegt, speichere man den Feed also unter
~/web/my.server.com/twtxt.txt
ab.
jenny wird neue twts dort anhängen. Danach führt es einen Befehl aus, um
die Datei tatsächlich im Internet zu veröffentlichen. Beides muss in der
obligatorischen Config-Datei ~/.config/jenny/conf.json
eingestellt
werden:
{
"maildir_target": "~/Mail/twt",
"local_twtxt_dir": "~/web/my.server.com",
"publish_command": "some-rsync-script"
}
publish_command
ist ein Skript, das du notwendigerweise selbst
schreiben musst, weil es ganz und gar von deinem Setup abhängt. Du
kannst hier einfach scp
benutzen, um die Datei auf deinen Server zu
kopieren, und fertig. Oder du machst git commit
und git push
irgendwo hin. Oder du setzt es eben doch auf /bin/true
, weil du dich
entschieden hast, das hier eben doch alles auf deinem öffentlichen
Server auszuführen. Hängt alles von dir ab.
Sobald das alles erledigt ist, führe man testweise jenny
aus: Es
sollte dich in deinen $VISUAL
-Editor werfen, in dem du einen neuen twt
verfassen kannst. Für den Moment schreibst du einfach hello world
rein
und beendest den Editor. Danach prüfst du in
~/web/my.server.com/twtxt.txt
, ob der neue Eintrag angekommen ist.
Ebenso in https://my.server.com/twtxt.txt
nachschauen, ob er
erfolgreich im Internet gelandet ist.
An diesem Punkt angekommen, kann man nun twts schreiben und veröffentlichen. Aber wahrscheinlich will man auch die Posts anderer Leute lesen. Hier kommt dann mutt ins Spiel.
mutt ist eigentlich ein Mail-Client und hat gar nichts mit twtxt zu tun. Das Ding ist aber, dass mutt sehr gut darin ist, Gesprächsverläufe in Threads anzuzeigen und handzuhaben. Außerdem ist es wunderbar anpassbar. Daher schreibt jenny die daten in ein maildir und mutt arbeitet dann darauf. Normalerweise startet man daher nicht jenny direkt sondern mutt – und das ruft dann jenny auf.
Hier ist eine komplette Konfiguration für mutt mit integrierter jenny
(welches nun im $PATH
enthalten sein muss):
# Strictly necessary or highly recommended.
set mbox_type = Maildir
set sort = threads
set sort_aux = date-sent
set strict_threads = yes
# Optional styling.
color index yellow default ~P
set index_format = "%Z│%-15.15F│%3M│%s %* │%[%Y-%m-%d %H:%M]"
# To highlight messages sent by you.
alternates cathy
# Key binds for jenny: Alt+T to write a new twt, Alt+R to reply to
# thread, Alt+F to reply in a forked thread.
macro index,pager <esc>T "<shell-escape> jenny<Enter>" "Write new twt"
macro index,pager <esc>R "\
<enter-command> set my_pipe_decode=\$pipe_decode nopipe_decode<Enter>\
<pipe-message> jenny<Enter>\
<enter-command> set pipe_decode=\$my_pipe_decode; unset my_pipe_decode<Enter>" \
"Reply to current twt"
macro index,pager <esc>F "\
<enter-command> set my_pipe_decode=\$pipe_decode nopipe_decode<Enter>\
<pipe-message> jenny -t<Enter>\
<enter-command> set pipe_decode=\$my_pipe_decode; unset my_pipe_decode<Enter>" \
"Reply to current twt (fork conversation)"
Und dann kann man mutt aufrufen (hierfür wird man natürlich irgendeinen Shortcut einrichten wollen):
$ mutt -F ~/.muttrc-jenny -f ~/Mail/twt
An Leute, die schon mutt benutzen: Dieses ganze Setup kann unabhängig und parallel zum existierenden mutt-Setup benutzt werden.
Das gibt es seit einiger Zeit auch. Ist aber nicht zwingend nötig für
diese kurze Einleitung, daher sei auf das vim/
-Verzeichnis im Repo
verwiesen, dort gibt es eine README
dazu.
Nach dem Start von mutt sieht man irgendwie sowas:
Diese twts hat jenny erstellt, während ich während der Setup-Schritte herumgespielt habe.
Wählen wir mal den letzten Eintrag aus und drücken Alt+Shift+R
, um zu
antworten. mutt startet jetzt jenny, was einen wiederum in den Editor
wirft:
Man sieht hier unter anderem eine „Snip“-Zeile. Alles darüber wird
dein neuer twt werden. Der Bereich über der Snip-Zeile wird manchmal von
jenny vorausgefüllt. Es ist üblich, in Antworten eine @-Mention des
ursprünglichen Autors zu setzen, was jenny dann automatisch tut. Einen
Subject-Hash wie (#mh4qzda)
, den jenny berechnet, muss man zwingend
benutzen, damit es ein Thread wird. Speichern, beenden, dann sieht man
soetwas:
Man beachte, wie mutt hier auch hübsch einen Thread darstellt.
Neue twts zu schreiben, geht dann ganz ähnlich. Man muss nur den anderen Hotkey in mutt drücken.
Um jemand anderem zu folgen, erstelle man die Datei
~/.config/jenny/follow
. Das Format ist:
$name $url $optionale_flags
Beispielsweise:
sandra https://www.uninformativ.de/twtxt-blog-example.txt
Ja, leider muss der Name des anderen Users in der eigenen Config
auftauchen. Das twtxt-Protokoll sieht nicht zwingend vor, dass alle
Leute eine nick
-Zeile in ihrem Feed haben (aber wenn sie existiert,
wertet jenny sie auch aus).
Die optionalen Flags ignorieren wir jetzt einmal, mehr dazu in der README.
Damit diese Feeds auch tatsächlich abgerufen werden, braucht es noch einen Cronjob, einen systemd-Timer oder irgendetwas anderes, das regelmäßig das hier ausführt:
$ jenny -f
Für dieses Beispiel führen wir jetzt mal jenny -f
manuell aus. Dann
wieder mutt starten und es sollte so aussehen:
Jetzt ergeben auch die Farben Sinn. Gelb sind die eigenen twts. Das „📣“ ist ein Zeichen dafür, dass man selbst hier erwähnt wurde.
Und das war’s dann auch schon. Glückwunsch und Willkommen im Twtiverse!
Wenn du bei twtxt mitmachen willst, ohne selbst großartig etwas zu tun (kein Self-Hosting, keine hübschen nerdigen Terminal-Spielzeuge), dann kannst du einfach einem der existierenden Yarn.social-Pods beitreten, zum Beispiel https://twtxt.net/. Account erstellen, fertig. Dafür gibt es dann sogar eine App für Android oder iOS, nennt sich Goryon.
Aber der Punkt von twtxt ist eigentlich, dass man es selbst hosten kann. Deswegen ist das aus technischer Sicht so einfach. Wenn du also technisch dazu in der Lage bist, solltest du Self-Hosting wählen.
Und wenn du trotzdem keine Lust auf so ein „uraltes“ Konsolenprogramm hast, dann kannst du auch deinen eigenen Yarn.social-Pod für eine Rundumsorgloswebanwendung aufsetzen.
perf stat
und Hyper-Threading
perf stat
und Hyper-ThreadingIm letzten Blogpost ging es um „Hintergrundlast“ und ihren Einfluss auf die Ausführungszeit von anderen Prozessen (im Wesentlichen Reduzierung der CPU-Frequenz, um Überhitzung zu vermeiden). Mein Fazit war dann, dass die Messung der CPU-Zyklen ein besserer Weg ist.
Das ist zwar besser, aber immer noch sehr problematisch, insbesondere wenn man es auf modernen CPUs mit Standardkonfiguration einsetzt.
Weil ich ein bisschen paranoid bin, ist Hyper-Threading bei mir üblicherweise deaktiviert. Wahrscheinlich ist das nicht wirklich notwendig in meinen Szenarien, aber so sehr tut es auch nicht weh. Meine Kisten, obgleich von 2013/2014, sind eh mehr als schnell genug für mich. Eine „Standardkonfiguration“ ist das dann also nicht. „Standardkonfiguration“ wäre, Hyper-Threading angeschaltet zu haben.
Weil das bei mir aus war, blieb mir der folgende Effekt zunächst verborgen.
Erstmal ein kleines Programm für Linux/amd64:
.global _start
_start:
movq $1000000, %rax
.L0:
decq %rax
cmp $0, %rax
jne .L0
/* _exit(0) */
movq $60, %rax
movq $0, %rdi
syscall
Übersetzen mit:
$ as -o prog.o prog.s
$ ld -o prog prog.o
Das tut nichts Nützliches. Es wird nur eine Million Mal der Wert eines Registers um eins verringert. Der Sinn ist nur, ein Programm zu haben, von dem man ganz gut weiß, wie viele Instruktionen es ausführt:
.L0
.In Summe müssten wir also 1 + 3 * 1000000 + 3
sehen. Mal schauen:
$ perf stat -e instructions:u ./prog
Performance counter stats for './prog':
3,000,005 instructions:u
Na sowas, eine zu viel. Naja, egal, gut genug für dieses Beispiel. :)
Zur Erinnerung, unterschiedliche Instruktionen können unterschiedlich viel „kosten“, weshalb es nicht ausreicht, einfach nur diese Zahl in der Messung zu erfassen. Deswegen hat der vorige Blogpost die Zahl der CPU-Zyklen eingeführt:
$ perf stat -e cycles:u,instructions:u ./prog
Performance counter stats for './prog':
1,001,300 cycles:u
3,000,005 instructions:u # 3.00 insn per cycle
Was ich jetzt gehofft hatte, war, dass cycles:u
relativ stabil
bleibt, egal, was sonst noch auf dem System los ist. (Diese Annahme
funktioniert nur bei rein CPU-gebundenen Aufgaben.)
Wenn Hyper-Threading aus ist, stimmt das auch. Ist es aber an und laufen im Hintergrund noch andere Dinge, dann sieht man das:
$ while true; do true; done ← das hier in ein paar anderen Terminals
$ perf stat -e cycles:u,instructions:u ./prog
Performance counter stats for './prog':
1,971,297 cycles:u
3,000,005 instructions:u # 1.52 insn per cycle
So ein Elend. Die Zahl der Instruktionen bleibt gleich, aber die CPU braucht jetzt mehr Zyklen, um sie auszuführen.
Ein Blick auf die volle Ausgabe von perf stat
liefert einen Hinweis:
$ perf stat ./prog
Performance counter stats for './prog':
0.61 msec task-clock:u # 0.641 CPUs utilized
0 context-switches:u # 0.000 /sec
0 cpu-migrations:u # 0.000 /sec
1 page-faults:u # 1.652 K/sec
2,020,909 cycles:u # 3.339 GHz
———————→ 1,212,002 stalled-cycles-frontend:u # 59.97% frontend cycles idle
3,000,005 instructions:u # 1.48 insn per cycle
# 0.40 stalled cycles per insn
1,000,002 branches:u # 1.652 G/sec
4 branch-misses:u # 0.00% of all branches
CPUs sind heute unglaublich komplex. Es wird jetzt ein bisschen schwer, herauszufinden, warum genau das passiert. Mir ist kein Weg bekannt, um mir anzuschauen, was die CPU da intern tut.
Es gibt diese Doku:
Da habe ich mich noch nicht komplett durchgegraben. Nach dem, was ich bisher verstehe, stand mein Programm für 1212002 Zyklen still, weil, naja, die CPU „irgendwas anderes“ gemacht hat. Was genau, weiß ich nicht. Wer das detailliert erklären kann, möge sich gerne melden.
Dieses Phänomen tritt nur auf, wenn
logical_cores / 2 - 1
laufende Hintergrundprozesse gibt.– Edit: Ursprünglich stand hier real_cores
, das war falsch. Gemeint
ist die Anzahl der logischen Cores, die man zum Beispiel in htop
sieht. Beispiel: 8 logische Cores in htop
mit aktivem HT → wenn mehr
als 3 Hintergrundprozesse aktiv sind, zeigt sich der Effekt.
time(1)
ist keine gute Metrik für „wie viel Arbeit hat die CPU
verrichtet“, um eine Aufgabe zu erledigen. Hintergrundprozesse
können Thermal-Throttling anstoßen und damit den zu messenden
Prozess negativ beeinflussen.perf stat -e cycles:u
ist auch nicht besonders gut, falls
Hyper-Threading an ist. „Hyper-Threads“ sind nunmal keine echten
Kerne und können die Ausführung anderer Prozesse dann auch wieder
negativ beeinflussen.cycles:u
hat wenig Aussagekraft. Man kann
daran nicht ablesen, wie viele dieser Zyklen die CPU gearbeitet hat
und wie viele sie verschwendet hat. Es ist lediglich die Zahl der
Zyklen, die unserem Prozess zugeschrieben wurden. stalled-cycles-frontend:u
abziehen, wenn Hyper-Threading aktiviert ist.cycles:u
ist wohl doch nicht „die Summe aller Cycles“, wie man zum
Beispiel an perf stat date
sehen kann, wo
stalled-cycles-frontend:u
größer als cycles:u
ist …Es ist wirklich nur die Spitze des Eisbergs. Es sind noch so viele
weitere Komponenten involviert. „Einfache Benchmarks“, wie ich sie
ursprünglich mit time(1)
durchführen wollte, sind deutlich
komplizierter, als man meinen mag, vor allem wenn das System mehr als
eine Sache zu einer Zeit tut (was im Jahre 2021 mit ziemlicher
Sicherheit der Fall ist).