Neulich traten die hiesigen Bürgermeisterkandidaten zu einer Podiumsdiskussion an. Motto: Nachhaltigkeit.
Es ging um Mobilität, Wohnen, Teilhabe. Gut und schön, aber es gibt Aspekte, auf die Bürgermeister wenig Einfluss haben.
Zwei Beispiele:
Mein Acer-Laptop ist ein paar Jahre alt und war nicht ganz billig, er enthält zwei Grafikchips, einer spart Strom, einer zum Spielen. Leider unterstützt Windows 10 den besseren Chip nicht, der Hersteller hat natürlich auch null Motivation, einen neuen Treiber zu schreiben. Die Kiste ist also zum Spielen nicht mehr zu gebrauchen. Wer da die Abwärtskompatibilität abgesägt hat, kriegt von mir die verschimmelte Himbeere der Nichtnachhaltigkeit.
An meinem Arbeits-PC hängt ein ziemlich cooler (da ständig blau blinkender) WLAN-Stick von Asus mit toller Reichweite. Leider wird der enthaltene Chip von Linux-Kerneln ab 5.4 nicht mehr unterstützt. Würde ich also das (natürlich aus Sicherheitsgründen empfohlene) Update durchführen, müsste ich entweder Stunden in diffiziles Herumgefummel an komplizierten Treiber-Quellcodes investieren oder den alten WLAN-Stick wegschmeißen und einen neuen kaufen. Gerade Linux als Open Source-Betriebssystem sollte sich meiner Ansicht nach solche Fehler nicht erlauben. Die nächste verschimmelte Himbeere.
Verbunden sei dies mit dem Aufruf, nur dann auf Abwärtskompatibilität zu verzichten, wenn dadurch keine Hardware obsolet wird.
Ich bin ja Experte darin, Dinge in einen Topf zu werfen, die sonst nix miteinander am Hut haben. Anders ausgedrückt:
Wenn man Müsli, Tomatensoße, Klebstoff und Lötzinn kräftig aufkocht, kommt wohl etwas wie das hier dabei raus:
Das ist … erklärungsbedürftig.
Das Bild zeigt eine App, an der ich gerade arbeite, und die demnächst ins Licht der Öffentlichkeit treten wird (ich teste und optimiere noch wild dran herum). Hinter dem klobigen Namen „Visual Forth for Arduino“ verbirgt sich eine Art grafische Variante der Programmiersprache Forth. Ähnlich wie Scratch kann man Befehle als Klötzchen untereinander kleben, um ein Programm zu erzeugen. Der Clou daran: Dieses Programm läuft dann per Knopfdruck auf einem ganz normalen Arduino Nano, der über ein OTG-Kabel via USB am Tablet hängt. Einzige Voraussetzung: Der Arduino wurde zuvor mit Finf bespielt. Finf („Finf is not Forth“) ist eine kleine Forth-Umgebung für Arduino, Open Source, ursprünglich von Leandro A. F. Pereira, wird aber von mir weiterentwickelt (und korrigiert, räusper). Meine App erzeugt also aus den Klötzchen Forth-Code (oder besser: Finf-Code und lässt ihn auf dem Arduino laufen.
Vorteil im Vergleich zu Scratch für Arduino: Das Programm funktioniert auch dann noch, wenn man den Arduino aus dem Tablet stöpselt und einfach an eine Stromversorgung anschließt! (In der Premium-Version meiner App, har har.)
Jetzt könnt ihr ja mal raten, was obiger Forth-Code auf dem Arduino tut.
Auflösung
Wenn man den mittleren Anschluss eines Spannungsteilers mit einem normalen und einem lichtempfindlichen Widerstand an Eingang A2 anschließt und eine Leuchtdiode an D2, dann geht die LED an, wenn es dunkel wird.
An der Einrücktiefe erkennt man übrigens die Stapel-Höhe vor dem jeweiligen Befehl. So hinterlässt der Analogread-Befehl seinen Messwert natürlich (symbolisch sichtbar anhand der Einrückung darunter) auf dem Stapel. Daher „visuell“ – denn wer mal versucht hat, Forth zu programmieren, ist entweder verrückt geworden oder hat sich dergleichen aufgemalt (Mehr zum Thema „Forth“ und „verrückt“ in diesem Buch).
KI hier, KI da – Autos sollen sie fahren, in der Medizin beraten oder gleich den ganzen Laden übernehmen. Wie schlecht Deep Learning dazu geeignet ist, zeigt sich immer wieder daran, wie leicht man eine KI überlisten kann. Letztlich vergleicht sie nur eine Eingabe mit immensen Mengen „gelernter“ Daten und gibt eine Schätzung ab. Vor einiger Zeit habe ich hier einen Karton gezeigt, den eine KI mit sehr hoher Wahrscheinlichkeit als Nacktfoto identifiziert haben wollte.
Hier nun ein weiterer Test, diesmal mit how-old.net, einer KI von Microsoft, die „gelernt“ hat, aus Fotos auf das Alter von Personen zu schließen.
Das Ergebnis schwankt offenbar abhängig von Brille und Gesichtsausdruck (also Faltentiefe) zwischen „ich fühle mich geschmeichelt“ und „ok dann geh ich meine Rente beantragen“.
Wenn man sich einmal vage vorstellt, wieviel Entwicklungsarbeit und letztlich Daten- und Energieverbrauch hinter so einem Projekt steckt, muss man ernsthaft die Frage stellen, ob das nicht einfach nur groteske Verschwendung von Ressourcen ist. Nichts gegen Grundlagenforschung: Aber solche Ergebnisse sollten eigentlich nahelegen, dass Deep Learning vielleicht doch einfach zu dumm für die meisten ernsthafte Einsätze ist, und dass es vielleicht in vielen Bereichen doch die bessere Idee ist, menschliche Arbeitsplätze nicht vorschnell durch bräsige, CO2 produzierende Software zu ersetzen.
Heute veranstalte ich einen Talk zu meinem Buch „Besser coden“:
https://www.meetup.com/de-DE/Clean-Code-Ruhrgebiet/events/262992087/
Mit von der Partie ist mein Co-Autor Martin Schroer, der in der zweiten Hälfte der Veranstaltung über Kommunikation in Teams sprechen wird.
Die Veranstaltung ist leider ausgebucht, aber es gibt eine Warteliste 😉
Ich wollte ja schon moppern: „Was hilft das Speicher sparende Google Go, wenn man die fette, große Google-App eh nicht deinstallieren kann?“
Aber es hilft tatsächlich, in Zahlen: Bei mir aufm Xperia 450 MB mehr freier Speicherplatz, wenn ich Google Go installiere und die Google-App deaktiviere (komplett deinstallieren geht ja nicht). Ob ich irgendwelche Features vermisse, oder ob Google Go sich mit der Zeit auch ein halbes Gigabyte reinpfeift, erzähl ich später …
Die Sache zeigt jedenfalls: Wenn man will (oder vom Boss dazu gezwungen wird), kann man durchaus speichersparend programmieren, liebe Entwickler!
KI geht nur mit Masse. Deshalb müssen wir bei Captchas Treppen und Brücken anklicken. Und deshalb verfolgt Google die ganze Zeit unseren Aufenthaltsort – um total sinnvolle Lebensverbesserungen anbieten zu können, und das ganz kostenlos ohne Geld dafür zu verlangen.
Jetzt neu: Durch Standortdaten „errät“ eine Google-KI die genauen Fahrzeiten von Bussen, so dass ich an meiner Haltestelle selbst dann eine Verspätungsinfo bekommen kann, wenn das Verkehrsunternehmen dergleichen gar nicht erfasst.
Für unser neues Android-Gameprojekt (ist noch unter höchster Geheimhaltungsstufe) brauchte ich eine Physik-Engine. Natürlich trifft man als allererstes auf Box2D bzw. deren Java-Version jBox2D.
Ich griff zu letzterer, nachdem ich irgendwo gelesen hatte, der Performanceunterschied sei nicht allzu groß. Dass das Projekt seit 2014 kein Release mehr gesehen hat (aber durchaus Commits!), fand ich nicht weiter schlimm, es ist stabil, gut getestet, von recht hoher Codequalität und da die Physik selten Features hinzugewinnt, muss auch so eine Library nicht dauernd irgendwas nachziehen.
Ich litt etwas unter einem gewissen Mangel an Beispielcode – die meisten Programmierer verwenden heutzutage ja multiplattformhalber Unity und mühen sich nicht mit Android-Libraries ab. Ich bin da speziell – durch konsequente Nichtunterstützung von iPhones habe ich den Apfelkonzern schon an den Rand des Ruins gebracht.
Bei Levels mit etwas mehr physikalischen Objekten darin hatte die Engine merklich Probleme, die Framerate von mindestens 25fps zu halten, jedenfalls in Momenten mit vielen Kollisionen; teils flogen auch mal Teile durcheinander durch. Nun muss man wissen, dass jBox2Ds Funktion world.step() (die für die Physik zuständig ist) single-threaded ist, aber Phones und Tablets heutzutagen auch schon mit 2, 4 oder 8 Kernen daher kommen. Da wird eine Menge Potenzial nicht genutzt. Zwar gilt für die native C++-Version dasselbe, aber Maschinensprache ist eben per se schneller als Java.
Der große Aha-Effekt kam, als ich mir probeweise ein chinesisches Billig-Tablet beschaffte (XGody, 10 Zoll, knappe 70 Euro bei ebay, aber sogar mit OTG-Kabel in der Schachtel (zur hypercoolen Verwendung solcher Kabel in Kürze mehr an dieser Stelle)). Die Framerate sank bei Action im Bild auf unter 5 fps. Autsch.
Eine schnelle Messung ergab: Die Grafik ist nicht schuld, das komplette Zeichnen inklusive Debug-Hilfslinien etc. dauert (im TextureView) nur 25ms. Aber world.step() brauchte 40ms und mehr.
Also ging ich daran, jBox2D durch box2D zu ersetzen. Nun darf man sich das nicht zu einfach vorstellen, denn die Bibliothek gibt es nur im Quellcode, und übrigens auch auf dem Stand von 2014, wie gesagt: Seither hat sich an der Physik dieses Universums nichts verändert.
Die einzige praktikable Option heißt libgdx: Diese Multiplattform-Gameengine verfügt nämlich über ein Erweiterungsmodul, das die native box2D-Bibliothek enthält, und zwar verpackt in einen dünnen Java-Wrapper. Wie sich herausstellte, ließ sich jBox2D fast 1:1 durch libgdx-box2D ersetzen. Fast.
Ein paar Klassen heißen in box2D anders, z.B. Vector2 statt Vec2. Einige Attribute sind nur mit Gettern und nicht direkt zugreifbar. Und body.userdata lässt sich nicht über BodyDef setzen, nur direkt im Body. Eine Klasse AABB (zur Kollisionsberechnung) gibt es nicht, world.QueryAABB() nimmt direkt die Koordinaten des Rechtecks entgegen. Alles recht schmerzfrei.
Natürlich wollte ich nicht das ganze Projekt auf libgdx umstellen, deshalb konnte ich nicht den libgdx-eigenen Project-Wizard verwenden, der auf Wunsch direkt ein Gradle-Projekt mit Box2D-Unterstützung erzeugt. Und wie man das per Hand hinbekommt, steht nirgendwo. Also war probieren angesagt. Entscheidend sind ein paar Änderungen in der build.gradle. Als da wären:
Dies sorgt dafür, dass die App später die aufgeführten Architekturen unterstützt. Wichtig dabei: Ab 1. August ist Unterstützung der 64-Bit-Architekturen seitens Google Play obligatorisch!
sourceSets {
main {
jniLibs.srcDirs = ['lib']
}
}
Hiermit wird das Verzeichnis für die nativen Libs festgelegt. Aber woher kommen die?
task copyAndroidNatives {
doFirst {
file("lib/armeabi/").mkdirs()
file("lib/armeabi-v7a/").mkdirs()
file("lib/arm64-v8a/").mkdirs()
file("lib/x86_64/").mkdirs()
file("lib/x86/").mkdirs()
configurations.natives.files.each { jar ->
def outputDir = null
if (jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("lib/arm64-v8a")
if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("lib/armeabi-v7a")
if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("lib/armeabi")
if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("lib/x86_64")
if(jar.name.endsWith("natives-x86.jar")) outputDir = file("lib/x86")
if(outputDir != null) {
copy {
from zipTree(jar)
into outputDir
include "*.so"
}
}
}
}
}
tasks.whenTaskAdded { packageTask ->
if (packageTask.name.contains("package")) {
packageTask.dependsOn 'copyAndroidNatives'
}
}
So wird Gradle beigebracht, die nativen Bibliotheken an Ort und Stelle abzulegen. (Man kann das später in Android Studio mit Analyze/APK… nachschauen.)
Und nun zur Pointe: Die native Version der Bibliothek ist grob geschätzt um einen Faktor 25 schneller. Sogar das lahme-Ente-Tablet aus China erreicht damit eine spielbare Framerate:
Mit ins Bild geschummelt hat sich das mitgelieferte OTG-Kabel
Ein schnelleres Tablet oder Phone kommt locker auf 1ms Rechenaufwand für die Physik (also world.step()), während das Zeichnen „nur“ um einen Faktor 3 schneller ist. Aber da lässt sich sicher auch noch was optimieren, das soll ein andermal erzählt werden.
Der Teufel steckt aber mal wieder im Detail. Während jBox2D und box2d selbst ihre physikalischen Objekte (bodies) in verketteten Listen verwalten, benutzt der libgdx-Wrapper zusätzlich eine LongMap. Deren Schlüssel sind aber nicht sortiert, daher geht die Reihenfolge der Bodies zwischen Hinzufügen und Zeichnen verloren! Da keine Möglichkeit vorgesehen ist, eine z-Ebene mitzugeben, kann es passieren, dass Objekte vor anderen (also später) gezeichnet werden, die eigentlich dahinter gezeichnet werden sollen (also früher). Da hat wohl jemand tief geschlafen, als er den Wrapper implementiert hat. Mal sehen, wie ich das löse. Es bleibt spannend.
TILT: Hier geht die Reihenfolge verloren, in der die Bodys hinzugefügt werden, da der Schlüssel der verwendeten LongMap keine Reihenfolge kennt (World.java).
Fazit: Finger weg von jBox2D, sondern gleich die native Version nehmen. Schneller ist schöner.
Ich bedanke mich für die Aufmerksamkeit und gehe mal besser coden.
Da ja bekanntlich demnächst künstliche Intelligenzen die Macht übernehmen, Arbeitsplätze wegnehmen und ein bisschen besser Auto fahren als wir (da wird man natürlich neidisch, siehe dazu auch hier über Vandalismus gegen selbstfahrende Autos), habe ich mal einen Selbversuch gewagt.
Ich habe mir Tensorflow installiert, dazu die Anwendung nudeNet, sowie einen fertigen Classifier Dataset, der mit insgesamt ungefähr 711.000 Bildern trainiert wurde (20 GB Nacktbilder und Nichtnacktbilder). Das ist ein typisches Beispiel für Machine Learning, das beispielsweise bei Uploadfiltern eingesetzt wird, bloß bei den großen Diensten mit noch mehr Bildern. Zur Erinnerung: Ein System wie dieses braucht optimalerweise Millionen von Fotos, um zu lernen, Esel und Hasen voneinander zu unterscheiden (ein Kleinkind nur zwei oder drei), und man braucht ein zweites System, um Penisse von Schraubenziehern unterscheiden zu können und so weiter (etwas vereinfacht gesagt). Nur die ganz großen Dienstleister verfügen über solche Datenmengen!
Mit einem kleinen Python-Skript habe ich mein ganzes Home-Verzeichnis auf meinem PC nach Nacktbildern abgesucht, d.h. nach Bildern, die nudeNet mit >90% Sicherheit für nackt hält.
Das Resultat ist ernüchternd. So hielt die KI beispielsweise fast alle Einzelframes eines meiner Animationsvideos für nackt, und mit einer Sicherheit von 99,1% auch dieses höchst versaute Foto (Kinder bitte Augen zu!):
Bei dieser nackten Verpackung eines Billy-Regals sieht man eindeutig die Genitalien, oder etwa nicht?
Nichts gegen Maschinenlernen und künstliche Intelligenzen, aber man darf eines nicht vergessen: Sie sind nicht wirklich „intelligent“. Man kann sie mit verwirrenden Eingaben sehr leicht verarschen. Und umso mehr Maschinenlernen unseren Alltag erobert, desto mehr Menschen werden versuchen, genau das zu tun.
Das Resultat ist so vorhersehbar wie die Klimakatastrophe.
Natürlich völlig anonymisiert und verfremdet präsentiere ich mal wieder einige aktuelle Fundstücke aus meiner Tätigkeit als gefürchteter Softwarequälitäts-Ganzgenauhinschauerundbesserwisser. Manchmal läuft einem ein Schauer über den Rücken, wenn man den Kontext kennt, aus dem der fragliche Code stammt (den ich freilich nicht verraten darf, weil ich meterlange Verschwiegenheitserklärungen unterschrieben habe). Funktioniert diese Software wirklich genau so, wie sie soll? Und wenn ja: Wie lange noch?
package com.companyname.classesToDeleteWhenUsingJava6; // TODO delete as soon as Java 1.6 available
Java 1.6, auch bekannt als 6.0, erschien Ende 2006, also vor 13 Jahren.
Nun ja, es hat sich wohl noch nicht überall herumgesprochen, dass Code auch nach Inbetriebnahme gewisser Pflege bedarf. Nur einer der Gründe, wieso Softwarekosten häufig zu niedrig angesetzt werden.
////// ????? ask norbert
Ja, über lustige Kommentare und deren (Un)Zulässigkeit kann man trefflich streiten. Nicht streiten kann man über TODOs, die nicht einmal als solche gekennzeichnet sind. Wenn hier tatsächlich fachlicher Klärungsbedarf besteht oder bestand, dann gehört die eigentliche Frage (die hier zudem nicht einmal hier steht!) nicht in den Code, wo sie ganz offensichtlich in Vergessenheit geriet, sondern in eine Mail an Norbert, in ein Ticket oder von mir aus auf ein Post-it, obwohl die den Nachteil haben, beim Durchlüften verloren zu gehen, und die positive Wirkung von Frischluft aufs Denkvermögen sollte man nicht unterschätzen. The answer my friend is blowing in the wind. But not in the code.
Hier mal kein Codebeispiel, sondern eine Auswirkung: Der direkte Weg in die Programmiererhölle, sogar im wahrsten Sinne des Wortes. Wer auch immer da bei Spotify (oder bei einem Musikverlag, von dem die Daten ursprünglich stammen) eine Art Metadaten-Migration von „V2 to V3“ implementiert hat: Er hat irgendeinen Spezialfall nicht berücksichtigt, so dass beim Copyright dieses Albums ein unsinniger Default-Wert („Default-C Credit“) eingesetzt wurde. Metadaten-Fehler sind auf den ersten Blick harmlos, weil an sie meist keine kritische Geschäftslogik geknüpft ist. Aus leidvoller Erfahrung mit dem Thema Musik-Metadaten kann ich jedoch verraten: Obacht! Es kann passieren, dass durch ähnliche Fehler ein Titel gar nicht erst in einem Suchindex landet und folglich nicht auffindbar ist. Bei einigen Songs für die Nicht-Hörer sicher verschmerzbar, nicht aber für den Künstler. Jedenfalls ein Fehler, der nicht hätte passieren müssen. Spätestens bei einem Codereview hätte jemand feststellen müssen, dass so etwas wie „Default-Credits“ überhaupt keinen Sinn ergeben. Der Bug kann Stand heute (Februar 2019) übrigens weiterhin im Spotify-Webplayer besichtigt werden.