# Große Datenmengen effizient programmieren



## Fabse (29. Jul 2019)

Hi,

*Hintergrund:*
Ich analysiere Börsendaten aus der Vergangenheit über Sinn bzw. Un-Sinn hier bitte nicht diskutieren 

Ich habe diese Daten im csv Format vorliegen. Einmal die Werte von jedem Tag und einmal jeden einzelnen Tick pro Tag.
Die csv von den Tagen hat ein paar Megabyte, das ist kein Problem, aber die Datei mit den Ticks hat ca. 5 Gigabyte. Ich lese zuerst die Tagesdaten aus und speichere sie in ein entsprechendes Objekt:

```
private LocalDateTime date;
    private BigDecimal open;
    private BigDecimal high;
    private BigDecimal low;
    private BigDecimal close;
    private List<CandleStickTick> listCandleStickTick = new ArrayList<>();
```

Je nach "Testfall" gibt es in dem Objekt auch noch 4-5 weitere Felder, booleans oder auch BigDecimals.

Dann lese ich die Tickdaten aus und speichere sie in die Klasse CandleStickTick und die schaut so aus:

```
private LocalDateTime date;
    private BigDecimal ask;
    private BigDecimal bid;
```
Danach mappe ich dann alle Ticks zu den Tagesdaten, die ArrayList wird dann befüllt.

Wenn ich das alles getan habe, habe ich eine Arbeitsspeicherauslastung von über 50GB!
Dann verarbeite ich die Daten in mehreren Threads mit unterschiedlichen Ausgangsparametern.

*Problem:*
Wieso ist die Auslastung so extrem hoch? Also das 10-fache von der csv Datei? BigDecimal wird vermutlich teuer sein? Wie viel teurer als ein int? Ich habe nicht nur ganze Zahlen könnte aber mit einem entsprechenden Faktor auf eine kommen und somit einen int verwenden, aber wie viel spare ich damit? 
Eine Datenbank verwenden? Da ich aber mehrere Threads verwende (8Kerne / 16 Threads) könnte es hier ja zu Problemen kommen? Ich brauche nicht immer alle Ticks...demnach könnte ich dann immer mit einem Statement auf die DB gehen und mir immer nur die Ticks holen die ich aktuell brauche. Läuft das Programm dann aber immer noch so schnell? 1 Thread braucht aktuell ca. 0,5-1 Sekunde. Wenn ich jetzt z.B. an 500 Tagen die Ticks brauche, dann brauche ich ja schon 500 DB Zugriffe in dem Thread. Vermutlich ist das dann deutlich langsamer?

Bevor ich jetzt anfange und ein paar Sachen davon ausprobieren, habe ich hier mal nachgefragt. Vielleicht hat jemand ja noch eine viel bessere Idee 

Wenn ich die Speicherauslastung runterbekomme, dann kann ich noch mehr Daten auf einmal testen. Alternativ müsste ich mir sonst einen Threadripper kaufen und 128 Gigabyte Speicher. Klar 32 Kerne und 128 Gigabyte Speicher macht es so oder so schneller, aber eventuell bekomme ich ja eine Performance hin, die mit 8 Kernen und 64 Gigabyte auch in Ordnung ist 

Danke!


----------



## Xyz1 (29. Jul 2019)

Fabse hat gesagt.:


> booleans oder auch BigDecimals.


BigDecimal braucht man bei Börsenkursen wahrlich nicht.



Fabse hat gesagt.:


> Wieso ist die Auslastung so extrem hoch


Das liegt an BigDecimal


----------



## httpdigest (29. Jul 2019)

Kannst du die Daten stattdessen nicht streamen, statt sie ALLE erstmal in den Speicher zu laden und DANN zu analysieren?
Das hangt jetzt davon ab, was du da genau analysierst. Vielleicht musst du auch nur pro Tag buffern und kannst einzelne Tage streamen.
Ich würde zumindest niemals alle Daten zeitgreich im Speicher halten.


----------



## Xyz1 (29. Jul 2019)

Na dann füge mal ein paar Beispieldaten hier ins Forum ein 

Dann können wir Dir sagen, wie Du sowas wie BigDecimal und LocalDateTime vermeiden tust... 

@httpdigest Das KANN manchmal sinnvoll sein... aber sicherlich nicht so


----------



## httpdigest (29. Jul 2019)

Tobias-nrw hat gesagt.:


> @httpdigest Das KANN manchmal sinnvoll sein... aber sicherlich nicht so


Was meinst du denn mit "aber sicherlich nicht so"? Ich hab ja nicht gesagt, WIE er das machen soll. Nur, dass er die Daten streamen soll und mit "streamen" meine ich: Jedes Datum verarbeiten, wenn er es einliest und nicht erst ALLES einlesen und dann ALLES verarbeiten. Solch eine Vorgehensweise ist generell IMMER sinnvoll, wenn es die Operation, die man auf den Daten durchführen möchte, zulässt.


----------



## kneitzel (29. Jul 2019)

Und Datenbank Ioannis durchaus sehr sinnvoll sein, auch bei mehreren Threads. Die Hersteller stecken da halt sehr viel KnowHow in Methoden, wie Daten in einem Cache gehalten werden können und wie am gezieltesten die benötigten Daten ausgelesen werden können.

Es sei denn, dass es einmalige Auswertungen sind, denn dann macht es wenig Sinn. Dann wäre tatsächlich der Vorschlag von @httpdigest gut, bei dem die Daten nur einmal beim Lesen ausgewertet wird. So musst Du Dir genau überlegen, welche Daten du brauchst. Wenn du nicht alle Ticks brauchst, dann ist es auch nicht notwendig, die Daten komplett in die Datenbank zu übernehmen.

(Und es kann natürlich auch Mix sein: beim Lesen findet direkt eine Vorverarbeitung statt um dann eine konsolidierte Datenbasis zu haben für die weitere Analyse.


----------



## Fabse (29. Jul 2019)

Beispieldaten kommen später.
Das mit dem Streamen wird glaube ich nicht funktionieren. Wenn ich alle Daten im Speicher habe, lasse ich parallel auf 15 Threads den gleichen Programmcode laufen nur mit unterschiedlichen Ausgangsparametern. 
Beispiel: Das heißt Thread 1 läuft über alle Daten rüber und schaut was passiert wäre, wenn nach 10€ Buch-Gewinn der Gewinn realisiert worden wäre, Thread 2 prüft das gleiche mit 15€ Gewinn usw.
Da es noch an mehr Bedingungen hängt, kann es sein das Thread 1 die Ticks von Tag 5 nicht braucht, aber Thread 2 schon.
Vorher raus zubekommen ob bestimmte Ticks von gar keinem Thread benötigt werden, ist nicht möglich.


----------



## mrBrown (29. Jul 2019)

Wenn die Streaming-Lösung mit einem Thread funktioniert, sollte das grundsätzlich auch mit mehreren Threads funktionieren - entweder weiter alle "Auswertungen" parallel mit Threads oder aber seriell hintereinander.


----------



## AndiE (29. Jul 2019)

Ich würde das blockweise verarbeiten. Von mir aus kann ein Block einen Tag beinhalten. Dann kann ich die Ticks für den Tag laden, auswerten und in eine Datei schreiben. Damit soll eine Verringerung der Datenmenge erreicht werden.


----------



## Xyz1 (29. Jul 2019)

Fabse hat gesagt.:


> Beispieldaten kommen später.


Es ist ganz einfach, ohne Beispieldaten keine Antwort von mir. Außerdem solltest Du besser an ein leichteres Thema herangehen, wenn Du die Basics überhaupt noch nicht verstehst.


----------



## mrBrown (29. Jul 2019)

Tobias-nrw hat gesagt.:


> Es ist ganz einfach, ohne Beispieldaten keine Antwort von mir. Außerdem solltest Du besser an ein leichteres Thema herangehen, wenn Du die Basics überhaupt noch nicht verstehst.


Falls du nur schlechte Stimmung verbreiten willst, halt dich doch bitte raus


----------



## mrBrown (29. Jul 2019)

AndiE hat gesagt.:


> Ich würde das blockweise verarbeiten. Von mir aus kann ein Block einen Tag beinhalten. Dann kann ich die Ticks für den Tag laden, auswerten und in eine Datei schreiben. Damit soll eine Verringerung der Datenmenge erreicht werden.


Dürfte doch Streaming mit Puffern entsprechen?


----------



## Xyz1 (29. Jul 2019)

mrBrown hat gesagt.:


> Falls du nur schlechte Stimmung verbreiten willst, halt dich doch bitte raus


Ach, der von TE offensichtlich angestellte Unsinn interessiert keinen, stattdessen antwortet ihr irgendwie mit Streams am Thema vorbei, aber ich verbreite schlechte Stimmung.


----------



## kneitzel (29. Jul 2019)

Also ich würde die Raten, dich mit der Mathematik im Detail auseinander zu setzen, damit du die Daten so komprimiert bekommst, dass Du die Werte, die zu den gewünschten Berechnungen notwendig sind, in komprimierter Form vorhalten kannst.

Oder Du brauchst tatsächlich einen Weg, bei dem Du die Daten in einem Weg liest und parallel die x Berechnungen durchführst (Da muss es aber eine triviale Berechnung sein).

Aber ohne Details, was du genau wie berechnen willst, wird Dir niemand Vorschläge machen können. Und selbst wenn: nicht ohne Grund sitzen da oft Mathematiker und arbeiten an diesen Berechnungen.... Es geht dabei ja nicht nur um 1:1 Umformungen sondern auch um Vereinfachungen mit Bestimmung der maximalen Abweichung und so. Wenn einen nur  die ersten 2 Nachkommastellen interessieren, dann ist es egal, wenn das Ergebnis um bis zu 0,005 abweicht vom korrekten Ergebnis.

Aber das ist ein Bereich, in dem ich bisher auch noch nicht aktiv war und das ich nur von Erzählungen von Kollegen kenne.

Ansonsten ist die Möglichkeit tatsächlich, die Daten, die man verarbeiten will, komplett zu nutzen und da massiv drüber zu gehen. Da ist aber die Frage tatsächlich, ob die Ressourcen ausreichen, die da sind. Für vieles gibt es Lösungen:
Auf große Datenmengen mit ‚wenig‘ Hauptspeicher zugreifen: Datenbanken!
Zu viele Klassen pro Datensatz kann problematisch sein, da ja deutlich mehr gespeichert werden muss als nur der eigentliche Wert (das war im ersten Post ja Dein Problem!)
Aber auf die Daten muss man ja auch zugreifen. Somit braucht man gewisse Datenstrukturen....

Aber ohne Details ist das ein raten. Du willst Hilfe, dann bist Du in der Bringschuld. Hinweise zu theoretischen Möglichkeiten hast Du bekommen.


----------



## Dukel (29. Jul 2019)

Hast du dir schonmal Big Data Tools wie Hadoop, Kafka oder Apache Spark angeschaut?


----------



## mihe7 (29. Jul 2019)

Fabse hat gesagt.:


> Beispieldaten kommen später.


Hm... vermutlich ist sein Rechner seit 7 h mit Swapping beschäftigt.


----------



## Xyz1 (30. Jul 2019)

mihe7 hat gesagt.:


> Hm... vermutlich ist sein Rechner seit 7 h mit Swapping beschäftigt.


mihe7, Schlag unter die Gürtellinie! Ab auf die stille Treppe.


----------



## Fabse (30. Jul 2019)

Erst mal danke für die Meinungen. Ich merke schon ich muss mehr Fleisch an den Knochen bringen, damit mich alle verstehen.
Die csv Dateien die ich verarbeite sehe wie folgt aus:
1. 1 Stunde Kerzen:

```
2014070110:00:00,1.36921,1.36928,1.36845,1.36898
2014070111:00:00,1.369,1.36972,1.3679,1.36938
2014070112:00:00,1.3694,1.36966,1.3687,1.36889
....
```
2. Ticks:

```
2018110109:00:00:086,1.13603,1.13605
2018110109:00:00:594,1.13601,1.13605
2018110109:00:00:916,1.13593,1.13597
...
```

Und werden dann in die Objekte gepackt (siehe oben).
Die 1 Stunden Kerzen sind kein Problem, da habe ich nur eine Auslastung von ca. 100mb.
Ja bei den Ticks brauche ich kein BigDecimal, das werde ich mal als erstes ausprobieren wie es dann aussieht. Das Date brauche ich aber, damit ich die Ticks zu den Stunden mappen kann. Klar ich kann mir das auch aus den Strings rauslesen, aber ob das performanter ist bzw. weniger Speicher frisst? Es ist auch gar nicht so wichtig wie lange es dauert bis ich alle Werte im Speicher habe. Wenn ich aktuell die csv Dateien auslese und die Ticks zu den Stunden mappe dauert das ~3-5 Minuten, da kann man ganz gewiss auch etwas optimiere, aber darum geht es mir erst mal nicht!

Wenn ich alles eingelesen habe erweitere ich das 1 Stunden Objekt mit ein paar zusätzlichen Parametern / Werten. Die bleiben aber für jeden Testlauf gleich!
Dann durchlaufe ich mehrere for schleifen und setze über diese die verschiedenen Parameter mit denen der Testlauf durchgeführt werden soll:

```
for (int prozentDcAbstand = 1; prozentDcAbstand <= 25; prozentDcAbstand = prozentDcAbstand
                                                                                      + 1) {

                prozentDcAbstandBd = new BigDecimal(prozentDcAbstand
                                                    * 0.01).setScale(2,
                                                                     RoundingMode.HALF_UP);

                for (int mindAbstandAbsolut = 3; mindAbstandAbsolut <= 10; mindAbstandAbsolut++) {

                    RunGenerateCandleSticks2.mindAbstandAbsolut = mindAbstandAbsolut;

                    for (int faktorHalfTp = 0; faktorHalfTp <= 10; faktorHalfTp++) {

                        if (faktorHalfTp == 10) {
                            faktorHalfTpDouble = 1;
                        } else {
                            faktorHalfTpDouble = new BigDecimal("0."
                                                                + faktorHalfTp).doubleValue();
                        }

                        for (int faktorFirstTp = 0; faktorFirstTp <= 10; faktorFirstTp++) {
                       
                        ....
```

Da kommen noch mehr for-Schleife deswegen kürze ich das hier ab.
Mit "mindAbstandAbsolut", "faktorHalfTp", "faktorFirstTp",... starte ich dann den Testlauf in einem seperaten Thread max. 16 Stück, wenn alle 16 Threads am arbeiten sind warte ich bis einer fertig ist und dann legt er wieder mit den nächsten Parametern los.

In dem Testlauf durchlaufe ich dann mit einer for-schleife alle 1 Stunden Kerzen und prüfe dann ob ich mit den übergegebenen Parametern in dieser Stundenkerze in einen Trade eingestiegen wäre, wenn ja durchlaufe ich die Ticks und hole mir dann den Preis wo ich eingestiegen wäre. Wenn Trades offen sind werden auch die Ticks geprüft wann ich wieder mit Gewinn oder Verlust ausgestiegen wäre. Durch die verschiedenen Parameter aus den for Schleifen verändern sich die Ein-und Ausstiege. Zum Schluss werden alle geschlossenen Trades zusammengerechnet und ein Ergebnis auf der Console ausgegeben. Dieses Ergebnis aus der Console kopiere ich dann manuell heraus und verarbeite es in Excel weiter (meist nach ein paar Stunden, wenn ich über 100.000 Ergebnisse habe). Ein Testdurchlauf dauert 0,5-1Sekunde pro Thread. Damit bekomme ich schon jede Menge Ergebnisse in ein paar Stunden zusammen. Wenn ich alle for-Schleifen zusammenrechne komme ich auf über 1 Million verschiedene Möglichkeiten...aber in bestimmten Bereichen breche ich ab und schärfe die Parameter nach.

Den gesamten Code zu posten sind weit über 1000 Zeilen, glaube nicht, dass das viel helfen würde.
Ist jetzt klar geworden wie ich vorgehe? Aktuell ist es so, dass ich einen Zeitraum von 4 Jahren abdecken kann. Ziel wäre mind. 8 Jahre abzudecken und dafür müsste ich die Speicherauslastung runterbekommen.
Wenn sich jeder Thread z.b. immer nur die Daten für einen Monat streamt, dann muss jeder Thread 48 mal für jeden Durchlauf sich die Daten von der Platte streamen. Ich vermute einen Monat einzulesen inkl. Ticks wird schon fast so lange dauern wie jetzt ein Durchlauf. Das gleiche bei einer Datenbank. Ziel ist die Speicherauslastung runterzubekommen, aber ein Testdurchlauf darf dadurch nicht wesentlich länger dauern! Deswegen kann es in meinen Augen nur funktionieren wenn alle Daten im Speicher sind. Alles andere würde das Programm verlangsamen.
Es sei denn ich habe den komplett falschen Ansatz!
Wenn ich nur die Daten aus den csv Dateien + Mapping gemacht habe, habe ich eine Auslastung von 50Gb, was dann danach kommt frisst vielleicht noch 2-3Gb aber das ist erst mal egal. Ist die ArrayList in dem Stunden Objekt wo alle Ticks drin hängen vielleicht auch sehr teuer?
Nach den BigDataTools habe ich noch nicht geschaut, werde ich mir aber mal anschauen.

EDIT: Ok ganz ohne LocalDate und mit float anstatt BigDecimal bin ich nach dem Auslesen bei 16Gb


----------



## mrBrown (30. Jul 2019)

Nur um zu gucken, ob ich das korrekt verstanden hab:
* Die zig Schleifen dienen nur dazu, einige "Testläufe" zu erstellen.
* Alle Testläufe sind unabhängig voneinander
* Ein Testlauf steigt bei bestimmen Werten ein/aus, ermittelt also, wie "gut" er für den jeweiligen Kursverlauf abgeschnitten hat?


Unter den Annahmen spricht nicht wirklich etwas gegen den Streaming-Ansatz. Ein Testlauf braucht (korrigier mich wenn ich falsch liege) nie alle Daten auf einmal, sondern nur Tick zu Zeitpunkt t_0, danach t_1, t_2, ..., t_n, (und u.U. noch (gleitender) Durchschnitt/min/max/sonstiges?).
Streaming heißt ja nicht, dass du das monatsweise einliest und die Datei dabei X mal einlesen muss - man würde das weiterhin einmal einlesen, aber eben nicht den gesamten Inhalt auf einmal im Speicher halten.


----------



## httpdigest (30. Jul 2019)

Yepp. Der einzige Grund, warum man `N` Daten im Speicher haben _müsste_, wäre, wenn bei einer Berechnung ein Datensatz an Stelle 0 von einem Datensatz an Stelle `N` abhängt (für ein beliebig großes `N`). Das ist aber bei den wenigsten Berechnungen der Fall.
Falls `N` aber eben nicht _alle_ Daten beinhaltet, sondern lediglich kleinere Chunks, wie zum Beispiel die jetzt das erste Mal angesprochenen "Tage" mit ihren "Kerzen", dann braucht man lediglich eine Puffergröße von `N`.
Auf der anderen Seite wollen aber die Speicherchip-Hersteller auch Geld verdienen. Und mit mehr Metall/Hardware konnte man noch immer jedes Problem erschlagen - und im Zweifel ist ein weiterer 64GB Riegel im Rechner günstiger als die Arbeitszeit, die ein besserer Algorithmus an Kosten verursachen würde.


----------



## Fabse (31. Jul 2019)

Super danke für die Antworten! Ja mrBrown du hast es richtig verstanden und ich habe somit den Streaming Ansatz noch nicht verstanden. Ich werde mich einlesen und wenn ich dann noch fragen habe, werde ich mich wieder melden.


----------



## Xyz1 (31. Jul 2019)

BigDecimal, LocalDateTime und Objekte sind für so etwas zu teuer. Anstatt float kannst du auch int nehmen. Du brauchst nicht zu jedem Zeitpunkt alle Daten im Speicher. Schreibe Dir einen Stream dafür.


----------



## Xyz1 (31. Jul 2019)

Zum Bleistift so:

```
import java.util.Arrays;
import java.util.Iterator;
import java.util.Random;
import java.util.Scanner;
import java.util.StringJoiner;

public class Aktien implements Iterator<int[]> {
	Strings1 s1 = new Strings1();
	String[] s = new String[1000];
	int i = 0;
	int j = 0;

	@Override
	public boolean hasNext() {
		return i < j || s1.hasNext();
	}

	@Override
	public int[] next() {
		if (i == j) {
			i = 0;
			while (i < s.length && s1.hasNext()) {
				s[i++] = s1.next();
			}
			j = i;
			i = 0;
		}
		String[] split = s[i++].split(",");
		int[] l = new int[] { 
				Integer.parseInt(split[0].split(":")[0]), 
				(int) (Float.parseFloat(split[1]) * 100000),
				(int) (Float.parseFloat(split[2]) * 100000), 
				(int) (Float.parseFloat(split[3]) * 100000),
				(int) (Float.parseFloat(split[4]) * 100000) };
		return l;
	}

	public static void main(String[] args) {
		new Aktien().forEachRemaining(l -> System.out.println(Arrays.toString(l)));
	}
}

class Strings1 implements Iterator<String> {
	String s = "2014070110:00:00,1.36921,1.36928,1.36845,1.36898\r\n"
			+ "2014070111:00:00,1.369,1.36972,1.3679,1.36938\r\n" 
			+ "2014070112:00:00,1.3694,1.36966,1.3687,1.36889";
	Scanner sca;

	Strings1() {
		Random r = new Random();
		StringJoiner j = new StringJoiner("\n");
		for (int i = 0; i <= 1111; i++) {
			j.add(2014070110 + i + ":00:00," 
					+ (int) (r.nextFloat() * 100000) / 100000f + ","
					+ (int) (r.nextFloat() * 100000) / 100000f + "," 
					+ (int) (r.nextFloat() * 100000) / 100000f + ","
					+ (int) (r.nextFloat() * 100000) / 100000f );
		}
		s = j.toString();
		sca = new Scanner(s);
	}

	@Override
	public boolean hasNext() {
		return sca.hasNextLine();
	}

	@Override
	public String next() {
		return sca.nextLine();
	}
}
```


Statt meiner super tollen Klasse Strings1 nimmst Du an der Stelle `s[i++] = s1.next();` einfach einen FileReader o.Ä.


----------



## AndiE (31. Jul 2019)

@Tobias-nrw: Kannst du für die Dummen wie mich mal erklären, wie das funktioniert?


----------



## mihe7 (31. Jul 2019)

AndiE hat gesagt.:


> @Tobias-nrw: Kannst du für die Dummen wie mich mal erklären, wie das funktioniert?


Was genau meinst Du?


----------



## AndiE (31. Jul 2019)

```
public static void main(String[] args) {
        new Aktien().forEachRemaining(l -> System.out.println(Arrays.toString(l)));
    }
```

Wie soll diese Zeile funktionieren?

Es ist kein Konstruktor deklariert. Selbst wenn ich annehme, dass dennoch ein verwei auf ein Aktion-Ob´jekt übergeben wird, werden zwar hasNext zwei mal implementiert, aber die Methode "forEachRemaining" des gleichen Interfaces aber nicht. Selbst wenn die Variable l vom Typ "int[]" ist, wie komme ich dann zu den Strings? 

Das mit dem "Scanner sca" kann ich auch nicht so richtig nachvollziehen. 

Wahrscheinlich habe ich zu wenig ahnung von funktionaler Programmierung, aber ich finde das so nicht so richtig nachvollzieh- und wartbar.


----------



## mihe7 (31. Jul 2019)

AndiE hat gesagt.:


> Es ist kein Konstruktor deklariert.


Standardkonstruktor.



AndiE hat gesagt.:


> aber die Methode "forEachRemaining" des gleichen Interfaces aber nicht.


default Implementierung im Interface.



AndiE hat gesagt.:


> wie komme ich dann zu den Strings?


java.util.Arrays.toString


----------



## mrBrown (31. Jul 2019)

AndiE hat gesagt.:


> Wie soll diese Zeile funktionieren?
> 
> Es ist kein Konstruktor deklariert. Selbst wenn ich annehme, dass dennoch ein verwei auf ein Aktion-Ob´jekt übergeben wird, werden zwar hasNext zwei mal implementiert, aber die Methode "forEachRemaining" des gleichen Interfaces aber nicht. Selbst wenn die Variable l vom Typ "int[]" ist, wie komme ich dann zu den Strings?


"Es ist kein Konstruktor deklariert"-> default Konstruktor
"werden zwar hasNext zwei mal implementiert" -> nur einmal je Klasse 
"aber die Methode "forEachRemaining" des gleichen Interfaces aber nicht" -> ist eine default-Methode, muss also nicht implementiert werden
"Selbst wenn die Variable l vom Typ "int[]" ist," -> ist sie 
"wie komme ich dann zu den Strings" -> zu welchen String, denen, die ausgegeben werden? Das macht doch genau `Arrays.toString`



AndiE hat gesagt.:


> Wahrscheinlich habe ich zu wenig ahnung von funktionaler Programmierung, aber ich finde das so nicht so richtig nachvollzieh- und wartbar.


Das liegt in diesem Fall nicht an Funktionaler Programmierung, sondern an grottenschlechtem Code


----------



## Xyz1 (31. Jul 2019)

Das sind quasi zwei Puffer, wobei er dann für Berechnungen noch einen dritten Puffer bräuchte.



AndiE hat gesagt.:


> Es ist kein Konstruktor deklariert


Na und? default



AndiE hat gesagt.:


> Selbst wenn die Variable l vom Typ "int[]" ist, wie komme ich dann zu den Strings


Im Consumer, nicht Supplier...



AndiE hat gesagt.:


> Wahrscheinlich habe ich zu wenig ahnung von funktionaler Programmierung


Green check mark ✅ Aber kein Grund zur Besorgnis.


----------



## Xyz1 (31. Jul 2019)

mrBrown hat gesagt.:


> Das liegt in diesem Fall nicht an Funktionaler Programmierung, sondern an grottenschlechtem Code


Behauptung entbehrt jeder Grundlage, schreibe es bei gleicher Funktionalität besser würde ich mal sagen.


----------



## mrBrown (31. Jul 2019)

Tobias-nrw hat gesagt.:


> Behauptung entbehrt jeder Grundlage, schreibe es bei gleicher Funktionalität besser würde ich mal sagen.


Die Variablen s1, s, i, j, split & l sollten Grundlage genug sein


----------



## Xyz1 (31. Jul 2019)

@mrBrown Ich kann nichts dafür wenn du kein Java kannst.


----------



## mrBrown (31. Jul 2019)

Ansonsten, zum Kürzen reduziert auf die Ticks mit 2 Werten statt mit 4:


```
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.*;

public class StreamingExample {

    static class Tick {
        //FALLS das wirklich noch ein Problem ist, kann man es ersetzen, zB mit epochSeconds/NanoSecods
        LocalDateTime localDateTime;
        int ask;
        int bid;
        public Tick(final LocalDateTime localDateTime, final int ask, final int bid) {
            this.localDateTime = localDateTime;
            this.ask = ask;
            this.bid = bid;
        }
    }

    //Wie auch immer die Testläufe aussehen
    private static class Testlauf {
        int max = Integer.MIN_VALUE;

        void processTick(Tick tick) {
            max = Math.max(tick.ask, max);
        }
    }

    public static void main(String[] args) {
        Testlauf testlauf = new Testlauf();
        readFile()//Ersetzen mit Files.lines()
                .map(StreamingExample::parseLine)
                .forEach(testlauf::processTick);
        
        System.out.println(testlauf.max);
    }

    static Stream<String> readFile() {
        long epochSeconds = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
        final Random random = new Random();
        return IntStream.range(0, 1111).mapToObj(
                i -> String.format("%s,%.5f,%.5f",
                                   LocalDateTime.ofEpochSecond(epochSeconds + i, 0, ZoneOffset.UTC),
                                   random.nextFloat(),
                                   random.nextFloat())
        );
    }

    static Tick parseLine(String line) {
        String[] columns = line.split(",");
        return new Tick(LocalDateTime.parse(columns[0]),
                        (int) (Float.parseFloat(columns[1]) * 100000),
                        (int) (Float.parseFloat(columns[2]) * 100000));
    }

}
```


----------



## Xyz1 (31. Jul 2019)

statische innere Klassen sind ein Unding,
unnötige Objekte,
hatten wir nicht über LocalDateTime gesprochen?
Note: 6, von mir. 

Bearbeitung: Achja - das hat natürlich nicht dieselbe Funktionalität.


----------



## mrBrown (31. Jul 2019)

Tobias-nrw hat gesagt.:


> statische innere Klassen sind ein Unding,


Mehrere Klasse pro Datei auch - einigen wir uns darauf, dass es einfach dem Format hier geschuldet ist und man in beiden Fällen mehrere Dateien mit Klassen hätte?



Tobias-nrw hat gesagt.:


> unnötige Objekte,


Da Java keine structs oder ähnliches bietet, muss man wohl Objekte nutzen. Arrays sind kein wirklicher Ersatz für Objekte, vor allem nicht in Hinblick auf Verständlichkeit und Erklärungen, worum es hier ja ging.



Tobias-nrw hat gesagt.:


> hatten wir nicht über LocalDateTime gesprochen?


Nein, nicht wir, nur du. Und weil du LocalDateTime hier nicht als sinnvoll ansieht, steht dort extra ein Kommentar genau dazu.



Tobias-nrw hat gesagt.:


> Bearbeitung: Achja - das hat natürlich nicht dieselbe Funktionalität.


Ich weiß. Allerdings würde ich die gleiche Funktionalität in deinen Augen wohl nur erreichen, wenn es genau der selbe Code ist.

Oder welche großen Unterschiede gibt es in der Funktionalität?
Die Reduzierung auf zwei statt 4 Worte? Ist erwähnt, der Kürze des Programms geschuldet und für die Veranschaulichung völlig unerheblich.
Hinzufügen von Testlauf statt einfach nur Ausgabe aus der Konsole? Dient der Verbesserung der Verständlichkeit im Kontext dieses Threads, wenn es dich stört, denk dir dort einfach einen entsprechendes println.


Falls die relevante Funktionalität, die dein Code zeigen soll, ist, wie man Arrays ausgibt - ja, dann ist das wirklich keine vergleichbare Funktionalität.


----------



## Xyz1 (31. Jul 2019)

mrBrown hat gesagt.:


> Da Java keine structs oder ähnliches bietet, muss man wohl Objekte nutzen. Arrays sind kein wirklicher Ersatz für Objekte, vor allem nicht in Hinblick auf Verständlichkeit und Erklärungen, worum es hier ja ging


Es ging hier um große Datenmengen, nicht besonders leserlicher Code. Und structs sind nebenbei unleserlicher als Array.

Wenn du zu derselben Lösung gelangt wärst, wieso spricht du dann von "grottenschlechten Code"? Das ist einfach das was man machen sollte. Das andere ist syntaktischer Zucker.

(So ähnlich wie eine Himbeere - schmeckt gut, hat aber keinen hohen Nährwert.)


----------



## mrBrown (31. Jul 2019)

Tobias-nrw hat gesagt.:


> Es ging hier um große Datenmengen, nicht besonders leserlicher Code. Und structs sind nebenbei unleserlicher als Array.
> 
> Wenn du zu derselben Lösung gelangt wärst, wieso spricht du dann von "grottenschlechten Code"? Das ist einfach das was man machen sollte. Das andere ist syntaktischer Zucker.


Okay, ich geh mal einen Schritt auf dich zu.

Es gibt dabei wohl zwei Streitpunkte: Lesbarkeit und Effizienz (und letztere in Geschwindigkeit und Speicher-Verbrauch) - zumindest gehst du auf die beiden in dem Beitrag ein.

Wir können ja mal versuchen, das möglichst objektiv zu vergleichen, (bzw, bei Lesbarkeit so gut es eben geht).


Wenn ich deinen Code richtig verstehe, ist das int-Array der Länge Fünf die Entsprechung für folgende Klasse:


```
public class Tick {
    int epochSeconds;
    int open;
    int high;
    int low;
    int close;
    //Boilerplate bewusst weggelassen
}
```

`epochSeconds` ist bewusst als int gewählt, damit es dem int-Array entspricht.
Die Beispieldaten des TO haben Millisekunden-Auflösung, man würde also eher einen long oder eben einen andere entsprechenden Datentyp wählen (oder das nur als Millisekunden innerhalb des Tages darstellen, könnte auch möglich sein). Würde aber bei einem Vergleich von Array und Klasse dazu führen, dass man entweder ein long-Array nutzen müsste (wodurch das doppelt so groß wäre, was ein Nachteil fürs Array wäre), oder Array und Klasse hätten unterschiedliche Wertebereiche (was ein unfairer Vergleich wäre, weil nicht mehr fachlich gleich). Deshalb in beiden alles als int.

Aber zurück zur Frage: wäre das eine passende Repräsentation der Daten als Klasse?


----------



## Xyz1 (31. Jul 2019)

mrBrown hat gesagt.:


> wäre das eine passende Repräsentation der Daten als Klasse


Ja, wäre es... Aber dann genügt es aus Platzgründen auch ein Array.

epochSeconds; könnte man ja sogar noch "kürzen", also wenn alle Sekunden-Anzahlen größer gleich eines bestimmten Werts wären...


----------



## mrBrown (31. Jul 2019)

Tobias-nrw hat gesagt.:


> Ja, wäre es... Aber dann genügt es aus Platzgründen auch ein Array.


Okay, vergleichen wir doch mal den Platzbedarf (Werte sind für 32-Bit*):

* Objekte haben einen 8-Byte Header, dann kommen in diesem Fall fünf mal 4 Byte für die ints hinzu, macht insgesamt 28 Byte.

* Arrays haben eine 12-Byte Header, dazu dann fünf mal 4 Byte für die ints, macht insgesamt 32 Byte.

Beide Werte müssen noch auf ein Vielfaches von 8 aufgerundet werden, macht 32 Byte vs. 32 Byte - Platzgründe sprechen also nicht für ein Array.

Stimmst du mir da zu oder siehst du da Fehler in meiner Rechnung?


Spoiler: * Erklärungen und 64-Bit und Compressed oops



Quelle: https://wiki.openjdk.java.net/display/HotSpot/CompressedOops
Fehler gern korrigieren 

32-Bit:

Der Header besteht aus zwei mal 4-Byte, beim Array zusätzlich 4-Byte für die Array-Länge.

64-Bit:

Objekte und Arrays haben jeweils zwei mal 8-Byte Header, beim Array zusätzlich 4 Byte für die Array-Länge und 4 Byte Padding.
Für Objekte macht 16 Byte für Header und 20 zusätzlich für Felder, mit Padding *40 Byte.*
Für Arrays macht das 24 Byte Header, 20 für Daten, mit Pudding also *48 Byte*.

64-Bit mit compressed oops:
Der Header besteht aus 8+4 Byte, die Array-Länge rutscht in die 4 Byte, die frei im Vergleich zu 64-Bit sind, das erste Feld des Objekts kann auch in die 4 freien Byte rutschen.
Macht also:
Für Arrays: 16 Byte + 20 für Daten, mit Padding *40 Byte*
Für Objekte: 16 Byte (in denen das erste Feld schon enthalten ist) + 16 Byte (für die 4 restlichen Felder), also *32 Byte*



Bleibt noch Performance in Bezug auf Zeit und Lesbarkeit als Möglichkeiten für den Vergleich, richtig?



Tobias-nrw hat gesagt.:


> epochSeconds; könnte man ja sogar noch "kürzen", also wenn alle Sekunden-Anzahlen größer gleich eines bestimmten Werts wären...


Kleiner als ein int wird man es aber nicht bekommen, bzw hätte es keinen wirklichen Vorteil (auf Seiten des int-Arrays braucht man sowieso ints, im Objekt könnte man den int bei gleicher Größe durch einen long ersetzen).
Oder wie würdest du das anders als mit einem int darstellen?


----------



## AndiE (31. Jul 2019)

Ich bin da noch nicht so glücklich. Nehmen wir mal an, die einen Daten sind 2,5 Mega und die anderen 5000 Megabyte. Nehme ich mal weiter an, dass ich beim ersten 50 Byte brauche( rechnet sich leichter), dann sind das 50000 Datensätze, und das andere, da ja auch nu aus 3 int+DateTime bestehend(2*4)+8=16 Byte. Da sind dann ca. 3 Millionen Datensätze. In dem Falle sind es zwar nur 60 Datensätze je Tick(einer je Minute), aber die Beispielzahlen lassen auf bis zu 10000 Datensätze je Tick( 3 je Sekunde) schließen. Theoretisch ist das alles machbar, da die Begrenzung der Größe für Arrays bei 2 Milliarden liegt. Dennoch würde ich die Pufferung nicht an das System abgeben, sondern selbst machen. 

Wieso?

Ich bin immer noch eher in der Auffassung verhaftet, dass man nur über Dinge iterieren sollte, die im Speicher sind. Jedenfalls bei Java. Es gibt zwar Streamdeitoren, die würde ich aber nicht typisch für das Java-Konzept sehen.


----------



## mihe7 (31. Jul 2019)

AndiE hat gesagt.:


> Ich bin immer noch eher in der Auffassung verhaftet, dass man nur über Dinge iterieren sollte, die im Speicher sind. Jedenfalls bei Java.


Sind sie ja - immer eins


----------



## mrBrown (31. Jul 2019)

AndiE hat gesagt.:


> 3 int+DateTime bestehend(2*4)+8=16 Byte


Soll das eine Rechnung auf Java-Ebene sein? Da muss man die gänzlich anders berechnen, sie zB meinen Beitrag, der sich mit deinem überschnitten hat 





AndiE hat gesagt.:


> Dennoch würde ich die Pufferung nicht an das System abgeben, sondern selbst machen.


In welchem Beispiel siehst du Pufferung durchs System?
In meinem vielleicht impliziert durch den Stream, aber auf der Ebene würde man sowieso nicht selbst puffen. In dem anderen Beispiel könnte man es als Puffern bezeichnen, allerdings hat das nicht wirklich was mit Streaming zu tun, wenn alles in ein Array gelesen wird, bevor was anderes passiert.




AndiE hat gesagt.:


> Ich bin immer noch eher in der Auffassung verhaftet, dass man nur über Dinge iterieren sollte, die im Speicher sind. Jedenfalls bei Java. Es gibt zwar Streamdeitoren, die würde ich aber nicht typisch für das Java-Konzept sehen.


Alle InputStreams und Reader in Java funktionieren so - sie lesen nur jeweils einen begrenzten Teil und nicht ganze Dateien in einem Rutsch (außer die Datei ist klein genug, zB kleiner als der Puffer beim BufferedInputStream)


----------



## Fabse (1. Aug 2019)

Ich sage schon mal vielen Dank für die lebhafte Diskussion, da kann jeder etwas lernen 
Feedback von mir wird aber dauern, da ich aktuell nicht allzu viel zeit habe....
Aber das hindert euch natürlich nicht hier weiter zu diskutieren


----------



## mrBrown (1. Aug 2019)

Hey @Tobias-nrw, gibts gar keine Antwort mehr?


----------



## Xyz1 (1. Aug 2019)

mrBrown hat gesagt.:


> Hey @Tobias-nrw, gibts gar keine Antwort mehr?


Is doch schon alles gesagt...


----------



## mrBrown (1. Aug 2019)

Tobias-nrw hat gesagt.:


> Is doch schon alles gesagt...


Schade, ich dachte du sagst noch was dazu, warum Arrays besser sind...


----------



## Xyz1 (1. Aug 2019)

mrBrown hat gesagt.:


> Schade, ich dachte du sagst noch was dazu, warum Arrays besser sind...


Überprüft doch einfach mal die Speichernutzung:



> long maxMemory = runtime.maxMemory();
> long allocatedMemory = runtime.totalMemory();
> long freeMemory = runtime.freeMemory();


----------



## mrBrown (1. Aug 2019)

Tobias-nrw hat gesagt.:


> Überprüft doch einfach mal die Speichernutzung:


Und da kommt dann was bei raus?

Das Objekte in diesem Fall nicht mehr, eher weniger, Platz brauchen als Arrays, hab ich doch extra detailliert vorgerechnet.

Wenn dir das nicht reicht gibts auch noch 'nen Benchmark dazu (im wesentlichen dein Code, und int[] durch Tick ersetzt), kommt das bei raus:


```
Benchmark                                                  Mode  Cnt        Score        Error   Units
AktienBenchmark.intArray                                   avgt   20  1356949,644 ±  18142,629   ns/op
AktienBenchmark.intArray:·gc.alloc.rate                    avgt   20     1016,320 ±     17,345  MB/sec
AktienBenchmark.intArray:·gc.alloc.rate.norm               avgt   20  3401686,609 ±    151,942    B/op
AktienBenchmark.tick                                       avgt   20  1329285,886 ±   9449,018   ns/op
AktienBenchmark.tick:·gc.alloc.rate                        avgt   20     1039,001 ±     10,755  MB/sec
AktienBenchmark.tick:·gc.alloc.rate.norm                   avgt   20  3392785,908 ±    170,487    B/op
```

Mit Objekten also nicht nur theoretisch, sondern auch real besser.
Der Unterschied zwischen beiden sind (Fehler außer acht gelassen) in diesem Fall 8.900 Byte, was ziemlich genau dem rechnerischen Unterschied von 8896 Byte entspricht, die fehlenden 6 Byte sind der Messungenauigkeit geschuldet.


Und mit dem Benchmark hat man auch gleich die Laufzeiten verglichen, mit Objekten braucht weder mehr Speicherplatz noch mehr Zeit als mit Arrays


----------



## Xyz1 (1. Aug 2019)

Ich weiß gar nich was du da getestet hast, aber bestimmt nicht das richtige


----------



## mrBrown (1. Aug 2019)

Tobias-nrw hat gesagt.:


> Ich weiß gar nich was du da getestet hast, aber bestimmt nicht das richtige



Benchmark findest du hier: https://gitlab.com/mrBrown/benchmarks

Wie gesagt, entspricht deinem Code, nur das int[] durch Tick ersetzt wurde. Der Benchmark ist sicher nicht der beste, du darfst den also gerne verbessern 
Für diese Problemstellung ist er aber vermutlich gut genug, da der Code in beiden nahezu äquivalent ist, und das Ergebnis weicht auch nicht vom erwarteten ab.


----------



## Xyz1 (1. Aug 2019)

Es ging erstmal nicht um Zeit sondern um Speicher.


----------



## mrBrown (1. Aug 2019)

Tobias-nrw hat gesagt.:


> Es ging erstmal nicht um Zeit sondern um Speicher.



Wenn du das einfach mal ausführen würdest, würdest du sehen, dass es auch den Speicherverbauch misst  

Ich hätte ja gedacht, dass "gc", "alloc" und "MB"/"B" Hinweise genug sind, dass es sich auf den Speicher und nicht auf die Zeit bezieht...
`gc.alloc.rate` ist die Angabe des während des Benchmarks alloziierten Speichers in MB/sec, `gc.alloc.rate.norm` ist das ganze normalisiert in B/op.

Dachtest du wirklich, die Zeit wird in MB gemessen?


----------

