1 Punkte von GN⁺ 2024-05-18 | 1 Kommentare | Auf WhatsApp teilen
  • Bend ist eine High-Level-Programmiersprache für parallele Programmierung, die die Ausdrucksstärke von Python und Haskell mit CUDA-artiger massiv paralleler Ausführung verbinden will und auf der HVM2-Runtime läuft
  • Sie unterstützt Higher-Order Functions mit Closures, schnelle Objektallokation, uneingeschränkte Rekursion und Continuations, läuft aber ohne explizite Parallelisierungsnotation wie Thread-Erzeugung, Locks, Mutexes oder Atomics auf paralleler Hardware wie GPUs
  • Das aktuelle Designziel ist Skalierungsleistung abhängig von der Anzahl der Kerne; mehr als 10.000 gleichzeitige Threads werden unterstützt, allerdings kann die Single-Core-Performance der aktuellen Version niedrig sein, und Verbesserungen bei Codegenerierung und Optimierung sind in Arbeit
  • Die Ausführung erfolgt über bend run-rs, bend run-c und bend run-cu; parallelisierbarer Code kann allein durch Ändern des Ausführungsbefehls parallel im C-Interpreter oder CUDA-Interpreter laufen
  • Windows-Unterstützung ist noch in Arbeit, daher ist WSL2 eine Alternative; GPU-Ausführung unterstützt derzeit nur NVIDIA-GPUs

Das von Bend angestrebte Programmiermodell

  • Bend ist eine Programmiersprache, die das Bediengefühl einer Hochsprache beibehält und zugleich auf massiv paralleler Hardware läuft
  • Sie bietet Funktionen aus ausdrucksstarken Sprachen wie Python und Haskell
    • schnelle Objektallokation
    • Higher-Order Functions mit Closures
    • uneingeschränkte Rekursion
    • Continuations
  • Sie läuft wie CUDA auf massiv paralleler Hardware wie GPUs und zielt auf eine nahezu lineare Beschleunigung basierend auf der Anzahl der Kerne ab
  • Für parallele Ausführung muss Folgendes nicht direkt geschrieben werden
    • Thread-Erzeugung
    • Locks
    • Mutexes
    • Atomics
  • Als Runtime wird HVM2 verwendet

Aktuelle Einschränkungen und Hinweise

  • Bend konzentriert sich darauf, die Performance mit der Anzahl der Kerne zu skalieren, und ist dafür ausgelegt, mehr als 10.000 gleichzeitige Threads zu unterstützen
  • Die aktuelle Version kann eine geringe Single-Core-Performance haben
  • Mit Fortschritten bei Codegenerierung und Optimierungstechniken werden Performance-Verbesserungen erwartet
  • Windows-Unterstützung ist noch in Arbeit; als Alternative kann WSL2 verwendet werden
  • GPU-Unterstützung umfasst derzeit nur NVIDIA-GPUs

Installation und Ausführung

  • Sowohl unter Linux als auch auf dem Mac ist eine Rust-Installation erforderlich
  • Für die C-Version von Bend wird GCC verwendet; das README empfiehlt GCC 12.x oder niedriger
  • Für die Nutzung der CUDA-Runtime ist die Installation des CUDA Toolkit 12.x für Linux erforderlich
  • HVM2 wird mit cargo install hvm installiert, Bend mit cargo install bend-lang
  • Die Befehle zum Ausführen von Bend-Programmen sind nach Runner getrennt
    • bend run <file.bend>: verwendet standardmäßig den C-Interpreter, parallele Ausführung
    • bend run-rs <file.bend>: verwendet den Rust-Interpreter, sequenzielle Ausführung
    • bend run-c <file.bend>: verwendet den C-Interpreter, parallele Ausführung
    • bend run-cu <file.bend>: verwendet den CUDA-Interpreter, massiv parallele Ausführung
  • Mit gen-c und gen-cu kann in eigenständige C/CUDA-Dateien kompiliert werden
  • Der Codegenerator befindet sich noch in einem frühen Stadium und ist nicht so ausgereift wie Compiler wie GCC oder GHC
  • Mit dem Flag -s lassen sich Anzahl der Reductions, Laufzeit und Interactions pro Sekunde anzeigen

Beispiele für sequenzielles und paralleles Summieren

  • Das Summenbeispiel im README vergleicht auf zwei Arten Code, der die Zahlen von start bis target addiert
  • Die sequenzielle Version ist so aufgebaut, dass zum Ergebnis von Sum(start + 1, target) das aktuelle start addiert wird
    • Die nächste Berechnung hängt vom vorherigen Summenergebnis ab
    • Bevor die aktuelle Berechnung abgeschlossen ist, kann nicht zum nächsten Schritt übergegangen werden, daher lässt sie sich nicht parallelisieren
    • Das Beispiel ruft Sum(1, 1_000_000) auf und enthält einen Kommentar, dass der Maximalwert von Bend-Zahlen überschritten werden kann
  • Die parallelisierbare Version teilt den Bereich in zwei Hälften und berechnet die linke und rechte Summe rekursiv
    • Die Berechnung von (3 + 4) hängt nicht von der Berechnung von (1 + 2) ab
    • Beide Berechnungen können gleichzeitig erfolgen, wodurch parallele Ausführung möglich ist
  • Wenn Code in Bend parallel ausgeführt werden kann, reicht eine Änderung des Ausführungsbefehls für parallele Ausführung

Performance-Beispiel: Bitonic Sorter

  • Das README führt einen mit unveränderlichen Baumrotationen implementierten Bitonic Sorter als Geschwindigkeitsbeispiel an
  • Dieser Algorithmus gehört zu den Typen, bei denen nicht unbedingt hohe GPU-Geschwindigkeit zu erwarten ist, doch durch den Divide-and-Conquer-Ansatz führt Bend ihn über mehrere Threads aus
  • Explizite Thread-Erzeugung oder Lock-Verwaltung ist nicht erforderlich
  • Die Benchmark-Ergebnisse lauten wie folgt
    • bend run-rs: CPU, Apple M3 Max, 12,15 Sekunden
    • bend run-c: CPU, Apple M3 Max, 0,96 Sekunden
    • bend run-cu: GPU, NVIDIA RTX 4090, 0,21 Sekunden
  • Weitere Algorithmen sind im examples folder zu finden

Referenzen

  • Die Basistechnologie von Bend ist im HVM2-Paper beschrieben
  • Die offizielle Dokumentation ist in Arbeit; ausführlichere Erklärungen stehen in GUIDE.md
  • Die Funktionsliste ist in FEATURES.md zu finden
  • Bend wird von HigherOrderCO entwickelt

1 Kommentare

 
GN⁺ 2024-05-18
Meinungen auf Hacker News
  • Ich habe das sum-Beispiel nach reinem Python übertragen: Mit pypy3 lief es single-threaded in 4,478 Sekunden, mit Python 3.12 dauerte es 1 Minute 42,148 Sekunden.
    Die single-threaded Bend-Version dagegen läuft auf meinem Laptop seit 42 Minuten, verbraucht 6 GB RAM und ist immer noch nicht fertig. Die Umgebung ist ein 12th Gen Intel(R) Core(TM) i7-1270P mit Ubuntu 24.04.
    Wenn es schon bei einem sehr einfachen Beispiel so langsam ist, fällt es schwer, bei komplexen Aufgaben viel zu erwarten. Ich frage mich außerdem, ob außerhalb von Mac/aarch64 getestet oder entwickelt wurde. Ich will es später noch einmal mit dem Argument -s laufen lassen.

    • Dass es 42 Minuten läuft, ist sehr wahrscheinlich ein Bug. Außer auf einem M3 Max haben wir noch nicht viel getestet, und uns ist bekannt, dass es auf Nicht-Apple-CPUs 2× langsamer ist; das wollen wir verbessern.
      Beim sum-Beispiel hat Bend den großen Nachteil, dass es für jede Zahlenoperation 2 IC-Knoten alloziert; Python tut das nicht. Wie bei HVM1 werden wir das bald vermeiden können, aber in HVM2 ist es noch nicht implementiert.
      Der Großteil der Arbeit an Bend floss darin, den parallelen Evaluator korrekt hinzubekommen, und Closures sowie unbegrenzte Rekursion auf der GPU auszuführen war sehr schwierig. Da wir diesen Teil gerade erst abgeschlossen haben, ist fast keine Arbeit in Mikro-Optimierungen geflossen, und auch die Codegenerierung von HVM2 ist noch ziemlich schlecht.
      Einen faireren Eindruck der tatsächlichen Performance bekommt man vermutlich, wenn man Fälle vergleicht, in denen beide Seiten ähnlich viel allozieren, etwa das Bitonic Sort-Beispiel. HVM1 war auf einem einzelnen Core etwa 3× langsamer als GHC, und ich denke, HVM2 kann dieses Niveau in nicht allzu ferner Zukunft ebenfalls erreichen.
      Ich verstehe, dass „es ist noch schlecht, wird aber besser“ ernüchternd klingen kann. Aber jetzt steht die Grundlage; Mikro-Optimierung ist der einfachste Teil, und ich glaube, dass die Performance hier noch deutlich steigen wird.
    • Ich habe in dieser Debatte keine eigenen Interessen, aber Rekursion testet eher, wie effizient Compiler/Interpreter Call Stacks auf- und abbauen, als die Rechenleistung selbst.
      Diese Sprache zielt auf rechenintensive GPU-Anwendungen und ist noch in einem frühen Stadium. Rekursion ist nicht die Zielanwendung, und ich halte sie nicht für einen passenden Benchmark.
    • Threads bedeuten auf GPU und CPU Unterschiedliches; auf der GPU entsprechen sie eher SIMD-Lanes.
      Das ist ähnlich wie bei ISPC, das so kompilieren kann, dass pro CPU-Thread 32 Funktionsaufrufe gleichzeitig ausgeführt werden. Nutzt man zum Beispiel auf AVX512 16-Bit-Daten, können 32 Cores × 2 SMT-Threads pro Core × 32 Compiler-Ausführungen = 2048 Ausführungen gleichzeitig laufen.
    • Python ist bei Rekursion sehr schwach, was einer der Gründe ist, warum es sich nicht gut für funktionale Programmierung eignet; daher ist das womöglich kein fairer Benchmark.
      Eine pythonische Implementierung hätte vermutlich Schleifen und veränderlichen Zustand verwendet.
    • Ich verstehe nicht, wozu +0 nötig ist. Ist das nicht eine Operation, die nichts bewirkt?
  • In diesem Thread gibt es viele negative Reaktionen, aber allein dafür, dass der Autor es so weit gebracht hat, möchte ich ihm kudos geben.
    Als ähnliches Projekt kenne ich eigentlich nur Futhark, aber dessen Haskell-artige Syntax kann für normale Entwickler, die C/C++/Python/JS/Java usw. gewohnt sind, ziemlich schwer zugänglich sein.
    Der größte Wermutstropfen ist, dass es anders als Futhark nur auf CUDA oder Multicore abzielt. Futhark kann OpenCL, CUDA, ISPC, HIP, Single-Core-CPU und Multicore-CPU als Ziel haben. Die Performance-Probleme, auf die andere hingewiesen haben, halte ich für gut lösbar.

  • OP bringt einige der coolsten Dinge auf HN in letzter Zeit ein, und ich finde es schade, dass er anscheinend nur lange Kritik bekommt, obwohl es eindeutig noch eine frühe Version ist.

    • HN ist eher eine Community, in der Leute Neues oder Originelles posten wollen. Wenn jemand loben möchte, klickt er oft eher auf Upvote bei einem bestehenden Kommentar, statt noch einen weiteren „cool“-Kommentar zu schreiben.
      Kritik dagegen kann endlos vielfältig werden: Es gibt nur begrenzt viele Arten, richtig zu liegen, aber viele Arten, falsch zu liegen. Deshalb gibt es nur ein paar positive Kommentare, während der Großteil wie Kritik oder „man sollte auch noch …“ wirkt. Das ist weniger die Schuld einzelner Personen als vielmehr ein Merkmal der heutigen Technikkultur.
    • Wenn es mein Projekt wäre, wäre ich ziemlich dankbar, wenn Leute es kritisieren. Nur so wächst man.
      Wenn die Leute die brutale Wahrheit immer nur hinter Applaus verstecken würden, würde die Welt zusammenbrechen.
    • Mit 905 Upvotes hat es ja auch reichlich positive Reaktionen bekommen.
      Kritik bedeutet auch, dass sich Leute für die Idee und den Ansatz interessieren und sich damit auseinandersetzen; das ist oft ein positives Signal.
    • Neue und ambitionierte Projekte nicht zu kritisieren, ist eine gute soziale Norm. Solche Versuche sollten ermutigt und nicht ausgebremst werden.
      Aber Projekte zu kritisieren, die irreführende, schlecht belegte oder falsche Behauptungen aufstellen, ist ebenfalls eine gute soziale Norm. Denn sie sorgt dafür, dass solche Behauptungen weniger werden.
    • Die coolsten Dinge sind meist am schwersten zu verstehen.
      Schwer Verständliches wirkt oft bedrohlich, und Kritik ist eine häufige Reaktion auf Bedrohungen sowie die Antwortform, die am wenigsten Verständnis erfordert.
  • Die Homepage ist wirklich gut gemacht. Man sieht sofort klar, worum es geht.
    Leute, die sich mit „Kombinatoren“ beschäftigen, wollen normalerweise viele einschüchternde Fachbegriffe verwenden, aber OP zeigt tatsächlich die einfache Idee hinter dem Tool. Mir gefällt, dass das das Gegenteil des akademischen Ansatzes ist, bei dem jedes letzte Detail gezeigt wird, ohne zu sagen, worum es eigentlich geht. Davon sollte es mehr geben.

  • Theoretisch cool und das Nutzenversprechen verstehe ich auch, aber ehrlich gesagt glaube ich nicht, dass das in der Praxis ein relevantes Tool wird.
    Das sind meine Notizen nach dem ersten Eindruck und dem Überfliegen des Papers. Mir ist klar, dass es sich um sehr frühe Software handelt.
    Bend wirkt wie eine sehr eingeschränkte DSL. Es gibt kein FFI, keine Möglichkeit, mit rohen Buffern zu interagieren, und das 24-Bit-Gleitkommaformat ist auch seltsam.
    Es gibt Gründe, warum IC nicht Mainstream ist. Die Performance wird wahrscheinlich auch künftig schrecklich sein, und Graph-Traversal passt nicht gut zur Hardware.
    Die Prämisse der optimalen Reduktion ist gültig, aber am Ende muss man Kernel so schreiben, dass sie parallelisierbar sind. Das heißt: keine Datenabhängigkeiten, und auch Rekursion muss bedacht werden.
    Es gibt keine ernsthaften Beispiele, die Bend/HVM-Code direkt mit äquivalenten OMP/CUDA-Programmen vergleichen. Schwer zu beurteilen, wie sehr die Implementierungskomplexität sinkt und wie die Performance aussieht.
    In realem High-Performance Parallel Computing gibt es kaum baumartige Strukturen, Arrays sind König. Das liegt an den physikalischen Eigenschaften, wie Speicher auf Hardware-Ebene funktioniert. Was auf veränderlichen, zusammenhängenden Speicherbuffern am besten funktioniert, sind Schleifen. Wenn HVM das implementiert, schaue ich es mir an.
    Im Moment wirkt es fast vollständig von externen Daten isoliert, sehr langsam und wie eine halb gare Sprache mit einer riesigen Abstraktionsschicht über der Hardware. Funktionen wie mehrstufige Caches, Tensor Cores, SIMD oder atomare Operationen kann es ebenfalls nicht nutzen.
    Sorry, falls das harsch klingt, aber die technische Umsetzung und der theoretische Hintergrund sind weiterhin sehr interessant. Nur von der Nützlichkeit in der realen Welt bin ich noch nicht überzeugt.

    • Danke für das Feedback. Um ein paar Dinge richtigzustellen: Wir nutzen mehrstufige Caches, und richtig eingesetzt können sie 5-mal höhere Performance liefern.
      FFI ist bereits implementiert, aber noch nicht veröffentlicht. Wir wollen es zusammen mit Grafik-Rendering herausbringen, und ich glaube, das wird ziemlich cool.
      Haskell/GHC verwendet ebenfalls Graphen und Bäume, aber niemand würde sagen, dass es unpraktisch ist. Arrays sind zwar König, aber viele moderne Algorithmen, die nicht gut zu Arrays passen – Compiler, Typechecker, Solver usw. –, sind in Haskell implementiert.
      Der Hauptgrund, warum IC nicht schnell ist, ist, dass niemand darauf wirklich Low-Level-Optimierung betrieben hat. Alle bisherigen Implementierungen waren extrem ineffizient, und bei meiner Arbeit lag der Fokus bisher darauf, es auf der GPU korrekt zum Laufen zu bringen.
      So wie du sagst, dass es noch nicht einmal Schleifen gibt: Die Lösung besteht einfach darin, Schleifen hinzuzufügen. Wenn du glaubst, dass es dort eine grundsätzliche Grenze gibt, wirst du überrascht sein.
      HVM2 ist endlich ein korrekter Algorithmus, der skalieren kann, und jetzt ist es an der Zeit, die tatsächliche Low-Level-Performance zu optimieren.
    • Zu Punkt 5: Bäume sind zwar anders als die übliche informatische Implementierung, werden aber ziemlich breit eingesetzt.
      Bei Fast-Multipole- oder Barnes-Hut-Algorithmen werden Morton-Order oder H-Index-Order verwendet, um paarweise O(n²)-Operationen auf O(n) bzw. O(n log n) zu reduzieren. Barnes-Hut ist in der Astrophysik verbreiteter, Fast Multipole sieht man häufiger in der chemischen Molekulardynamik.
  • Vor 10 Jahren habe ich an der CMU den Parallelalgorithmen-Kurs 15-210 belegt. Dort wurde erklärt, dass Parallelität die Zukunft des Computing sein würde, weil Moore's Law an Grenzen stößt, und das hat mich überzeugt, damit experimentieren zu wollen.
    Aber es gab nicht viele Optionen für universelle Parallelprogrammierung. Selbst das im Kurs verwendete SML war nicht parallel; am Ende gab es zwar einen Abschnitt mit Extensions und CUDA, aber soweit ich mich erinnere, war der begrenzt.
    Später konnte ich dank Rust ein wenig mit Multithreading experimentieren, und dank Shadertoy kreativ mit Shadern arbeiten. Aber eine universelle parallele Sprache auf der GPU – ich bin sehr gespannt darauf, sie selbst auszuprobieren.

    • Heutzutage ist 210 tatsächlich parallel. Mit MaPLe (https://github.com/MPLLang/mpl) kann man Code im 210-Stil ausführen und bekommt dabei konkurrenzfähige Performance gegenüber C/C++.
      Wenn dir 210 gefallen hat, könnte dir auch https://futhark-lang.org/ gefallen. Es ist eine Sprache aus der ML-Familie, kompiliert auf die GPU und hat gute Performance.
    • Der Trend zu Multicore-Maschinen war einer der Gründe, warum ich beschlossen habe, Elixir zu lernen.
  • Die Idee ist sehr cool, aber wenn ich nichts übersehen habe, wirkt es sehr langsam.
    Ich habe in C++ eine einfache Schleife geschrieben, die von 0 bis 2³⁰ summiert; ohne Optimierung, Single-Threaded, brauchte sie auf meinem Laptop 1,7 Sekunden, was in etwa der Bend-Performance auf einer RTX 4090 entspricht. Mit -O3 wird die Schleife vektorisiert und läuft in unter 80 ms.

    • Bend hat noch keine Tail-Call-Optimierung. Es allokiert einen Stack mit einer Milliarde Einträgen, während C einfach eine Schleife ausführt.
      Verglichen mit einem C-Programm, das tatsächlich Allokationen durchführt, dürfte Bend möglicherweise schon mit wenigen Threads schneller sein.
      Die Codegenerierung von Bend ist noch miserabel, aber das sind niedrig hängende Früchte. Der Großteil der Arbeit floss in den sehr schwierigen parallelen Evaluator, der korrekt sein musste.
      Ich weiß, das klingt nach „vertrau mir“, aber sobald wir mit Procedure Compilation, Schleifengenerierung usw. anfangen, wird die Single-Thread-Performance deutlich besser werden. Wir haben es nur noch nicht gemacht.
      Vielleicht hätte ich noch etwas länger warten sollen, bevor ich es wirklich hochgeladen habe.
    • Es lohnt sich, mit objdump zu prüfen, ob die Schleife tatsächlich vektorisiert wurde oder ob der Compiler sie komplett wegoptimiert hat.
      Diese Schleife verursacht einen Überlauf bei vorzeichenbehafteten Integern, und das ist in C++ undefiniertes Verhalten. Der Compiler darf legal jedes beliebige Ergebnis liefern.
      Um das zu vermeiden, solltest du sum als unsigned deklarieren. Überlauf bei vorzeichenlosen Integern ist wohldefiniert, und die Optimierung findet weiterhin statt, aber zumindest ist die Korrektheit garantiert.
    • Mit -O3 in clang wird die Schleife vollständig entfernt: https://godbolt.org/z/M1rMY6qM9
      Das ist vermutlich kein fairer Vergleich.
    • Der Kernpunkt scheint zu sein, dass Bend viel höher abstrahiert ist als C++.
      Natürlich kann es auch sein, dass ich den Punkt übersehe.
  • Ich möchte dem Autor gratulieren. Wirklich beeindruckende Arbeit.
    Korrekte automatische Parallelisierung zu bauen, ist alles andere als einfach, und darauf kann man zurecht stolz sein. Ich bin gespannt, wie sich das Projekt weiterentwickelt.

  • Ich verstehe nicht, warum es so viele negative Reaktionen gibt. Es wirkte wie ein wütender Mob, der sich in die Schwachstellen des README verbeißt und wie Bots versucht, Kontext und Absicht des Textes zu verdrehen.
    Stundenlang zu streiten, ohne auch nur zwei Minuten fürs gründliche Lesen aufzuwenden, ist ignorant und grausam. OP hat es als Ein-Personen-Projekt bis hierher geschafft; ich hoffe, er macht weiter.

  • Ich hatte mich gefragt, ob HVM2 Interaktionsnetze zum Beispiel nach SPIR-V kompiliert oder ob es, wie das ursprüngliche HVM, ein Interpreter ist, der auf der GPU läuft.
    Früher habe ich einmal versucht, Interaktionsnetze nach C zu kompilieren, indem ich ein Programm so weit wie möglich reduziert habe, die Eingaben aber nicht reduziert wurden, und das Ganze wie Whole-Program-Optimization behandelt. Eine Shader-Sprache als Ziel zu nehmen, schien mir auch nicht besonders schwierig.
    Im Repository steht, dass es eine Low-Level-IR-Sprache zur Spezifikation von HVM2-Netzen und einen Compiler nach C/CUDA bereitstellt: https://github.com/HigherOrderCO/HVM
    Beim erneuten Hinsehen wirkt die HVM2-CUDA-Runtime allerdings wie ein Interpreter, der den Graphen im Speicher durchläuft und Reduktionen anwendet: https://github.com/HigherOrderCO/HVM/blob/5de3e7ed8f1fcee6f2...
    Was ich meinte, ist ein Ansatz, bei dem man das Interaktionsnetz durchläuft, Terme rekonstruiert, die Lambda-Kalkül-Ausdrücken nahekommen, und sie in kleinen Stücken nach C absenkt, um den Runtime-Overhead zu minimieren.
    Die ehrliche Motivation ist, dass es mit Bend schwierig sein dürfte, handgeschriebene GPU-Kernel etwa bei ML-Workloads zu schlagen. Theoretisch kann HVM als Klebstoff dienen, der Rechenkernel verbindet und die Ausführungsreihenfolge parallelisiert, aber dafür braucht es eine gute FFI.
    Interaktionsnetze lassen sich schwer über FFI-Grenzen hinweg übersetzen; wenn man aber FFI-Rechenkernel-Knoten in das Interaktionsnetzwerk legt und das Netz nach C kompiliert, kann man eine vernünftige FFI ohne Übersetzungs-Overhead wiederherstellen.
    Eine andere Option wäre, HVM in Hardware zu implementieren; damit experimentiere ich gerade ein wenig auf einem übrig gebliebenen FPGA.

    • Es ist sowohl ein Interpreter, der auf der GPU läuft, als auch ein Compiler nach nativem C und CUDA.
      SPIR-V wird nicht direkt anvisiert, ist aber ein Ziel.
      Der C-Compiler liefert den erwarteten Geschwindigkeitszuwachs, also 3- bis 4-fach und bald mehr, aber die CUDA-Runtime erzielte gegenüber der nicht kompilierten Version keinen großen Speedup.
      Als Ursache vermute ich Warp-Divergenz. In nicht kompilierten Prozeduren kann man alle Funktionsaufrufe zu einem einzigen „universellen“, interpreterartigen Funktions-Expander zusammenführen, und die Warp-Threads können ohne Verzweigungen reduzieren. Das wollen wir künftig genauer untersuchen.