# Parallelisierung von Severanfragen?



## Conax (16. Aug 2018)

Vielen Dank erstmal an Alle die mir letztes mal geholfen haben. Die gute Nachricht ist das Programm macht im Prinzip was es soll - es holt mir von den verschiedenen Cryptobörsen den Betrag für das günstigste Angebot. Das Problem ist aber die Performance - Anfragen an die selbe Börse über unterschiedliche Coins werden jedesmal einzeln gestellt(sprich erst Verbindungsaufbau Anfrage z.B. bei anycoindirect Preis für Bitcoin dann wartet das Programm auf Feedback dann hole ich die Daten für Ethereum usw. usv. ) d.h. entweder müsste ich eine große Anfrage an den Server stellen statt jedesmall dieses Frage-Antwort Spiel oder wenn das nicht möglich sein sollte erstmal lauter kleine Anfragen parallel versenden und wenn dann die Antworten kommen diese entsprechend zuordnen. Was vielleicht auch einen Performancegewinn verspricht sind verschiedene Threads sprich für jede Kryptobörse einen Thread. Das sind zumindest die Gedankenspiele die ich mir gemacht habe.

Gedankenspiele sind das eine aber wie man so etwas konkret umsetzt ja wieder etwas völlig anderes weswegen ich wie jeder erstmal gegoogelt habe um mir Anreize zu holen:
https://docs.oracle.com/javase/tutorial/networking/datagrams/broadcasting.html
https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html
https://www.foreach.be/blog/parallel-and-asynchronous-programming-in-java-8

Hier mal der Code wie ich bisher die Daten beziehe als Parser habe ich diesen hier eingesetzt:
https://github.com/fangyidong/json-simple


```
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

// für Bitcoin
            URL url1 = new URL("https", "anycoindirect.eu",
                    "/api/public/buyprices?CoinCode=BTC&FiatCode=EUR&CoinAmount=1");

            // für Ethereum
            URL url2 = new URL("https", "anycoindirect.eu",
                    "/api/public/buyprices?CoinCode=ETH&FiatCode=EUR&CoinAmount=1");

            // für Litecoin
            URL url3 = new URL("https", "anycoindirect.eu",
                    "/api/public/buyprices?CoinCode=LTC&FiatCode=EUR&CoinAmount=1");

try (BufferedReader in = new BufferedReader(new InputStreamReader(url1.openStream())))
            {
                String inputLine;

               
                
                System.out.println("/***** File content Bitcoin *****/n");
                while ((inputLine = in.readLine()) != null)
                {
                    // System.out.println(inputLine);

                    JSONParser parser = new JSONParser();
                    JSONObject obj2 = (JSONObject) parser.parse(inputLine);

                    
                    JSONArray lang = (JSONArray) obj2.get("Data");
                    // @SuppressWarnings("rawtypes")
                    Iterator i = lang.iterator();

                    while (i.hasNext())
                    {
                        JSONObject innerObj = (JSONObject) i.next();
                       
                        System.out.println(innerObj.get("FiatAmount"));
                        anycoinbtc = (double) innerObj.get("FiatAmount");
                        break;
                    }
                }
            } catch (ParseException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException ioe)
            {
                ioe.printStackTrace(System.err);
            }

            try (BufferedReader in = new BufferedReader(new InputStreamReader(url2.openStream())))
            {
                String inputLine;

                
                System.out.println("/***** File content Ethereum *****/n");
                while ((inputLine = in.readLine()) != null)
                {
                    // System.out.println(inputLine);

                    JSONParser parser = new JSONParser();
                    JSONObject obj2 = (JSONObject) parser.parse(inputLine);

                    JSONArray lang = (JSONArray) obj2.get("Data");
                    // @SuppressWarnings("rawtypes")
                    Iterator i = lang.iterator();

                    while (i.hasNext())
                    {
                        JSONObject innerObj = (JSONObject) i.next();
                        
                        System.out.println(innerObj.get("FiatAmount"));
                        anycoineth = (double) innerObj.get("FiatAmount");
                        break;
                    }
                }
            } catch (ParseException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException ioe)
            {
                ioe.printStackTrace(System.err);
            }

            try (BufferedReader in = new BufferedReader(new InputStreamReader(url3.openStream())))
            {
                String inputLine;

               
                System.out.println("/***** File content Litecoin *****/n");
                while ((inputLine = in.readLine()) != null)
                {
                    // System.out.println(inputLine);

                    JSONParser parser = new JSONParser();
                    JSONObject obj2 = (JSONObject) parser.parse(inputLine);

                    JSONArray lang = (JSONArray) obj2.get("Data");
                    // @SuppressWarnings("rawtypes")
                    Iterator i = lang.iterator();

                    while (i.hasNext())
                    {
                        JSONObject innerObj = (JSONObject) i.next();
                        
                        System.out.println(innerObj.get("FiatAmount"));
                        anycoinltc = (double) innerObj.get("FiatAmount");
                        break;
                    }
                }
            } catch (ParseException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException ioe)
            {
                ioe.printStackTrace(System.err);
            }
```

Hat jemand Erfahrung wie ich besagtes Performanceproblem lösen kann?


----------



## mrBrown (17. Aug 2018)

Conax hat gesagt.:


> Hat jemand Erfahrung wie ich besagtes Performanceproblem lösen kann?


Bei den einzelnen Seiten nach einer passenden API suchen (damit zB alle Werte auf einmal angefragt werden können) und zusätzlich parallelisieren, am passendsten dürfte da ExecutorService sein


----------



## Thallius (17. Aug 2018)

mrBrown hat gesagt.:


> Bei den einzelnen Seiten nach einer passenden API suchen (damit zB alle Werte auf einmal angefragt werden können) und zusätzlich parallelisieren, am passendsten dürfte da ExecutorService sein



Ich denke nicht dass eine Seite ihre Daten als quasi komplett Dump kostenlos in einer API zur Verfügung stellt. Programme wie sie der TO schreibt sind sicher nicht im Interesse der Seitenbetreiber.


----------



## mrBrown (17. Aug 2018)

Thallius hat gesagt.:


> Ich denke nicht dass eine Seite ihre Daten als quasi komplett Dump kostenlos in einer API zur Verfügung stellt. Programme wie sie der TO schreibt sind sicher nicht im Interesse der Seitenbetreiber.


Warum sollte eine API für Einzeldaten angeboten werden, aber eine die mehrere Daten zusammenfasst (und damit auch den Server massiv entlastet) „nicht im Interesse der Seitenbetreiber“ sein?


----------



## Thallius (17. Aug 2018)

mrBrown hat gesagt.:


> Warum sollte eine API für Einzeldaten angeboten werden, aber eine die mehrere Daten zusammenfasst (und damit auch den Server massiv entlastet) „nicht im Interesse der Seitenbetreiber“ sein?



Weil bei den ganzen API auch nur eine maximale Anzahl Anfragen erlaubt ist. Die sind ganz bewusst nicht dafür gemacht was der TO will


----------



## mrBrown (17. Aug 2018)

Thallius hat gesagt.:


> Weil bei den ganzen API auch nur eine maximale Anzahl Anfragen erlaubt ist. Die sind ganz bewusst nicht dafür gemacht was der TO will


Öffentlich dokumentierte APIs sind nicht dazu da, benutzt zu werden? 

Ob begrenzt oder nicht ist zumindest völlig egal, wenn es um das Abfragen von Einzeldaten vs. Abfragen von Collections geht, das ist in beiden Fällen völlig gleichwertig umsetzbar...


----------



## mihe7 (17. Aug 2018)

@mrBrown kann es sein, dass Du und @Thallius aneinander vorbei schreibt? 

Technisch spielt es freilich oft kaum eine Rolle, ob Einzel- oder "Sammel"anfragen beantwortet werden. Auf der anderen Seite haben Dienstleister ein Interesse daran, dass die Leute für die Inanspruchnahme von Leistungen bezahlen. 

Da ist die künstliche Verknappung des Gutes eine Möglichkeit und in diesem Zusammenhang macht es natürlich einen entscheidenden Unterschied, ob 10 Einzel- oder 10 Sammelanfragen gratis sind


----------



## Xyz1 (17. Aug 2018)

Worum geht es hier in dem Thema den nochmal genau (kurz und knapp)?  Erbitte Rekapitulation....


----------



## mihe7 (17. Aug 2018)

DerWissende hat gesagt.:


> Worum geht es hier in dem Thema den nochmal genau (kurz und knapp)?


Mehrere REST-Requests parallel absetzen und JSON-Antwort verarbeiten.


----------



## mrBrown (17. Aug 2018)

mihe7 hat gesagt.:


> @mrBrown kann es sein, dass Du und @Thallius aneinander vorbei schreibt?
> 
> Technisch spielt es freilich oft kaum eine Rolle, ob Einzel- oder "Sammel"anfragen beantwortet werden. Auf der anderen Seite haben Dienstleister ein Interesse daran, dass die Leute für die Inanspruchnahme von Leistungen bezahlen.
> 
> Da ist die künstliche Verknappung des Gutes eine Möglichkeit und in diesem Zusammenhang macht es natürlich einen entscheidenden Unterschied, ob 10 Einzel- oder 10 Sammelanfragen gratis sind


Naja, in dem Code oben wird offensichtlich eine API benutzt, der Anbieter scheint also nichts dagegen zu haben, dass die API benutzt wird.

Wenn man das begrenzt auf insgesamt 10 Anfragen macht’s natürlich ne Unterschied, aber wenn man das (was sinnvoll wäre), nicht auf die Anzahl der Anfragen sondern die Anzahl der Abgefragten Werte bezieht (eine Sammelabfrage mit 10 Werten = 10 Einzelabfragen) macht’s wieder keinen Unterschied in der Menge der Daten.

In diesem Fall ist die Dienstleistung aber auch der Handel mit „Coins“ und nicht mit Wechselkursen. Da würde ich erwarten, dass die freie Verfügbarkeit von Wechselkursen eher den Verkauf fördert bzw ihn zumindest nicht negativ beeinflusst...


----------



## Xyz1 (17. Aug 2018)

mihe7 hat gesagt.:


> Mehrere REST-Requests parallel absetzen und JSON-Antwort verarbeiten.


Es geht doch eh nur sequenziell raus und rein. Was soll der Mumpitz


----------



## mrBrown (17. Aug 2018)

DerWissende hat gesagt.:


> Es geht doch eh nur sequenziell raus und rein. Was soll der Mumpitz


Mit Mumpitz meinst du deine Antwort?


----------



## Xyz1 (17. Aug 2018)

Nein gleich drehen wir uns wieder im Kreis => keine Ahnung => du schon gar nicht => keine Ahnung usw.
Er kann natürlich das zwischen rein/raus durchaus überbrücken, aber ich bezweifle das das bei diesem Setting irgendeinen Sinn macht....


----------



## mihe7 (17. Aug 2018)

mrBrown hat gesagt.:


> Naja, in dem Code oben wird offensichtlich eine API benutzt, der Anbieter scheint also nichts dagegen zu haben, dass die API benutzt wird.


Ich habe mir deren Nutzungsbedingungen nicht angesehen. Es kann natürlich sein, dass es ihnen völlig gleich ist, wer wie oft ihrer Server nutzt.



mrBrown hat gesagt.:


> aber wenn man das (was sinnvoll wäre), nicht auf die Anzahl der Anfragen sondern die Anzahl der Abgefragten Werte bezieht


Das ist sicher "sinnvoll" aus technischer Sicht. Wirtschaftlich will ich den Benutzer gerade keinen Komfort für lau bieten, sondern ihn dazu animieren, für Mehrwert zu bezahlen.



mrBrown hat gesagt.:


> In diesem Fall ist die Dienstleistung aber auch der Handel mit „Coins“ und nicht mit Wechselkursen.


Die Dienstleistung besteht hier aus dem Anbieten eines (Web-)Services. Ob die Firma ansonsten mit Coins handelt oder Kühe melkt und Milch verkauft, interessiert doch nicht. Amazon war auch mal nur Buchhändler. 

Dass das teilweise absurde Züge annehmen kann, ist klar: bei der Post konnte man sich früher ein Programm kaufen(!), mit diesem und einer monatlichen Nutzungsgebühr(!) man doch tatsächlich in die Lage versetzt wurde, Dienstleistungen bei der Post zu bestellen. Mehrwert: man brauchte keine Frankiermaschine mehr.


----------



## Conax (18. Aug 2018)

Also bisher beinhaltet das Programm 5 APIs von Kryptobörsen, 1 API vom Händler (anycoin direct) und 3 APIs für andere Dinge (Difficulty, block reward, Wechselkurse). Es soll ja einfach nur mal einen Überblick geben wo Coin XYZ gerade am günstigsten zu bekommen ist - früher hatte dafür der Börsenmarkler seine 5 Bildschirme an der Wand und heute gibt er ja auch das Wertpapier ein und sieht sofort wo dieses am günstigsten aktuell gehandelt wird. Außerdem ist natürlich der sog. Spread zwischen den Handelsplätzen auch sehr interessant und vor allem wodurch dieser ausgelöst wird bsp. wenn die Südkoreaner auf bithumb kaufen wie verrückt. Über manche der APIs lassen sich ja sogar Kauf- bzw. Verkaufsaufträge durchführen und jede Transaktion bringt der Handelsplattform schließlich Gebühren von daher wäre es ja unproduktiv Beschränkungen einzuführen. 

Ich wüsste auch nicht warum sollte ein Händler wie anycoin direct (dies ist ja kein Marktplatz im klassischen Sinne) eine extra Gebühr verlangen um per API die "Premiumdaten" (also ein Datenpaket welches von allen Coins den aktuellen Preis enthält) frei zu geben. Ich bin ja sogar auf der Seite angemeldet aber eine Zusatzoption für spezielle API Features lässt sich auch nicht buchen.

Mein persönlicher Eindruck ist eher der das die Börsen und Händler alle über ihre API angebunden werden wollen (sonst würde man es ja erst gar nicht anbieten) aber die ganze Umsetzung aktuell noch stark in den Kinderschuhen steckt (das fängt ja mit der Doku zu den APIs schon an). Das Problem sehe ich also eher in der praktischen Herangehensweise - wie kann ich das was mir zur verfügung steht optimal nutzen? Über Philosophie, Konzept und was die Handelsplattform mit der API bezweckt können wir hier sicherlich lange mutmaßen halte ich aber für wenig zielführend.


----------



## mihe7 (18. Aug 2018)

Conax hat gesagt.:


> Es soll ja einfach nur mal einen Überblick geben wo Coin XYZ gerade am günstigsten zu bekommen ist



Das Ziel ist schon klar, aber genau das ist doch der Nutzen, den Du aus den Leistungen ziehst, den die Händler/Börsen anbieten: Du verwendest APIs, damit Du Dir automatisiert einen Überblick verschaffen kannst, wo Du am günstigsten an Produkt X kommst. 

Schön, wenn das gratis möglich ist, selbstverständlich ist das allerdings nicht. Denn es ist schon etwas anderes als die Nutzung einer API zur Kaufabwicklung.



Conax hat gesagt.:


> Das Problem sehe ich also eher in der praktischen Herangehensweise


Die hat @mrBrown doch hier schon beschrieben.


----------



## Xyz1 (18. Aug 2018)

@mihe7 Man kann doch eigentlich nur die "Latenz" bei vielen, kleinen Anfragen zur Gegenseite überbrücken - anderes wäre unsinvoll.

(Wenn ich zum Bleistift eine "Latenz" von 10ms nach Frankfurt habe, könnte ich zum Beispiel 10 Anfragen gleichzeitig nach Frankfurt wegschicken um eben nicht 100ms sonder 10 bis 20ms nur zu brauchen. Was anschließend geschieht muss auch nochmal ausklamüsert werden!!)

Jetzt lese ich etwas mit "Bitcoin" - da stellt sich die Frage, was wann wie schnell sein soll! 

Es geht doch hoffentlich nicht um micro- oder nano- Trading!!


----------



## Xyz1 (18. Aug 2018)

mihe7 hat gesagt.:


> Komfort für lau


Ihr immer mit euren Services und Komfort (für Lau) - Die Dienstleistungsanbieter wollen doch auch nur ihrer Arbeit nachkommen.... 



mihe7 hat gesagt.:


> Das Ziel ist schon klar, aber genau das ist doch der Nutzen, den Du aus den Leistungen ziehst, den die Händler/Börsen anbieten: Du verwendest APIs, damit Du Dir automatisiert einen Überblick verschaffen kannst, wo Du am günstigsten an Produkt X kommst.



Erster Schritt wäre Dich als Roboter zu erkennen zu geben, wenn Du dann keine reliable Antwort bekommst dann weißt Du schonmal => Wahrscheinlich verboten.



Conax hat gesagt.:


> in der praktischen Herangehensweise



Was möchtest du wissen?


----------



## mihe7 (18. Aug 2018)

DerWissende hat gesagt.:


> Man kann doch eigentlich nur die "Latenz" bei vielen, kleinen Anfragen zur Gegenseite überbrücken - anderes wäre unsinvoll.


Die Latenz ist aber gerade bei vielen kleinen Anfragen relativ hoch. 

Mal praktisch: 

100 Anfragen sequentiell:

```
Total transferred:      126589 bytes
HTML transferred:       56800 bytes
Requests per second:    9.36 [#/sec] (mean)
Time per request:       106.858 [ms] (mean)
Time per request:       106.858 [ms] (mean, across all concurrent requests)
Transfer rate:          11.57 [Kbytes/sec] received
```

100 Anfragen mit 4 parallelen Verbindungen:

```
Total transferred:      126505 bytes
HTML transferred:       56800 bytes
Requests per second:    30.22 [#/sec] (mean)
Time per request:       132.350 [ms] (mean)
Time per request:       33.088 [ms] (mean, across all concurrent requests)
Transfer rate:          37.34 [Kbytes/sec] received
```


----------



## Bontik (20. Aug 2018)

Macht es wirklich mit allen Coins, Hauptasche das Angebot ist gut oder nur die, die du willst? 

Wenn du aussuchen kannst, dann fokussiere dich auf Dash. Ich muss ehrlich sein, dieser Währung ist heutzutage sehr gut und ihre Zukunft ist wirklich hell, deshalb die Empfehlung. 
Ein Freund von mir hat es vor kurzem gekauft und kam sehr gut zurecht damit. Auf einer Seite (klicken) sammelte er sehr gute Infos und bekam so einen klaren Überblick. Auf diese Art konnte er sehr leicht vorankommen und jetzt profitiert er ganz gut. 

Grüß!


----------



## daybyter (25. Aug 2018)

Ich hab auch meine Monate mit diesen Rest APIs gekämpft und es irgendwann aufgegeben. 

Schau Dir mal die Websocket API's einiger Börsen an und evtl Xchange-Stream. Wir ham das damals aber nicht stabil ans Laufen gebracht und mussten was eigenes basteln.

Noch besser sind aber Fix (oder schnellere) Verbindungen, wo Du alles im Stream erledigst. Bastle gerade an einer Tradeapp eben dafür.


----------



## Thallius (25. Aug 2018)

daybyter hat gesagt.:


> Ich hab auch meine Monate mit diesen Rest APIs gekämpft und es irgendwann aufgegeben.



Nicht dein Ernst oder? Der große Vorteil von REST ist dich gerade das es sehr simpel und dadurch universell ist. Da frage ich mich echt wie man das nicht hinbekommen kann.


----------



## daybyter (25. Aug 2018)

Da hast Du mich irgendwie falsch verstanden. Ich hatte die Abfragen am Laufen, aber nicht in dem Tempo, welches ich gebraucht hätte. Mein Bot sollte knapp 385000 Handelswege überwachen und ich konnte einfach die Preise nicht schnell genug bekommen. Hab es dann mit Proxies versucht, aber das war nix stabiles. Deshalb der Umstieg auf Streams.
Hab übrigens sogar einen Teil meines Rest Codes in github hochgeladen.


----------



## Xyz1 (25. Aug 2018)

alles ist nebulös....


daybyter hat gesagt.:


> 385000 Handelswege überwachen


wenn ich Dich richtig verstehe dann ist ca. 148,225 Milliarden Berechnungen für ein mobilephone eine sportliche Leistung und nicht das RESTfullying ist das Problem.


----------



## Xyz1 (25. Aug 2018)

nebenbei Das würde nicht 'parallelisiert' ca. 123 Tage dauern.


----------



## daybyter (25. Aug 2018)

Wie kommst Du auf diese Zahlen? Meine Wege waren max 5 Schritte lang. Also 5 Multiplikationen und 5 Additionen. Aber dazu die Optimierung der gehandelten Summe, die viel Zeit braucht, wenn man es richtig macht. 

Ich war irgendwann an dem Punkt, wo ich das auf der GPU rechnen wollte, aber ich konnte ausreichend unprofitable Wege schnell genug entfernen.

Das lief auf nem normalen Quadcore Desktop.

http://i.imgur.com/EgiTKPQ.png


----------



## Xyz1 (25. Aug 2018)

Ah, dann vergiss das mit den Berechnungen wieder ich kenne den Algorithmus ja nicht
Also dauert nur das RESTfullying lange?


----------



## daybyter (25. Aug 2018)

Nein, das Problem waren die Zugriffslimitierungen der Anbieter. Da wurde teilweise der Zugriff auf 1 http Request alle 5s beschränkt. 

Das ist absurd, wenn Du paar hundert Preise von denen brauchst. Also wurden Lösungen wie Proxies eingebaut und zeitweise liefen die Abfragen dann über bis zu 600 Proxies.

Aber wenn Du mal einen Streaming Bot gebaut hast, siehst DuDu, dass das alles Mist ist. Über diese Streams bekommst Du ja einerseits locker über 1000 Preise pro Sekunde und der Broker liefert Dir ja direkt die Änderungen. Du musst nicht mehr pollen, auf welchem Markt sich was tut.


----------



## Thallius (25. Aug 2018)

kapier ich alles nicht. Wozu brauchst du einen REST Server wenn du ein Frontend baust? REST ist das Backend und wenn du die Daten von fremden Servern sammelst, dann kannst du diese auch nur so sammeln wie sie dir dieser Server zur Verfügung stellt. Wenn der also nur eine Anfrage pro Sekunde pro Nutzer erlaubt und du gehst über hunderte von proxies um das zu umgehen, dann handelst du hochgradig illegal und ich würde verdammt aufpassen dass ich dabei nicht erwischt werde. 
Was du jetzt genau mit Steams meinst die du jetzt benutzt erschließt sich mir auch nicht.


----------



## mihe7 (26. Aug 2018)

Thallius hat gesagt.:


> Was du jetzt genau mit Steams meinst die du jetzt benutzt erschließt sich mir auch nicht.


Er meint vermutlich Reactive Streams.


----------



## Thallius (26. Aug 2018)

mihe7 hat gesagt.:


> Er meint vermutlich Reactive Streams.



Habe mir das gerade mal durchgelesen. Ist ja echt ein Witz. Da erfindet jemand etwas das Normalerweise jeder etwas intelligente Programmierer sowieso so umsetzen würde und gibt ihm einen wichtigen Namen.

Wer bitte programmiert denn bitte blockig Messages an andere devices? Egal ob es ein anderer Server  oder einfach nur ein USB Stick ist. Sobald ich nicht 100% sicher sein kann, dass der messagecempfänger innerhalb von Millisekunden antwortet arbeite ich asynchron.

Da muss doch keiner für herkommen und ein „Reactive“ Design Pattern definieren und eine Reactive API schreiben....


----------



## mrBrown (26. Aug 2018)

Du kanntest reactive Streams nicht, aber hälst sie für etwas was jeder Programmierer machen würde? 

Also entweder bist du ein unglaubliches Genie und hast in den letzten 15 Jahren unter einem Stein gelebt hat, oder ich weiß auch nicht...

(BTW: Asynchron ist nicht das gleiche wie reactive Streams...)


----------



## Conax (3. Dez 2018)

Also ich hab das bis jetzt so gelöst. Inwiefern unterscheidet sich jetzt das von diesen "Streams"?

https://github.com/ProjectXcode/CryptoFun/blob/master/Coinmonitor.java


----------



## mrBrown (3. Dez 2018)

Conax hat gesagt.:


> Also ich hab das bis jetzt so gelöst. Inwiefern unterscheidet sich jetzt das von diesen "Streams"?


Die einfachere Frage ist, wo liegen die Gemeinsamkeiten: beides ist in Java geschrieben.


Deins setzt so ziemlich alle Anti-Pattern um, die man da einbauen könnte...


----------

