1. Foren
  2. Kommentare
  3. Software-Entwicklung
  4. Alle Kommentare zum Artikel
  5. › Mozillas…

Danke!

  1. Thema

Neues Thema Ansicht wechseln


  1. Danke!

    Autor: Linuxschaden 16.07.15 - 14:37

    Das erste Beispiel hat mir bereits gezeigt, dass man von dieser Programmiersprache besser einen grooooooooooßen Bogen macht. Denn wenn eine Funktion die "Erlaubnis" verliert, auf ein Element IHRES EIGENEN STACKS (!!!) zuzugreifen, dann ist da doch was kaputt, was man nicht mehr in Ziffern ausdrücken kann.

    Übrigens: ich kann nicht mehr Speicher dynamisch und manuell freigeben und reservieren? Noch ein Argument GEGEN die Sprache.
    Ich nehme gerade etliche Schmerzen auf mich, meinen HTTP-Stack weitestgehend lockless zu bekommen (keine malloc/free-Calls, heißt das). Das kann klappen, man muss aber mit Speicher auf dem Heap und unsyncronisiertem Zugriff daran gehen. In C geht das, sogar ziemlich gut, wenn man ein ordentliches API baut. In Rust scheint das nicht der Fall zu sein => Tonne.
    Selbst C++ ist nicht soweit gesunken.



    1 mal bearbeitet, zuletzt am 16.07.15 14:42 durch Linuxschaden.

  2. Re: Danke!

    Autor: Schnarchnase 16.07.15 - 15:31

    Linuxschaden schrieb:
    --------------------------------------------------------------------------------
    > Denn wenn eine Funktion die "Erlaubnis" verliert, auf ein Element IHRES
    > EIGENEN STACKS (!!!) zuzugreifen, dann ist da doch was kaputt, was man
    > nicht mehr in Ziffern ausdrücken kann.

    Du kannst sie auch Ausleihen, dann kann sowohl die eigentliche Funktion als auch die von dort aufgerufene die Daten manipulieren. Daran ist auch nichts kaputt, das passt sehr gut zu Rusts grundlegendem Konzept. Standardmäßig kannst du nämlich gar nichts manipulieren, das musst du erst mit „mut“ explizit als änderbar deklarieren.

    > Übrigens: ich kann nicht mehr Speicher dynamisch und manuell freigeben und
    > reservieren?

    Du hast sehr viele Möglichkeiten der Speicherverwaltung in Rust.

    Hier gibt es einen groben Überblick:
    [pcwalton.github.io]

    Hier etwas mehr zu den Möglichkeiten in Rust:
    [pcwalton.github.io]

  3. Re: Danke!

    Autor: Linuxschaden 16.07.15 - 15:46

    @Schnarchnase:

    Ich lese mich also so ein bisschen in den gelinkten Artikel ein, und stolpere dann über das hier:

    > It’s also easy to write a function that acts exactly as free does, so you can precisely choose
    > when your objects die. Unique pointers are not traced by the GC (unless they point to a type
    > that transitively contains a garbage-collected pointer), so they are an effective way to cut
    > down marking times.

    Das ist schön, dass ich das kann - aber warum kommen die nicht mit einer Beispielfunktion in der Laufzeitbibliothek an?

    Außerdem erinnert mich die Art und Weise, wie mit dem Speicher umgegangen wird, ein bisschen an .NET. Man hat sicheren und unsicheren Code - in meinen Augen ein wenig hässlich. Kein direktes Argument gegen die Sprache - wenn es funktioniert, bitte ...

    Aber trotzdem ... der Ansatz in C ist ein wenig schöner. Ich habe weniger das Gefühl, ich müsste mich verbiegen, um das gleiche zu erreichen.

    EDIT:
    Das gleiche Problem habe ich im Grunde mit C++. Man hat mir damals versucht, C++ mit dem Argument zu verkaufen, dass ich damit die gleichen Sachen wie in C machen kann, nur halt noch mehr. Und als ich dann versucht habe, die gleichen Sachen zu machen, kamen die gleichen Leuten um die Ecke und haben mir gesagt, dass man X in C++ so nicht macht, und hier und bla - und wir reden nicht von trivialen Sachen wie printf/cout, wir reden von Kopierkonstruktoren, By-Value-Parameterübergaben, die nicht By-Reference sein sollten, die mich gezwungen haben, eben diesen Kopierkonstruktor und den Destruktor zu überladen und jede Menge (aus meiner Sicht) unnützen Code dabeizutun.

    Es bringt mir nichts, die gleichen Sachen in Programmiersprache X machen zu können wie in A, wenn die Best Practices was vollkommen anderes verlangen.



    1 mal bearbeitet, zuletzt am 16.07.15 15:52 durch Linuxschaden.

  4. Re: Danke!

    Autor: Schnarchnase 16.07.15 - 16:06

    Linuxschaden schrieb:
    --------------------------------------------------------------------------------
    > Es bringt mir nichts, die gleichen Sachen in Programmiersprache X machen zu
    > können wie in A, wenn die Best Practices was vollkommen anderes verlangen.

    Richtig erkannt. Rust ist auch nicht als Ablösung für C oder C++ angetreten.

    In der Regel muss man den Speicher nicht manuell verwalten, das war mir bei C eher lästig, daher kommt mir Rust an der Stelle entgegen. Wenn du die Kontrolle brauchst, halte ich es für Unsinn die Software in Rust zu schreiben, nimm einfach C und gut ists.

  5. Re: Danke!

    Autor: esgeh 16.07.15 - 16:19

    Linuxschaden schrieb:
    --------------------------------------------------------------------------------
    > Denn wenn eine
    > Funktion die "Erlaubnis" verliert, auf ein Element IHRES EIGENEN STACKS
    > (!!!) zuzugreifen, dann ist da doch was kaputt, was man nicht mehr in
    > Ziffern ausdrücken kann.

    Natürlich! Wenn da kein valides Objekt mehr im Speicher steht, sollte man damit auch nichts mehr anstellen können -- es sei den, Du weist dieser Speicherstelle einen neuen Wert zu. Ich kann jetzt deinen Gedankengang ehrlich gesagt nicht ganz nachvollziehen. Wie kommst du darauf, dass es etwas schlechtes sei, wenn der Compiler Dir auf die Finger haut anstatt Dich mit fehlerhaftem Code zur Laufzeit Segfaults provozieren zu lassen?

    > Übrigens: ich kann nicht mehr Speicher dynamisch und manuell freigeben und
    > reservieren?

    Doch. Das geht. Das braucht man schon, um Dinge wie std::Vec implementieren zu können. Vielleicht solltest Du Dir Rust doch nochmal angucken und es nicht voreilig verwerfen.

    > Ich nehme gerade etliche Schmerzen auf mich, meinen HTTP-Stack
    > weitestgehend lockless zu bekommen (keine malloc/free-Calls, heißt das).
    > Das kann klappen, man muss aber mit Speicher auf dem Heap und
    > unsyncronisiertem Zugriff daran gehen.

    Ich hoffe, Du hast irgendeine andere Definition von "unsynchronisiert" als ich. Wenn Du Atomics doch als Form der Synchronisierung dazu zählst und das trotzdem "unsynchronisiert" machst, dann wünsche ich Dir viel Spaß mit dem undefinierten Verhalten.

  6. Re: Danke!

    Autor: twothe 16.07.15 - 16:19

    Das war auch mein erster Gedanke, aber dann hab ich noch mal darüber nachgedacht, und festgestellt, dass sie Rust-Lösung eigentlich gut ist. Nicht weil sie der Funktion verbietet ihre eigene Variable zu benutzen, sondern weil sie dem Programmierer sagt, dass er hier Mist programmiert hat.

  7. Re: Danke!

    Autor: esgeh 16.07.15 - 17:22

    @Linuxschaden:

    Das, was Du dir da jetzt vorhin durchgelesen hast ist ein bisschen out-of-date. Da ist noch die von tracing GCs und co ... Das gibt's alles gar nicht in aktuellem Rust.

    Linuxschaden schrieb:
    > Außerdem erinnert mich die Art und Weise, wie mit dem Speicher umgegangen
    > wird, ein bisschen an .NET. Man hat sicheren und unsicheren Code - in
    > meinen Augen ein wenig hässlich. Kein direktes Argument gegen die Sprache -
    > wenn es funktioniert, bitte ...

    Ich kenne C# nicht. Aber `unsafe` in Rust ist OK. Man braucht das eben, wenn man nicht doch noch Dinge nach C auslagern will. Der Trick ist es, `unsafe` möglichst wenig zu benutzen. Damit man darauf selten angewiesen ist, bietet die Standardbibliothek eben viele nette Abstraktionen an, die intern "unsafe" sind, aber nach außen eine nette und vor allem sichere Schnittstelle anbieten, die man bzgl Speichersicherheit nicht falsch benutzen kann.

    > EDIT:
    > Das gleiche Problem habe ich im Grunde mit C++. Man hat mir damals
    > versucht, C++ mit dem Argument zu verkaufen, dass ich damit die gleichen
    > Sachen wie in C machen kann, nur halt noch mehr. Und als ich dann versucht
    > habe, die gleichen Sachen zu machen, kamen die gleichen Leuten um die Ecke
    > und haben mir gesagt, dass man X in C++ so nicht macht, und hier und bla -
    > und wir reden nicht von trivialen Sachen wie printf/cout, wir reden von
    > Kopierkonstruktoren, By-Value-Parameterübergaben, die nicht By-Reference
    > sein sollten, die mich gezwungen haben, eben diesen Kopierkonstruktor und
    > den Destruktor zu überladen und jede Menge (aus meiner Sicht) unnützen Code
    > dabeizutun.
    >
    > Es bringt mir nichts, die gleichen Sachen in Programmiersprache X machen zu
    > können wie in A, wenn die Best Practices was vollkommen anderes verlangen.

    Die gleichen Sachen in Programmiersprache X machen zu können wie in A heißt erstmal nur, dass X nicht weniger mächtig als A ist. Die Spielwiese ist nur größer. Und auf der größeren Wiese gibt es noch andere Dinge, die auch sehr gut sind und als Ersatz dessen funktionieren, was du von A schon kennst. Das ist natürlich mit einem Lernprozess verbunden. Und der "bringt" schon was, IMHO. Du kannst Rust jetzt auch wie C benutzen, indem du überall unsafe Code benutzt und z.B. das malloc/free der libc direkt aufrufst. Aber das wäre eine echt blöde Idee und keine effiziente Nutzung der Dir zur Verfügung stehenden Sprachmittel.

  8. Re: Danke!

    Autor: Linuxschaden 16.07.15 - 18:20

    esgeh schrieb:
    --------------------------------------------------------------------------------
    > Linuxschaden schrieb:
    > ---------------------------------------------------------------------------
    > -----
    > > Denn wenn eine
    > > Funktion die "Erlaubnis" verliert, auf ein Element IHRES EIGENEN STACKS
    > > (!!!) zuzugreifen, dann ist da doch was kaputt, was man nicht mehr in
    > > Ziffern ausdrücken kann.
    >
    > Natürlich! Wenn da kein valides Objekt mehr im Speicher steht, sollte man
    > damit auch nichts mehr anstellen können -- es sei den, Du weist dieser
    > Speicherstelle einen neuen Wert zu. Ich kann jetzt deinen Gedankengang
    > ehrlich gesagt nicht ganz nachvollziehen. Wie kommst du darauf, dass es
    > etwas schlechtes sei, wenn der Compiler Dir auf die Finger haut anstatt
    > Dich mit fehlerhaftem Code zur Laufzeit Segfaults provozieren zu lassen?

    ... wir reden hier vom Stack. VOM STACK. Nix Heap, nix dynamischer Speicherverwaltung. Soweit ich das sehe, liegt das Objekt auf'm Stack. Und das liegt da immer noch, wenn die zweite Funktion aufgerufen wird und abgearbeitet hat und deren Stackframe abgebaut ist - auch dann noch haben wir das Objekt auf'm Stack. Und der Compiler verbietet mir jetzt, auf das Objekt, welches auf'm Stack liegt, zuzugreifen. Weil, äh, das schon abgebaut wurde - wo doch der Stack bei Funktionsaustritt der Funktion, die den jeweiligen Frame aufgebaut hat, wieder abgebaut wird (wobei nicht feststeht, von wem, das besagt die Calling-Convention).

    Wie kann ich da Segfaults produzieren? O_o

    > Doch. Das geht. Das braucht man schon, um Dinge wie std::Vec implementieren
    > zu können. Vielleicht solltest Du Dir Rust doch nochmal angucken und es
    > nicht voreilig verwerfen.

    Habe ich bereits erklärt bekommen, aber anscheinend muss man sich wieder was eigenes zusammenbasteln, weil das nicht mitgeliefert wird. Nämlich ein eigenes free. Ja, gut, kann man machen, habe ich in C für den lockless Stack gemacht. Aber C ist da wenigstens ehrlich und sagt an, wie scheiße das ist. In Rust sagt man "Kann man machen, aber dann gibt es X und Y und Z, mach's doch so. Das ist zwar langsamer, aber garantiert sicherer!"

    Ich hätte lieber gerne beides.

    > Ich hoffe, Du hast irgendeine andere Definition von "unsynchronisiert" als
    > ich. Wenn Du Atomics doch als Form der Synchronisierung dazu zählst und das
    > trotzdem "unsynchronisiert" machst, dann wünsche ich Dir viel Spaß mit dem
    > undefinierten Verhalten.

    Hinter malloc/free steht immer ein Kernel-Call, sei es, um Speicher vom OS anzufordern, oder um sicherzustellen, dass mehrere Threads den internen Status von malloc/free nicht durcheinanderbringen. Die eigentliche Programmlogik ist ja bei vernünftigen Implementierungen im Userspace - aber diese beiden Kernel-Calls hast du. Und hier bin ich gerade dabei, diese zu eliminieren.

    (Stimmt übrigens nicht ganz, dass IMMER eine Synchronisierung nötig ist. Vor einigen Jahren gab es ein Paper darüber, wie sie einen lockless allocator gebaut haben, aber das habe ich gerade verlegt - ich weiß daher nicht, ob das in gängigen Implementierungen bereits drin ist und wie gut das performt).

    esgeh schrieb:
    --------------------------------------------------------------------------------
    > @Linuxschaden:
    > Ich kenne C# nicht. Aber `unsafe` in Rust ist OK. Man braucht das eben,
    > wenn man nicht doch noch Dinge nach C auslagern will. Der Trick ist es,
    > `unsafe` möglichst wenig zu benutzen. Damit man darauf selten angewiesen
    > ist, bietet die Standardbibliothek eben viele nette Abstraktionen an, die
    > intern "unsafe" sind, aber nach außen eine nette und vor allem sichere
    > Schnittstelle anbieten, die man bzgl Speichersicherheit nicht falsch
    > benutzen kann.

    OK, das hört sich schon besser an. :)

    > Die gleichen Sachen in Programmiersprache X machen zu können wie in A heißt
    > erstmal nur, dass X nicht weniger mächtig als A ist. Die Spielwiese ist nur
    > größer. Und auf der größeren Wiese gibt es noch andere Dinge, die auch sehr
    > gut sind und als Ersatz dessen funktionieren, was du von A schon kennst.
    > Das ist natürlich mit einem Lernprozess verbunden. Und der "bringt" schon
    > was, IMHO. Du kannst Rust jetzt auch wie C benutzen, indem du überall
    > unsafe Code benutzt und z.B. das malloc/free der libc direkt aufrufst. Aber
    > das wäre eine echt blöde Idee und keine effiziente Nutzung der Dir zur
    > Verfügung stehenden Sprachmittel.

    Lass mich mit einem Beispiel antworten:

    Es gibt in C++ (seit neuestem, glaub ich, habe mich nicht hauptsächlich mit der Sprache beschäftigt) Futures, was. soweit ich das verstanden habe, einfacher zu verwendende Threads sind, aber bereits in der Sprache integriert, nicht als externe Bibliothek. Das Problem ist, dass Futures langsamer sind als native Threads/selbstgeschriebene leichtgewichtige Abstraktion/OpenMP ... such dir was aus. Die sind alle langsamer.

    Aber die Leute werden wieder auf "unschöne" Konstrukte wie manuelles Threading oder OpenMP wechseln, weil das halt schneller ist als die native Implementierung.

    Um dir nun direkt zu antworten: Wenn ich gezwungen bin, außerhalb von der Sprache definierte Funktionen zu verwenden, damit es ordentlich läuft, dann ist das blöd. Der Ansatz, ordentliche Rust-Wrapper zu unsicheren C-Code anzubieten, ist zwar gut gemeint, dem will ich gar nicht mal entgegentreten. Aber dann kann ich eigentlich auch direkt in C programmieren. Wenn ich gezwungen bin, OpenMP oder pthreads oder was auch immer zu verwenden, weil die native Threadimplementierung zu langsam ist (aber ist halt nativ, Best Practice), dann kann ich das auch direkt in C machen. Oder zumindest den Kern in C implementieren.

    Wie gesagt - ich hätt gern beides.

  9. Re: Danke!

    Autor: esgeh 17.07.15 - 00:56

    Linuxschaden schrieb:
    > esgeh schrieb:
    > > Linuxschaden schrieb:
    > > > Denn wenn eine
    > > > Funktion die "Erlaubnis" verliert, auf ein Element IHRES EIGENEN
    > > > STACKS
    > > > (!!!) zuzugreifen, dann ist da doch was kaputt, was man nicht mehr in
    > > > Ziffern ausdrücken kann.
    > >
    > > Natürlich! Wenn da kein valides Objekt mehr im Speicher steht, sollte
    > > man
    > > damit auch nichts mehr anstellen können -- es sei den, Du weist dieser
    > > Speicherstelle einen neuen Wert zu. Ich kann jetzt deinen Gedankengang
    > > ehrlich gesagt nicht ganz nachvollziehen. Wie kommst du darauf, dass es
    > > etwas schlechtes sei, wenn der Compiler Dir auf die Finger haut anstatt
    > > Dich mit fehlerhaftem Code zur Laufzeit Segfaults provozieren zu lassen?
    >
    > ... wir reden hier vom Stack. VOM STACK. Nix Heap, nix dynamischer
    > Speicherverwaltung. Soweit ich das sehe, liegt das Objekt auf'm Stack.

    Das Objekt lag auf dem Stack. Es ist jetzt weg. Der Byte-Haufen im Stack entspricht nach der Besitzübergabe an die Funktion keinem gültigen Vec-Objekt mehr. Wenn Du den Speicherinhalt immer noch als gültigen Vec interpretieren würdest, käme es mindestens zu einem double-free (weil dessen Elemente ja schon in der aufgerufenen Funktion freigegeben wurden) und ggf. auch zu einem use-after-free Fehler, wenn Du versuchst, auf die Elemente, die es ja nicht mehr gibt, noch zuzugreifen. Das hat schon alles so seine Richtigkeit. Im Vergleich zu C++ ist die Move-Semantik in Rust "richtig destruktiv" in dem Sinne, dass dort, wo das Quellobjekt im Speicher stand, nichts gültiges mehr ist. Das erlaubt eine Flache kopie der Bits als Move. Der Vec, der sich für die Elemente verantwortlich fühlt, lebt im Beispiel dann für die Zeit der Ausführung der aufgerufenen Funktion in dessen Stackframe und ist danach (inklusive seiner Elemente) auch weg. In C++ dagegen ist nach einem "Move" das Quellobjekt noch "gültig", so dass der Destruktor noch drauf laufen kann/wird. Beim Vektor-Beispiel wäre der Quellvektor in C++ nach einer Movekonstruktion einfach leer (size=capacity=0) aber immer noch gültig.

    Dieses Vektor-Beispiel wird, wenn ich mich richtig erinnere, schön in folgendem Vortrag visualisiert:
    https://air.mozilla.org/guaranteeing-memory-safety-in-rust/
    (Nette Bildchen vom Speicherlayout von Stack und Heap)

    > [...]
    > (Stimmt übrigens nicht ganz, dass IMMER eine Synchronisierung nötig ist.
    > Vor einigen Jahren gab es ein Paper darüber, wie sie einen lockless
    > allocator gebaut haben, aber das habe ich gerade verlegt - ich weiß daher
    > nicht, ob das in gängigen Implementierungen bereits drin ist und wie gut
    > das performt).

    Ich habe immer noch den Eindruck, dass Du das Wort "Synchronisierung" anders benutzt als ich. Für mich ist z.B. ein atomares fetch_and_add oder ein compare_and_swap auch schon eine Art von Synchronisierung. Diese Operationen sind trotzdem lockfree. Unsynchronisiert heißt für mich potentielle Datenrennen, wenn mehrere Threads involviert sind. Datenrennen möchtest Du ja nicht haben.

    > Es gibt in C++ (seit neuestem, glaub ich, habe mich nicht hauptsächlich mit
    > der Sprache beschäftigt) Futures, was. soweit ich das verstanden habe,
    > einfacher zu verwendende Threads sind, aber bereits in der Sprache
    > integriert, nicht als externe Bibliothek. Das Problem ist, dass Futures
    > langsamer sind als native Threads/selbstgeschriebene leichtgewichtige
    > Abstraktion/OpenMP ... such dir was aus. Die sind alle langsamer.

    Egal wie du es machst, wenn du das Betriebssystem nach einem neuen Thread fragst, kostet das richtig viel Overhead. Deswegen gibt's ja so Sachen wie Threadpools. Nur für schnell durchzuführende Operationen einen neuen Thread starten und sterben lassen ist wegen des Overheads eher eine Verschwendung. Daran kannst du gar nichts ändern. Das ist bei pthreads auch so. Die nächst höhere (aber immer noch sehr low-levelige) Schnittstelle, die Dir die C++11 Standardbibliothek dafür bietet, ist std::thread. Das ist im Prinzip nur ein sehr dünner Layer, der vor allem Typsicherheit bringt. Damit wird man nämlich die ganzen void* Zeiger los. Mehr passiert da nicht. Und ja, dann gibt's auch noch sowas wie Futures, wenn man die mag. So richtig ausgegoren ist das alles noch nicht. Wie Du allerdings auf "Die sind alle langsamer" kommst, weiß ich jetzt nicht.

    > Aber die Leute werden wieder auf "unschöne" Konstrukte wie manuelles
    > Threading oder OpenMP wechseln, weil das halt schneller ist als die native
    > Implementierung.

    OpenMP ist für "fork-join" prima. Da werden bestimmt hintenrum auch Threadpools automatisch angelegt.

    > Um dir nun direkt zu antworten: Wenn ich gezwungen bin, außerhalb von der
    > Sprache definierte Funktionen zu verwenden, damit es ordentlich läuft, dann
    > ist das blöd. Der Ansatz, ordentliche Rust-Wrapper zu unsicheren C-Code
    > anzubieten, ist zwar gut gemeint, dem will ich gar nicht mal
    > entgegentreten.

    Was für C-Code denn? Du bist in Rust nicht auf C angewiesen.

    > [...] Wenn ich gezwungen bin, OpenMP oder pthreads oder was auch
    > immer zu verwenden, weil die native Threadimplementierung zu langsam ist

    Du kannst dich in Rust austoben wie du willst. Du kannst dir auch einen eigenen Threadpool schreiben, wenn dir die Threadpools nicht gefallen, die andere schon gebastelt haben. Der Threading-Kram in der Rust StdLib ist wie in C++ auch wieder nur ein dünner Layer über pthreads (für *nix) und CreateThread (für Win), der in erster Linie Typsicherheit bietet ... wobei Typsicherheit hier in Rust noch mit einschließt, dass Du keine Datenrennen provozieren kannst, wenn du im safe subset bleibst.

  10. Re: Danke!

    Autor: Linuxschaden 17.07.15 - 09:30

    esgeh schrieb:
    --------------------------------------------------------------------------------
    > Das Objekt lag auf dem Stack. Es ist jetzt weg. Der Byte-Haufen im Stack
    > entspricht nach der Besitzübergabe an die Funktion keinem gültigen
    > Vec-Objekt mehr. Wenn Du den Speicherinhalt immer noch als gültigen Vec
    > interpretieren würdest, käme es mindestens zu einem double-free (weil
    > dessen Elemente ja schon in der aufgerufenen Funktion freigegeben wurden)
    > und ggf. auch zu einem use-after-free Fehler, wenn Du versuchst, auf die
    > Elemente, die es ja nicht mehr gibt, noch zuzugreifen.

    Kurz: Objekt liegt auf dem Stack, enthält aber Referenzen auf dem Heap, welche, nachdem die Funktion zurückgekehrt ist, freigegeben wurden durch den "Destruktor" des Objekts, und da keine tiefe Kopie des Objekts angelegt wurde, sind die Werte flach und die Adressen sind freigegeben worden.
    OK, über den Sinn lässt sich zwar streiten, aber Rust soll ja sicher sein, nicht schnell.

    > Ich habe immer noch den Eindruck, dass Du das Wort "Synchronisierung"
    > anders benutzt als ich. Für mich ist z.B. ein atomares fetch_and_add oder
    > ein compare_and_swap auch schon eine Art von Synchronisierung. Diese
    > Operationen sind trotzdem lockfree. Unsynchronisiert heißt für mich
    > potentielle Datenrennen, wenn mehrere Threads involviert sind. Datenrennen
    > möchtest Du ja nicht haben.

    Und mehrere Threads habe ich laufen. Die bearbeiten alle ihr Stückchen Daten, das von den Sockets gelesen wurden. Solange die alle auf ihnen zugewiesenen Speicher zugreifen, ist alles in Ordnung.
    Tun sie auch meistens. Aber manchmal (bei besonders großen Dateien, oder gerade bei Chunked-Transfer, wo man gleichzeitig die Originaldaten behalten, aber dennoch eine Kopie ohne chunk header erstellen muss), muss man öfter mehr Speicher reservieren, als der Buffer des Threads gerade zur Verfügung hat. Mein Ansatz ist, dass reservierter Speicher nicht freigegeben wird, sondern innerhalb mehrerer Funktionen zur Verfügung steht (auch für den degzipper/engezipper später),

    Dadurch hat man immer noch vereinzelt malloc/free-Calls, welches die Synchronisierung (nicht auf atomarer Ebene, davon rede ich nicht und brauche ich auch nicht, weil der eigentliche Zugriff auf den Speicher in der Bearbeitung keine Synchronisierung benötigt - es geht hier nur malloc/free/allgemeine Mutexe) erfordern. Ganz eliminiert kriege ich die nicht. Aber im Vergleich zu C++ oder Perl-Implementierungen sind die Anforderungen dynamischen Speichers, dessen Freigabe, und die erzwungene Synchronisierung eines Threads ganz erheblich reduziert worden. Eine Aufgabe, die in Perl bsp. 6 Sekunden dauert, benötigt nur 1 Sekunde in C mit diesem Code. Und das, wo der Bulk des Aufwands eigentlich im TCP-Stack liegen sollte.

    Wofür man sowas braucht? Parallele Threads zum Webcrawling und hochskalierende HTTP-Server.

    > Egal wie du es machst, wenn du das Betriebssystem nach einem neuen Thread
    > fragst, kostet das richtig viel Overhead.

    Zwei Mikrosekunden auf Linux mit pthreads und einem iCore 7. Forken ist dagegen RICHTIG teuer (48 - 192 Mikrosekunden). Dagegen sind Threads superschnell.

    > Deswegen gibt's ja so Sachen wie Threadpools. Nur für schnell durchzuführende
    > Operationen einen neuen Thread starten und sterben lassen ist wegen des Overheads
    > eher eine Verschwendung.

    Davon reden wir ja nicht. Wir reden von Operationen, bei denen eine parallele Bearbeitung einen spürbaren Einfluss auf die Laufzeit hat. Und darauf bezog sich mein Beispiel - Futures stinken hier ab, OpenMP/pthreads sind um einiges schneller.

    Es ging ja auch nicht daraum, WAS jetzt schneller ist - sondern dass es keinen Sinn macht, einen "guten" Weg (safe code/Futures) anzubieten, wenn der schlechte Weg (unsafe/OpenMP/pthreads) mehr Vorteile (schneller, weniger Speicherverbauch) bietet.
    Ja, ich weiß, Rust legt mehr Wert auf Sicherheit, die kümmert nicht wirklich, ob das schnell ist. Aber mich kümmert das halt, weil jeder Cycle, den das Programm unnötigt läuft, ein Argument gegen das Programm ist. Das ist zumindest meine Denkweise.

    > Was für C-Code denn? Du bist in Rust nicht auf C angewiesen.

    Wenn es ordentlich laufen soll, habe ich zumindest so verstanden, dann doch, bin ich.

  11. Re: Danke!

    Autor: esgeh 17.07.15 - 10:31

    Linuxschaden schrieb:
    > Kurz: Objekt liegt auf dem Stack, enthält aber Referenzen auf dem Heap,
    > welche, nachdem die Funktion zurückgekehrt ist, freigegeben wurden durch
    > den "Destruktor" des Objekts, und da keine tiefe Kopie des Objekts angelegt
    > wurde, sind die Werte flach und die Adressen sind freigegeben worden.
    > OK, über den Sinn lässt sich zwar streiten, aber Rust soll ja sicher sein,
    > nicht schnell.

    Ok, streiten wir. Du siehst hier also irgendwo Performance-Einbußen. Warum? Diese Besitzübergabe kann genau das sein, was Du haben willst. Die aufgerufene Funktion könnte den Vec weiter an einen Thread übergeben. Da braucht nichts zu synchronisiert werden und man muss sich auch nicht sorgen, dass diese Elemente irgendwann vorzeitig veschwinden, weil der Thread dann der alleinige Besitzer wäre und damit automatisch für die Löschung zuständig ist.

    Du willst keine Besitzübergabe? Ok, dann *verleih* doch den Vec an die aufgerufene Funktion. Auf Maschinenebene wird dann nichts anderes als die Adresse des Vecs als Parameter an die Funktion übergeben. Die kann das Ding dann nicht zerstören und je nachdem, was für eine Referenz das war, ggf nicht mal ändern.

    Soll die Funktion ihren eigenen Vec bekommen und der deinige noch bestehen, klonst Du den Vec explizit. Das ist das, was in C++ implizit passieren würde. IMHO ist das eine gute Sache, dass potentiell *teure* Operationen wie .clone() in Rust explizit aufgerufen werden müssen, wohingegen man in C++ bei der Benutzung von std::string zum Beispiel ständig pass-by-value benutzen könnte, ohne damit zu rechnen, dass das Kopieren von std::string-Werten sehr teuer sein kann. Das ist nämlich die Sorte von "Fehlern", die Leute, denen diese versteckten Kosten nicht bewusst sind, dazu veranlassen zu sagen "C++ ist langsamer als C", obwohl sie es auch anders hätten implementieren können. Kosten sind in Rust eher explizit. Da schlägt das Herz des C Programmierers doch höher, nicht?

    > > Egal wie du es machst, wenn du das Betriebssystem nach einem neuen
    > > Thread fragst, kostet das richtig viel Overhead.
    >
    > Zwei Mikrosekunden auf Linux mit pthreads und einem iCore 7. Forken ist
    > dagegen RICHTIG teuer (48 - 192 Mikrosekunden). Dagegen sind Threads
    > superschnell.

    Joa, das heißt, das, was der Thread zu tun bekommt, sollte einiges länger als 2 Mikrosekunden dauern.

    > [...]
    > Davon reden wir ja nicht. Wir reden von Operationen, bei denen eine
    > parallele Bearbeitung einen spürbaren Einfluss auf die Laufzeit hat. Und
    > darauf bezog sich mein Beispiel - Futures stinken hier ab, OpenMP/pthreads
    > sind um einiges schneller.

    Hast Du hier auch Zahlen? Sind es dann 2,1 Mikrosekunden statt 2 Mikrosekunden bei pthreads?

    Bzgl OpenMP: Ich habe keine Ahnung, was die machen, nehme aber an, dass da Threadpools im Spiel sind, damit man auch die 2 Mikrosekunden für ein fork reduzieren kann. Ich rede hier übrigens nicht von Linux' fork sondern von dem fork in "fork-join parallelism". Das, was dir OpenMP bietet.

    Bzwl pthreads versus C++ std::thread API oder Rust std::thread API glaube ich Dir nicht. Die Zahlen muss ich mit eigenen Augen sehen, weil ich keinen Grund dafür sehe, warum es da überhaupt signifikante Unterschiede geben sollte. Wie gesagt, es sind sehr dünne Wrapper um pthreads, die für nichts anderes als Type Checking da sind.

    > Ja, ich weiß, Rust legt mehr Wert auf Sicherheit, die kümmert nicht
    > wirklich, ob das schnell ist.

    Doch doch. Lebenszweck von Rust ist es Sicherheit ohne (bzw mit minimalem) Laufzeit-Overhead zu bieten. Für Dich, als jemand der Software schreibt, die mit Netzwerken in Kontakt kommt und sich dementsprechend sorgen um Sicherheitslücken machen muss, wäre die Sprache eigentlich ein interessanter Ansatz. Die Frage ist da nur, ob du zu den Early Adoptern gehören willst oder dann später nachziehst. ;-)

    (Nee, im Ernst, ich gehe natürlich nicht davon aus, dass Rust C oder C++ aussterben lässt. Aber es wird bestimmt so einige der C und C++ Entwickler überzeugen, die gewillt sind, sich auch mal außerhalb ihrer Bubble aufzuhalten, um zu gucken, was es noch so gibt und wie das funktioniert. Bei mir hat das ja mit meinem C++ Background auch geklappt.)

    > Aber mich kümmert das halt, weil jeder Cycle,
    > den das Programm unnötigt läuft, ein Argument gegen das Programm ist. Das
    > ist zumindest meine Denkweise.

    Mir sind Zyklen auch wichtig (High Performance Computing, Numerik). Ich glaube, Du überschätzt die Laufzeitkosten bzgl C++ und Rust gewaltig. Vielleicht hast Du irgendwo bei deinen Benchmarks einfach was verkackt. Rust ist allerdings noch relativ jung. Das heißt, aktuelle Implementierungen sind wahrscheinlich noch optimierungsbedürftig. Was mich zu diesem Zeitpunkt da mehr interessiert ist das Potential der Sprache. Und was das angeht, sehe ich keine technischen Hürden, die das Design der Sprache zu verantworten hat. Rust ist einfach wie C, nur dass der Compiler viel mehr prüfen kann, weil das Typsystem eben auch Dinge wie Besitzverhältnisse, Borrowing, ob Werte von bestimmten Typen über Thread-Grenzen hinweg geteilt und/oder versendet werden dürfen, darstellen kann. Die Abstriche bzgl Performance wegen der "Sicherheit" sind minimal. Und da, wo du es dann verantworten kannst, kannst du im Notfall auch wieder unsafe Code einbauen. Es hilft ja schon, wenn man nur 1% des Codes genauer unter die Lupe nehmen muss, weil man nur dort richtig was bzgl Sicherheit verkacken könnte.

    > > Was für C-Code denn? Du bist in Rust nicht auf C angewiesen.
    >
    > Wenn es ordentlich laufen soll, habe ich zumindest so verstanden, dann
    > doch[...]

    Nö.

  12. Re: Danke!

    Autor: Linuxschaden 17.07.15 - 11:30

    esgeh schrieb:
    --------------------------------------------------------------------------------
    > Ok, streiten wir. Du siehst hier also irgendwo Performance-Einbußen. Warum?

    Weil wir wieder Objekte auf dem Heap verwalten.
    Wenn ich einen Vektor verwalten will, will ich den in der Nähe von Variablen halten, die ich oft verwende (des CPU caches wegen). Und das ist der Stack nun mal die erste Anlaufstelle - und dabei jetzt mal den ganzen Overhead durch dynamische Speicherverwaltung (Funktionsaufruf, Locken, Listen durchgehen, Sanity Checks, eventuell Speicher vom OS anfordern, neuen Eintrag hinzufügen, Lock freigeben - und das Freigeben war das noch nicht mal drin).

    > Diese Besitzübergabe kann genau das sein, was Du haben willst. Die
    > aufgerufene Funktion könnte den Vec weiter an einen Thread übergeben.

    Jau. Aber auch dann würde ich den Vektor komplett auf dem Stack halten, und halt nur immer die Adresse übergeben.

    > Soll die Funktion ihren eigenen Vec bekommen und der deinige noch bestehen,
    > klonst Du den Vec explizit. Das ist das, was in C++ implizit passieren
    > würde. IMHO ist das eine gute Sache, dass potentiell *teure* Operationen
    > wie .clone() in Rust explizit aufgerufen werden müssen, wohingegen man in
    > C++ bei der Benutzung von std::string zum Beispiel ständig pass-by-value
    > benutzen könnte, ohne damit zu rechnen, dass das Kopieren von
    > std::string-Werten sehr teuer sein kann.

    Gebe ich dir vollkommen recht mit - und das ist auch einer der Grunde, warum ich C++ in der Hinsicht nicht leiden kann. In Rust muss man das Kopieren also explizit erlauben ... das ist in der Tat nicht unintelligent.

    > Das ist nämlich die Sorte von
    > "Fehlern", die Leute, denen diese versteckten Kosten nicht bewusst sind,
    > dazu veranlassen zu sagen "C++ ist langsamer als C", obwohl sie es auch
    > anders hätten implementieren können. Kosten sind in Rust eher explizit. Da
    > schlägt das Herz des C Programmierers doch höher, nicht?

    Yup. :p

    > Joa, das heißt, das, was der Thread zu tun bekommt, sollte einiges länger
    > als 2 Mikrosekunden dauern.

    Entweder das, oder das System muss sofort wieder antworten und die zu erledigende Aufgabe an den Thread weiterleiten. Und da sind wir wieder bei den hochskalierenden Servern. :)

    > Hast Du hier auch Zahlen? Sind es dann 2,1 Mikrosekunden statt 2
    > Mikrosekunden bei pthreads?

    Ich habe den Test damals mit gettimeofday gemacht, bevor ich auf Cyclemessungen umgestiegen bin. Ich lasse grad die Festplatte nach dem Programm durchsuchen, aber notfalls muss ich das noch mal hacken.

    > Bzwl pthreads versus C++ std::thread API oder Rust std::thread API glaube
    > ich Dir nicht. Die Zahlen muss ich mit eigenen Augen sehen, weil ich keinen
    > Grund dafür sehe, warum es da überhaupt signifikante Unterschiede geben
    > sollte. Wie gesagt, es sind sehr dünne Wrapper um pthreads, die für nichts
    > anderes als Type Checking da sind.

    Futures werden normalerweise statt async deferred ausgeführt (http://en.cppreference.com/w/cpp/thread/launch). Sprich, die machen das dann lieber synchron.
    Man kann dem Compiler (zumindest unter dem g++, meine ich) sagen, dass man echte Threads haben will, aber das kostet dann plötzlich - soweit ich mich erinnere, steigt die Laufzeit auf 150%. Wo man mit OpenMP auf 50% der deferred Ausführung kommt. Die Zeiten waren damals alle im Sekundenbereich, also nicht wirklich trivial.

    > Doch doch. Lebenszweck von Rust ist es Sicherheit ohne (bzw mit minimalem)
    > Laufzeit-Overhead zu bieten. Für Dich, als jemand der Software schreibt,
    > die mit Netzwerken in Kontakt kommt und sich dementsprechend sorgen um
    > Sicherheitslücken machen muss, wäre die Sprache eigentlich ein
    > interessanter Ansatz. Die Frage ist da nur, ob du zu den Early Adoptern
    > gehören willst oder dann später nachziehst. ;-)

    Stimmt wohl. Fragt sich nur halt, ob das nicht wieder C++ ist, was du mir hier zu verkaufen versuchst. :) Aber wenn ich Zeit habe, werde ich mir die Sprache genauer anschauen.

  13. Re: Danke!

    Autor: esgeh 17.07.15 - 12:48

    Linuxschaden schrieb:
    > esgeh schrieb:
    > > Ok, streiten wir. Du siehst hier also irgendwo Performance-Einbußen.
    > > Warum?
    >
    > Weil wir wieder Objekte auf dem Heap verwalten.
    > Wenn ich einen Vektor verwalten will, will ich den in der Nähe von
    > Variablen halten, die ich oft verwende (des CPU caches wegen). Und das ist
    > der Stack nun mal die erste Anlaufstelle -

    In Rust wird dir genausowenig wie in C eine "Indirektion" aufgezwungen. Wenn du schreibst

    let v = vec![1,2,3];

    dann hast du im Stack ein struct liegen, was 3 Zeiger groß ist. Im Stack! Du hast da nicht einen Pointer liegen, der erst auf den Vektor zeigt, wo darin wieder Zeiger plus Längeninformation steht. Rust ist nicht Python und all die anderen Sprachen, wo alle Variablen tatsächlich Referenzen sind.

    Wenn Du ein Array mit statischer Länge im Stack halten willst — samt seiner Elemente — geht das auch:

    let a = [1,2,3]; // a ist jetzt ein [i32; 3] analog zu int[3] in C.

    Die "interne Indirektion" beim Vec hat man ja nur, damit sizeof(Vec<T>) unabhängig von der Elementanzahl ist und damit die Größe erst zur Laufzeit bekannt sein muss und diese dann auch nachträglich vergrößerbar und verkleinerbar bleibt.

    Das einzige, was Du meines Wissens in der Hinsicht in Rust nicht hast, ist alloca. Dafür bräuchte man die Unterstützung des Compilers. Diese Unterstützung gibt es zur Zeit nicht. Falls Du alloca jetzt nicht meintest, ist mir nicht ganz klar, wo Du da Unterschiede siehst, die bei Rust einen unüberwindbaren Overhead bedeuten.

    > > Diese Besitzübergabe kann genau das sein, was Du haben willst. Die
    > > aufgerufene Funktion könnte den Vec weiter an einen Thread übergeben.
    >
    > Jau. Aber auch dann würde ich den Vektor komplett auf dem Stack halten, und
    > halt nur immer die Adresse übergeben.

    Wozu? Das bringt doch nichts. Ich meine, das ginge mit "scoped threads" in Rust sogar, aber ich sehe da gar keinen Sinn drin. Einen Vec von einem Stackframe in einen anderen umziehen zu lassen (ggf in einen anderen Thread) ist genauso billig wie das Kopieren von 3 hintereinander im Speicher liegenden Zeigern.

    > [...] In Rust muss man das
    > Kopieren also explizit erlauben ... das ist in der Tat nicht
    > unintelligent.

    :-)

    > > Da schlägt das Herz des C Programmierers doch höher, nicht?
    >
    > Yup. :p

    :-)

    > > Joa, das heißt, das, was der Thread zu tun bekommt, sollte einiges
    > > länger
    > > als 2 Mikrosekunden dauern.
    >
    > Entweder das, oder das System muss sofort wieder antworten und die zu
    > erledigende Aufgabe an den Thread weiterleiten. Und da sind wir wieder bei
    > den hochskalierenden Servern. :)

    Hier scheinen wir uns langsam im Kreis zu drehen — zumindest bevor einer von uns mal wieder nachmisst, ob der dünne Wrapper um pthreads aus std::thread (in C++ sowie Rust) einen signifikanten Overhead hat. Wie gesagt, ich glaube das erst, wenn ich es sehe, weil in diesem Wrapper so gut wie nix passiert. Das ist hauptsächlich Compile-Time Type Checking.

    > Futures werden normalerweise statt async deferred ausgeführt
    > (en.cppreference.com Sprich, die machen das dann lieber synchron.

    Eine Verzögerung ist also besser? Versteh' ich jetzt nicht.

    > Man kann dem Compiler (zumindest unter dem g++, meine ich) sagen, dass man
    > echte Threads haben will, aber das kostet dann plötzlich - soweit ich mich
    > erinnere, steigt die Laufzeit auf 150%. Wo man mit OpenMP auf 50% der
    > deferred Ausführung kommt. Die Zeiten waren damals alle im Sekundenbereich,
    > also nicht wirklich trivial.

    Hast du denn bei OpenMP "echte Threads"? Oder sind das Threads, die schon im Pool warten und schneller reagieren können, weil das Betriebssystem schon alles andere "eingerichtet" hatte? Was sind "echte Threads"? Will man die überhaupt? Wenn std::async flotter ohne "echte Threads" den übergebenen Funktor startet, sind "echte Threads" irgendwie doof, oder?

    Anyways, ich vertraue dir da mit diesen Angaben nicht. Und wenn Du manuell per pthreads erzeugte Threads mit schon potentiell im Pool geparkten OpenMP Threads vergleichst, dann sind das Äpfel und Birnen. Wenn Du Threadpools haben willst, benutze Threadpools. Ganz einfach. Das ist jetzt kein intrinsisches C++ Problem. Es sieht da einfach genauso aus wie in C. Allerdings ist das bei C++ auch ein Baustellenbereich, wo man Besserung erwarten kann ("executors" in folgenden ISO Standards). Das heißt, früher oder später müsstest Du das in C++ NICHT mehr manuell machen, so wie in C, sondern bekommst Unterstützung von der Standardbibliothek. Ich tippe aber mal darauf, dass diese Entwickling in Rust einfach flotter laufen wird. Da passiert irgendwie mehr pro Zeiteinheit.

    > Stimmt wohl. Fragt sich nur halt, ob das nicht wieder C++ ist, was du mir
    > hier zu verkaufen versuchst. :) Aber wenn ich Zeit habe, werde ich mir die
    > Sprache genauer anschauen.

    Cool! :-)

    Naja, in C++ kann man auch ein paar Dinge machen, die in Rust nicht gehen. Beide Sprachen entwickeln sich weiter. Aber in C++ nachträglich das dranflanschen, was Dir in Rust die "Sicherheit" bringt, ist IMHO nahezu unmöglich.

  1. Thema

Neues Thema Ansicht wechseln


Um zu kommentieren, loggen Sie sich bitte ein oder registrieren Sie sich. Sie müssen ausserdem in Ihrem Account-Profil unter Forum einen Nutzernamen vergeben haben. Zum Login

Stellenmarkt
  1. Hays AG, München
  2. NEXPLORE Technology GmbH, Darmstadt, Essen
  3. WEINMANN Emergency Medical Technology GmbH & Co. KG, Hamburg
  4. Deutsche Bundesbank, Frankfurt am Main

Golem pur
  • Golem.de ohne Werbung nutzen

Anzeige
Top-Angebote
  1. (u. a. Acer CB342CKsmiiphzx 34-Zoll-LED UWQHD IPS FreeSync HDR 75Hz für 279,97€ (Deal des...
  2. 470,37€ (Vergleichspreis 497,19€)
  3. 119,90€ (Bestpreis!)


Haben wir etwas übersehen?

E-Mail an news@golem.de