Fable: Road to Fable 2.0: Leichte Typen?

Erstellt am 10. Jan. 2018  ·  57Kommentare  ·  Quelle: fable-compiler/Fable

Eine der Richtlinien beim Entwerfen von Fable bestand darin, JS so standardmäßig wie möglich zu generieren, das leicht von anderem JS-Code übernommen werden kann. Aus diesem Grund wurden F#-Typen (einschließlich Records und Unions) so kompiliert, dass sie den _realen_ Typen in modernen JS: ES2015-Klassen (mit Reflexionsinformationen) am nächsten kommen.

Dies funktioniert gut und bedeutet auch, dass Sie den Typ nach JS exportieren und normalerweise alle Eigenschaften und Methoden aufrufen können. Dies bedeutet jedoch auch, dass Typen in JS etwas _teuer_ werden (hauptsächlich in Bezug auf die Codegröße), wo es üblicher ist, weniger Klassen zu definieren und mehr wörtliche Objekte zu verwenden (auch bekannt als einfache alte JS-Objekte oder _pojo_). Beispiel, in dem 2 Zeilen F# zu 36 Zeilen JS führen . Besonders deutlich wird dies in Elmish-Apps, in denen Sie normalerweise viele kleine Typen deklarieren, um Nachrichten und Modelle zu definieren. Fulma ist ein Beispiel für eine sehr nützliche Bibliothek, die dem App-Bundle viele KBs hinzufügt, was für einige Benutzer ein großer Nachteil sein kann.

Um dem entgegenzuwirken, versuchen wir die Kompilierung bestimmter Typen mit einem speziellen Attribut zu optimieren (siehe #1308 #1316). Dies führt zu guten Ergebnissen, zwingt den Benutzer jedoch, daran zu denken, das Attribut und seine Sonderregeln zu verwenden und auch je nach Dekorator leicht unterschiedliche Laufzeitsemantiken für denselben Typ zu tolerieren (und wir haben bereits zu viele solcher Attribute! Erase , Pojo ...). Ein anderer Ansatz für eine neue Hauptversion wäre, alle Datensätze und Vereinigungen in einer einfacheren Form zu kompilieren, damit die Semantik vorhersehbarer ist und jeder Benutzer die Vorteile erhält, ohne spezielle Attribute zu verwenden. Ein erster Vorschlag könnte sein, etwas Ähnliches wie Reason/Bucklescript zu tun

  • Kompilieren Sie Unions als JS-Arrays : So wird beispielsweise aus Bar(6, "foo") [0, 6, "foo"] (das erste Element ist der Tag-Index). Unions ohne Datenfeld (zB type Foo = A | B | C ) könnten als Integer kompiliert werden (genauso wie Enums). Dies ist, was Reason/Bucklescript tun, und angesichts der Häufigkeit, mit der Unions in F#-Programmen verwendet werden, um Nachrichten usw mit Arrays umgehen).

Es ist auch möglich, alle Unionsfälle ohne Daten zu einer Ganzzahl zu kompilieren (ich denke, Bucklescript macht das), aber alle JS-Leistungsleitfäden, die ich gelesen habe, empfehlen, polymorphe Methoden zu vermeiden. Wenn eine Funktion also einen Unionstyp erwartet, sollte es besser sein, ihn zu füttern immer entweder ein Array oder eine ganze Zahl, nicht jeweils abhängig vom Fall.

  • Kompilieren Sie Datensätze als JS-Arrays (wie Reason/Bucklescript) oder als Pojos : Die erste Option sollte eine bessere Leistung und Codegrößen bieten (auch einfachere Laufzeitgleichheitsprüfungen, da wir bereits strukturelle Gleichheit für Arrays haben), während die zweite ein besseres Debugging ermöglichen sollte Erfahrung (Sie können die Objektfelder in den Browser-Devtools lesen) und Kompatibilität mit Bibliotheken wie React.

Im zweiten Fall müssen wir wahrscheinlich eine zusätzliche Eigenschaft hinzufügen, um F#-Datensätze (mit struktureller Gleichheit) von _normalen_ JS-Objekten zu unterscheiden, es sei denn, wir möchten standardmäßig strukturelle Gleichheit für alle Objekte in Fable implementieren.

Beachten Sie, dass dies bedeutet, dass in JS keine Klasse/Funktion generiert wird, um den Typ darzustellen. Instanz- und statische Member des Typs werden zu Methoden des einschließenden Moduls. Eigentlich tut dies der F#-Compiler standardmäßig, aber Fable mountet die Member im Typ.

Tupel werden weiterhin als JS-Arrays kompiliert und Klassen werden wahrscheinlich mehr oder weniger so bleiben, wie sie jetzt sind.

Dies wird einen erheblichen Arbeitsaufwand erfordern und wird mit einigen der aktuellen Fable-Funktionen, insbesondere in Bezug auf Reflexion und Typprüfung, nicht kompatibel sein. Wir müssen sorgfältig entscheiden, ob die Vorteile die Nachteile überwiegen:

Vorteile

  • Reduzierte Bundle-Größen, Verkürzung der Download- und Analysezeiten für Ihre App
  • Besseres Baumschütteln (da Typmethoden zu Modulfunktionen werden)
  • (Wahrscheinlich) bessere Leistung, da die Indizierung eines Arrays schneller sein sollte als der Zugriff auf eine benannte Eigenschaft

Nachteile

  • Typtests und das Abrufen von Reflexionsinformationen von eingerahmten Objekten sind für DUs und Datensätze nicht mehr möglich

Dasselbe passiert, wenn ein Typ an JS gesendet wird: Der externe Code kann nicht auf Typmember zugreifen. Dies ist möglicherweise keine große Sache, da Fable nicht oft verwendet wird, um F#-Typen an JS AFAIK zu senden.

  • Es wird Herausforderungen bei Laufzeitgleichheit/-vergleich und Stringkonvertierung geben
  • Ein Objekt muss generiert werden, wenn ein Typ in eine Schnittstelle umgewandelt wird (um die Schnittstellenmethoden anzuhängen)
  • Weniger lesbarer JS-Code?
dev2.0 discussion

Hilfreichster Kommentar

wie Reason/Bucklescript tun

Bucklescript ist eine sehr gute Quelle für solche Sachen.

So wird zum Beispiel Bar(6, "foo") zu [0, 6, "foo"]

Eines der wichtigsten Verkaufsargumente von Fable ist die Lesbarkeit des Codes, und jemand, der sich darauf verlässt, wird niemals ein Upgrade durchführen.

In Babel haben wir normalerweise eine Option, um die Ausgabe zu optimieren, was, wenn Fable ein Kompilierungs-Flag hinzufügt, das die Optimierungen einschaltet. Der Nachteil besteht darin, dass zwei Codepfade beibehalten werden müssen, die im Wesentlichen dasselbe tun, um zu vermeiden, dass ich empfehlen würde, einen anderen AST-Pass (wenn die Optimierung aktiviert ist) zu verwenden, um Folgendes zu transformieren: Bar(6, "foo") in [0, 6, "foo"] .

Auch wenn die Optimierung deaktiviert ist, können Sie den Code weiterhin so debuggen oder lesen, wie Sie es derzeit können.

Alle 57 Kommentare

Von all meinem Anwendungsfall brauchte ich bei der Verwendung von Fable nie Reflexion, also ist es für mich ein Ja.

Danach können wir die Reflexion vielleicht noch durch die Verwendung von Attributen unterstützen, wenn dies wirklich erforderlich ist. Aber im Allgemeinen vermeide ich es, Reflection zu verwenden, ich denke nicht, dass dies eine gute Praxis ist und hilft nicht beim Schreiben von sauberem, robustem Code.

Um den JS-Code lesbarer zu machen, könnten wir Kommentare wie etwas generieren: [0 /* Bar */, 6, "foo"] .
Wir könnten dies entweder im DEBUG Modus generieren oder immer und Plugins wie uglifyJS Kommentare aus der Ausgabe entfernen lassen.

Ich bin mir nicht sicher, diesen Teil zu verstehen:

Es wird Herausforderungen bei Laufzeitgleichheit/-vergleich und Stringkonvertierung geben

@MangelMaxime

Aber im Allgemeinen vermeide ich es, Reflection zu verwenden, ich denke nicht, dass dies eine gute Praxis ist und hilft nicht beim Schreiben von sauberem, robustem Code.

Keine gute Praxis? Jede Art von Metaprogrammierung (außerhalb von unsicherem Interop) ist ohne Reflection nicht möglich und da Fable keine qoutations unterstützt, ist Reflection der einzige Weg. Viele Bibliotheksimplementierer, die Reflection verwenden, erwarten, dass sie die Metadaten der von den Benutzern bereitgestellten Typen lesen können.

@Zaid-Ajaj Ich habe das gerade gesagt, weil ich jedes Mal, wenn ich Reflection verwende, endlich zu einer anderen Lösung zurückgekehrt bin. :)

Da die Verwendung von Reflection im Allgemeinen seltener ist, schlage ich auch vor, sie über ein Attribut zu unterstützen, wenn wir sie nicht auf andere Weise unterstützen können. :)
Dementsprechend ist der Code standardmäßig optimiert.

Einige Notizen :)

  • Die Reflektion wird nur für obj deaktiviert, sie ist jedoch weiterhin für Typen verfügbar, die zur Kompilierzeit bekannt sind, oder Generics (wenn Sie die Funktion inline oder das PassGenerics Attribut verwenden). Ich denke, dies sollte die meisten aktuellen Anwendungsfälle abdecken. Es kann für einige Fable-Core-Funktionen, die zur Laufzeit auf Reflection-Informationen angewiesen sind, schwierig sein, wie ofJson und keyValueList aber ich hoffe, wir finden eine Lösung.

  • Ja, Kommentare sind eine gute Idee. Sie werden in der Produktion von UglifyJS automatisch entfernt, daher sollte es in Ordnung sein.

  • Über die Stringkonvertierung zum Beispiel prüft Fable im Moment ToString zu überschreiben oder nicht). Wir können zur Kompilierzeit einige Informationen für die Root-Objekte erhalten, aber für verschachtelte Member ist es schwieriger.

Kompilieren Sie Datensätze als JS-Arrays (wie Reason/Bucklescript) oder als Pojos

Pojos ist für mich der Gewinner, da "Kompatibilität mit Bibliotheken wie React" und Debugging ein großes Plus ist.

wie Reason/Bucklescript tun

Bucklescript ist eine sehr gute Quelle für solche Sachen.

So wird zum Beispiel Bar(6, "foo") zu [0, 6, "foo"]

Eines der wichtigsten Verkaufsargumente von Fable ist die Lesbarkeit des Codes, und jemand, der sich darauf verlässt, wird niemals ein Upgrade durchführen.

In Babel haben wir normalerweise eine Option, um die Ausgabe zu optimieren, was, wenn Fable ein Kompilierungs-Flag hinzufügt, das die Optimierungen einschaltet. Der Nachteil besteht darin, dass zwei Codepfade beibehalten werden müssen, die im Wesentlichen dasselbe tun, um zu vermeiden, dass ich empfehlen würde, einen anderen AST-Pass (wenn die Optimierung aktiviert ist) zu verwenden, um Folgendes zu transformieren: Bar(6, "foo") in [0, 6, "foo"] .

Auch wenn die Optimierung deaktiviert ist, können Sie den Code weiterhin so debuggen oder lesen, wie Sie es derzeit können.

  • Ich mag den Opt-in-Optimierungsansatz.
  • Strukturelle Gleichheit - ich würde die F#-Semantik beibehalten; Wenn F#-Dokumente sagen, dass Datensätze standardmäßig strukturell gleich sind, sollte Fable dies nicht ändern (es gibt bereits ein Attribut dafür, wenn Sie sich abmelden möchten).
  • Unions - Unions sind zur Laufzeit bereits schwieriger zu lesen, vielleicht könnten wir zur ursprünglichen Fable-Implementierung (wo die Groß-/Kleinschreibung lesbar war) für Debug/nicht optimierte Ausgabe und Arrays für Release/optimiert zurückkehren?
  • Datensätze = Pojos + Modulmitglieder 👍
  • Reflexion/Metaprogrammierung - Ich würde jeden Tag Unterstützung für Zitate gegenüber Reflexion annehmen. Wenn wir Reflexion für vorhandene Funktionen benötigen - gut, aber ansonsten wäre ich damit einverstanden, sie nicht zu haben.

In Babel haben wir normalerweise eine Option, um die Ausgabe zu optimieren, was, wenn Fable ein Kompilierungs-Flag hinzufügt, das die Optimierungen einschaltet.

Nun, ich denke, es fasst nur meinen Standpunkt zusammen. 😉

Fulma ist ein Beispiel für eine sehr nützliche Bibliothek, die dem App-Bundle viele KBs hinzufügt, was für einige Benutzer ein großer Nachteil sein kann

Wir brauchen Zahlen 😉

Wie auch immer, solange Fable-Apps einfach zu debuggen sind, wird es für mich in Ordnung sein, denn auf lange Sicht ist es immer die Wartung von altem Code, die teuer wird.

@whitetigle Zitat @alfonsogarciacaro

Eine normale Fable SPA-App ist ungefähr 100 KB klein, aber Fulma fügt 200 KB extra hinzu

Und wir haben einige Arbeit, um etwa 100 KB nach Fulma zu entfernen, mit einer neuen Version von Fable, indem manuell [<Fable.Core.CompileAsArray>] hinzugefügt wird (neues Attribut).

Ich weiß nicht, ob Sie dies gesehen haben, aber neuere V8-Versionen haben ihren Ansatz zur Optimierung von Code geändert:

https://medium.com/the-node-js-collection/get-ready-a-new-v8-is-coming-node-js-performance-is-changing-46a63d6da4de

Hallo zusammen,

  • Das Hinzufügen eines bestimmten Attributs sollte vermieden werden , die Opt-in-Optimierung scheint ein besserer Ansatz zu sein
  • Strukturelle Gleichheit: Es sollte als Standard-F# funktionieren

  • Array/Objekt
    Array oder Objekt ist vielleicht nicht so wichtig, da wir immer noch eine gute Quellkarte für das Debuggen haben .
    Ich möchte js nicht debuggen/lesen.

  • Buddle-Größe ist wichtig
    Benutzer navigieren jetzt häufiger auf Mobilgeräten mit 3G oder 4G. Es ist immer mühsam, ein oder zwei Minuten zu warten, um die APP zu erhalten.

FWIW, ich habe Reflektion erst in letzter Zeit und nur als letzten Ausweg verwendet. Ich verwende es nur für eine generische Funktion (mit PassGenerics ), also denke ich, dass diese Änderungen dies nicht beeinflussen.

👍 zur Vereinfachung von kompilierten Unions- / Datensatztypen

Wenn dies mit einem globalen Schalter gesteuert werden soll, benötigen wir wahrscheinlich etwas, um dieses Verhalten pro Typ, Modul oder sogar Projekt zu überschreiben ( *.fsproj Datei, aus der eine Datei stammt). Falls Sie explizit möchten, dass bestimmte Typen Klassen sind.

Wäre es sinnvoll, diese Funktion in Fable 1.4 (?) nur für nicht-öffentliche Typen einzuführen und sie dann auf alle öffentlichen APIs in Fable 2 auszuweiten?

Vielen Dank für die Kommentare! Sie sind sehr nützlich, um das Design für Fable 2.0 zu erstellen , ich schreibe später eine Zusammenfassung :+1: Danke auch für den Link

Was Kompilierungsflags für Optimierungen angeht, denke ich, dass dies die Wartungskosten zu stark erhöhen kann, wenn die Laufzeitsemantik geändert wird, was dazu führen kann, dass einige Funktionen in Fable-Core nicht wie erwartet funktionieren. Es sollte jedoch möglich sein, einige spezifische Änderungen vorzunehmen, um das Debugging zu vereinfachen: wie die Verwendung von Strings für Union-Tags während der Entwicklung und Integers in der Produktion.

Leistung ist ein Feature und ich denke, Fable sollte bessere Laufzeitoptimierungen bieten. Opt-in über Attribute macht den Code weniger lesbar und auch schwieriger für die .net-Serverseite zu teilen. Daher würde ich Compiler- / Projektschalter für Optimierungsfunktionen bevorzugen, während das alte Verhalten beibehalten wird (zumindest bis es als veraltet erwiesen ist). Opt-in ist immer noch eine gute Wahl für ausgewählte Anwendungen.

Ich dachte auch, dass die Verwendung von F#-Arrays und -Listen aus Leistungs- / Größengründen optional auf einfache JS-Arrays optimiert werden könnte.

@alfonsogarciacaro Leistung ist nicht so wichtig, also warum sich die Mühe machen, Ein weiser Mann hat mir einmal gesagt, dass

Mal beiseite, Reflexion ist der Weg von so viel Bösem, Sie sollten die Leute davon abbringen, wenn sie es brauchen, können sie bei der älteren Version bleiben, während sie einen besseren Weg finden, um ohne Reflexion zu implementieren. Ohne Reflexion und ein paar andere .net-Edelsteine ​​wären wir in der Lage, den Ausführungsgraphen im Compiler (fsc) viel besser zu glätten und einzufügen.

Die Projektwechsel-Anweisungen werden sowieso ein Albtraum sein, das Projekt zu pflegen und in zwei Richtungen aufzuteilen. Es gibt immer die historischen Veröffentlichungen für diejenigen, die den alten Weg als alternative Version brauchen, aber ich denke, Sie müssen eine Richtung wählen und mit ihr gehen.

Leistung ist nicht so wichtig, also warum sich die Mühe machen, Ein weiser Mann hat mir einmal gesagt, dass Entwicklererfahrung wichtiger ist

war ich das? ;P

Leistungsoptimierungen sind wichtig, wenn jemand das Projekt tatsächlich nutzt... und tatsächlich ein Problem mit der Leistung hat. Ich wäre sehr vorsichtig, wenn ich vorzeitige Optimierungen einführe, die die Entwicklererfahrung nur für einige Benchmarks verschlechtern könnten.

Der Hauptpunkt der "Lightweight-Typen" ist nicht die Leistung. Es ist die Bundle-Größe, wenn ich meine Diskussion mit @alfonsogarciacaro verstanden habe.

Die Leistung, sind nur eine Konsequenz aus diesem Punkt :)

Im Allgemeinen schaue ich mir Performance-Probleme selten an, weil sie eher selten zu sehen sind. (Ich spreche nur von meiner Erfahrung :))

@Krzysztof-Cieslak, nun, Sie haben irgendwann eine Gestalt, aber ich habe mich in diesem Fall auf

Ich stimme voll und ganz zu, die Leistung ist in Bibliotheken wichtig, in denen Leute verwenden, die unweigerlich auf Leistungsprobleme stoßen. Ich denke, das wichtigste Gleichgewicht besteht darin, die Entwickler-Api so einfach / elegant wie möglich zu machen und die ganze harte Leistung hinter den Kulissen zu erledigen. Es ist keine LOB-App, bei der wir einfache klare Abstraktionen für komplexe Logik haben können / sollten ... Es ist eine Basisbibliothek Das sollte unter der einfachen / eleganten API magisch und leistungsstark sein und die Komplexität vor Dev verbergen.

Der Hauptpunkt der "Lightweight-Typen" ist nicht die Leistung. Es ist die Bundle-Größe, wenn ich meine Diskussion mit @alfonsogarciacaro verstanden habe.
Die Leistung, sind nur eine Konsequenz aus diesem Punkt :)

Aufgrund der CPU-Cache-Größen und der IO-Bandbreite sind die beiden stark korreliert 😄

@gerardtoconnor Hehe, obj mehr (und wahrscheinlich auch keine benutzerdefinierte Gleichheit für DUs), aber wenn dies für die meisten Benutzer in Ordnung ist, können wir es versuchen.

Auf jeden Fall haben Sie Recht mit der Schwierigkeit, verschiedene Code-Generationen zu pflegen, wir müssen stattdessen einen klaren Weg für Fable 2.0 definieren. Was die Reflexion angeht, erlaubt Fable nur einen kleinen Teil, aber es ist nützlich für die Serialisierung und andere Tricks (wie das Konvertieren von DUs in JS-Objekte oder Dropdown-Optionen). Dies wird beibehalten (für Typen, die zur Kompilierzeit bekannt sind oder PassGenerics ), aber es wird über Modulfunktionen verfügbar sein, sodass es durch Baumschütteln entfernt werden kann, wenn es nicht aufgerufen wird.

Ich übergebe Objekte als Nachrichten zwischen Fable und Akka.Net/F# auf dem Server. Da der Typ der Nachricht die Nachricht identifiziert, wird es mir wichtig bleiben, den Typ in die Serialisierung einzubeziehen und etwas in Fable zu deserialisieren, ohne zu wissen, was der Typ sein könnte. Nach der Deserialisierung einer Nachricht auf dem Client wird deren Laufzeittyp :? (instanceof) wird verwendet, um die Nachricht weiterzuleiten.
Es wäre in Ordnung, wenn Sie sich dafür entscheiden müssten, typeinfo und den echten JS/Runtime-Typ beizubehalten, damit dies weiterhin funktioniert. Die Mehrzahl der Arten braucht dies jedoch nicht, so die Proliferation von Entfernen [<Pojo>] begrüßt werden würde.

Ich habe gerade die Größe von 1 MB für das Prod-Bundle erreicht ... Jetzt ist es in Ordnung für Highspeed-Internet, aber nicht für alles andere als das. Und wieder ist es in der Tat eine große Größe. Also, wenn etwas kommt, als die Größe zu reduzieren. Ich bin dabei.

Nur um eine Idee zu geben. Wo ich nicht einmal [<Pojo>]

    type Validate = {
           IsValid : bool
           ErrMsg : string
        } with
           static member isValid =
               (fun m -> m.IsValid), (fun n m -> {m with IsValid = n})
           static member errMsg =
               (fun m -> m.ErrMsg), (fun n m -> {m with ErrMsg = n})

    type DocumentStatus = Select = 0 | All = 1 | New = 2 | InProcess = 3 | Processed = 4 | Error = 5

    type DocumentModel = {
        File : string
        FileName : string
        Label : string
        Status : DocumentStatus
        Patient : string
        Date : string
        Notes : string
    }with
        static member file = (fun m -> m.File), (fun n m -> {m with File = n})
        static member fileName = (fun m -> m.FileName), (fun n m -> {m with FileName = n})
        static member label = (fun m -> m.Label), (fun n m -> {m with Label = n})
        static member status = (fun m -> m.Status), (fun n m -> {m with Status = n})
        static member patient = (fun m -> m.Patient), (fun n m -> {m with Patient = n})
        static member date = (fun m -> m.Date), (fun n m -> {m with Date = n})
        static member note = (fun m -> m.Notes), (fun n m -> {m with Notes = n})


    type ErrorModel = {
        File : Validate
        FileName : Validate
        Label : Validate
        Patient : Validate
        Date : Validate
        Notes : Validate
    }with
        static member file = (fun m -> m.File), (fun n (m:ErrorModel) -> {m with File = n})
        static member fileName = (fun m -> m.FileName), (fun n (m:ErrorModel) -> {m with FileName = n})
        static member label = (fun m -> m.Label), (fun n (m:ErrorModel) -> {m with Label = n})
        static member patient = (fun m -> m.Patient), (fun n (m:ErrorModel) -> {m with Patient = n})
        static member date = (fun m -> m.Date), (fun n (m:ErrorModel) -> {m with Date = n})
        static member notes = (fun m -> m.Notes), (fun n (m:ErrorModel) -> {m with Notes = n})


    type Model = {
        Id : int
        ShowUploadModal : bool
        DocumentModel : DocumentModel
        ErrorModel : ErrorModel
        IsValid : bool
    }with
        static member documentModel = (fun m -> m.DocumentModel),(fun n m -> {m with DocumentModel = n})
        static member errorModel = (fun m -> m.ErrorModel), (fun n m -> {m with ErrorModel = n})

Jetzt wird das wie alles an Größe zunehmen. Statische Funktionen sind dazu da, lences - aether zu verwenden, um Datensatztypen zu ändern.

Und offensichtlich fehlt vielen Fischbetreibern die Lesbarkeit...

@kunjee17 Sie können auch kein statisches Mitglied verwenden, um die Linsen zu unterstützen. Sie können sie wie folgt schreiben:

type Model = {
  Id : int }
}

let modelIdLens = ...

// or

module ModelLens =

let id = ...

So können Sie die [<Pojo>] Attribute verwenden.

Verwenden Sie CDN in Ihrer Anwendung oder bündeln Sie alles darin, wie Ihren Code und Ihre Bibliotheken?

@MangelMaxime Ich

"dependencies": {
    "@servicestack/client": "^1.0.0",
    "animate.css": "^3.5.2",
    "babel-polyfill": "^6.26.0",
    "babel-runtime": "^6.26.0",
    "flatpickr": "^4.0.6",
    "font-awesome": "^4.7.0",
    "izitoast": "^1.1.5",
    "lowdb": "^1.0.0",
    "preact": "^8.2.1",
    "preact-compat": "^3.16.0",
    "remotedev": "^0.2.7"
  },
  "devDependencies": {
    "@types/lowdb": "^0.15.0",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.6.1",
    "bulma": "0.5.2",
    "bulma-extensions": "^0.5.2",
    "css-loader": "^0.28.7",
    "fable-loader": "^1.1.2",
    "fable-utils": "^1.0.6",
    "file-loader": "^0.11.1",
    "loglevel": "^1.5.0",
    "node-sass": "^4.5.3",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "webpack": "^3.6.0",
    "webpack-dev-server": "^2.8.2"

Ich werde versuchen, was Sie gesagt haben, aber die Dinge in ein Modul statt in ein statisches Mitglied einfügen

Update1
@MangelMaxime für denselben Code, den ich im obigen Kommentar geteilt habe. Der generierte Code ist in Fable-Antwort 50 Zeilen weniger und es gab auch kein Include, wenn ich Module mit Pojo anstelle von statischen Funktionen mit Typ verwende.

Für den obigen Code ist hier Code generiert

import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import { compareRecords, equalsRecords } from "fable-core/Util";
export class Validate {
  constructor(isValid, errMsg) {
    this.IsValid = isValid;
    this.ErrMsg = errMsg;
  }

  [_Symbol.reflection]() {
    return {
      type: "Test.Validate",
      interfaces: ["FSharpRecord", "System.IEquatable", "System.IComparable"],
      properties: {
        IsValid: "boolean",
        ErrMsg: "string"
      }
    };
  }

  Equals(other) {
    return equalsRecords(this, other);
  }

  CompareTo(other) {
    return compareRecords(this, other) | 0;
  }

  static get isValid() {
    return [function (m) {
      return m.IsValid;
    }, function (n, m_1) {
      return new Validate(n, m_1.ErrMsg);
    }];
  }

  static get errMsg() {
    return [function (m) {
      return m.ErrMsg;
    }, function (n, m_1) {
      return new Validate(m_1.IsValid, n);
    }];
  }

}
setType("Test.Validate", Validate);
export class DocumentModel {
  constructor(file, fileName, label, status, patient, date, notes) {
    this.File = file;
    this.FileName = fileName;
    this.Label = label;
    this.Status = status | 0;
    this.Patient = patient;
    this.Date = date;
    this.Notes = notes;
  }

  [_Symbol.reflection]() {
    return {
      type: "Test.DocumentModel",
      interfaces: ["FSharpRecord", "System.IEquatable", "System.IComparable"],
      properties: {
        File: "string",
        FileName: "string",
        Label: "string",
        Status: "number",
        Patient: "string",
        Date: "string",
        Notes: "string"
      }
    };
  }

  Equals(other) {
    return equalsRecords(this, other);
  }

  CompareTo(other) {
    return compareRecords(this, other) | 0;
  }

  static get file() {
    return [function (m) {
      return m.File;
    }, function (n, m_1) {
      return new DocumentModel(n, m_1.FileName, m_1.Label, m_1.Status, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get fileName() {
    return [function (m) {
      return m.FileName;
    }, function (n, m_1) {
      return new DocumentModel(m_1.File, n, m_1.Label, m_1.Status, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get label() {
    return [function (m) {
      return m.Label;
    }, function (n, m_1) {
      return new DocumentModel(m_1.File, m_1.FileName, n, m_1.Status, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get status() {
    return [function (m) {
      return m.Status;
    }, function (n, m_1) {
      return new DocumentModel(m_1.File, m_1.FileName, m_1.Label, n, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get patient() {
    return [function (m) {
      return m.Patient;
    }, function (n, m_1) {
      return new DocumentModel(m_1.File, m_1.FileName, m_1.Label, m_1.Status, n, m_1.Date, m_1.Notes);
    }];
  }

  static get date() {
    return [function (m) {
      return m.Date;
    }, function (n, m_1) {
      return new DocumentModel(m_1.File, m_1.FileName, m_1.Label, m_1.Status, m_1.Patient, n, m_1.Notes);
    }];
  }

  static get note() {
    return [function (m) {
      return m.Notes;
    }, function (n, m_1) {
      return new DocumentModel(m_1.File, m_1.FileName, m_1.Label, m_1.Status, m_1.Patient, m_1.Date, n);
    }];
  }

}
setType("Test.DocumentModel", DocumentModel);
export class ErrorModel {
  constructor(file, fileName, label, patient, date, notes) {
    this.File = file;
    this.FileName = fileName;
    this.Label = label;
    this.Patient = patient;
    this.Date = date;
    this.Notes = notes;
  }

  [_Symbol.reflection]() {
    return {
      type: "Test.ErrorModel",
      interfaces: ["FSharpRecord", "System.IEquatable", "System.IComparable"],
      properties: {
        File: Validate,
        FileName: Validate,
        Label: Validate,
        Patient: Validate,
        Date: Validate,
        Notes: Validate
      }
    };
  }

  Equals(other) {
    return equalsRecords(this, other);
  }

  CompareTo(other) {
    return compareRecords(this, other) | 0;
  }

  static get file() {
    return [function (m) {
      return m.File;
    }, function (n, m_1) {
      return new ErrorModel(n, m_1.FileName, m_1.Label, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get fileName() {
    return [function (m) {
      return m.FileName;
    }, function (n, m_1) {
      return new ErrorModel(m_1.File, n, m_1.Label, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get label() {
    return [function (m) {
      return m.Label;
    }, function (n, m_1) {
      return new ErrorModel(m_1.File, m_1.FileName, n, m_1.Patient, m_1.Date, m_1.Notes);
    }];
  }

  static get patient() {
    return [function (m) {
      return m.Patient;
    }, function (n, m_1) {
      return new ErrorModel(m_1.File, m_1.FileName, m_1.Label, n, m_1.Date, m_1.Notes);
    }];
  }

  static get date() {
    return [function (m) {
      return m.Date;
    }, function (n, m_1) {
      return new ErrorModel(m_1.File, m_1.FileName, m_1.Label, m_1.Patient, n, m_1.Notes);
    }];
  }

  static get notes() {
    return [function (m) {
      return m.Notes;
    }, function (n, m_1) {
      return new ErrorModel(m_1.File, m_1.FileName, m_1.Label, m_1.Patient, m_1.Date, n);
    }];
  }

}
setType("Test.ErrorModel", ErrorModel);
export class Model {
  constructor(id, showUploadModal, documentModel, errorModel, isValid) {
    this.Id = id | 0;
    this.ShowUploadModal = showUploadModal;
    this.DocumentModel = documentModel;
    this.ErrorModel = errorModel;
    this.IsValid = isValid;
  }

  [_Symbol.reflection]() {
    return {
      type: "Test.Model",
      interfaces: ["FSharpRecord", "System.IEquatable", "System.IComparable"],
      properties: {
        Id: "number",
        ShowUploadModal: "boolean",
        DocumentModel: DocumentModel,
        ErrorModel: ErrorModel,
        IsValid: "boolean"
      }
    };
  }

  Equals(other) {
    return equalsRecords(this, other);
  }

  CompareTo(other) {
    return compareRecords(this, other) | 0;
  }

  static get documentModel() {
    return [function (m) {
      return m.DocumentModel;
    }, function (n, m_1) {
      return new Model(m_1.Id, m_1.ShowUploadModal, n, m_1.ErrorModel, m_1.IsValid);
    }];
  }

  static get errorModel() {
    return [function (m) {
      return m.ErrorModel;
    }, function (n, m_1) {
      return new Model(m_1.Id, m_1.ShowUploadModal, m_1.DocumentModel, n, m_1.IsValid);
    }];
  }

}
setType("Test.Model", Model);

Und hier ist der aktualisierte Code

open Fable.Core
open Fable.Core.JsInterop

[<Pojo>]
type Validate = {
           IsValid : bool
           ErrMsg : string
        }

module ValidateLens = 
    let isValid =
        (fun m -> m.IsValid), (fun n m -> {m with IsValid = n})
    let errMsg =
        (fun m -> m.ErrMsg), (fun n m -> {m with ErrMsg = n})

type DocumentStatus = Select = 0 | All = 1 | New = 2 | InProcess = 3 | Processed = 4 | Error = 5

[<Pojo>]
type DocumentModel = {
    File : string
    FileName : string
    Label : string
    Status : DocumentStatus
    Patient : string
    Date : string
    Notes : string
}

module DocumentModelLens =
    let file = (fun m -> m.File), (fun n m -> {m with File = n})
    let fileName = (fun m -> m.FileName), (fun n m -> {m with FileName = n})
    let label = (fun m -> m.Label), (fun n m -> {m with Label = n})
    let status = (fun m -> m.Status), (fun n m -> {m with Status = n})
    let patient = (fun m -> m.Patient), (fun n m -> {m with Patient = n})
    let date = (fun m -> m.Date), (fun n m -> {m with Date = n})
    let note = (fun m -> m.Notes), (fun n m -> {m with Notes = n})

[<Pojo>]
type ErrorModel = {
    File : Validate
    FileName : Validate
    Label : Validate
    Patient : Validate
    Date : Validate
    Notes : Validate
}

module ErrorModelLens =
    let file = (fun m -> m.File), (fun n (m:ErrorModel) -> {m with File = n})
    let fileName = (fun m -> m.FileName), (fun n (m:ErrorModel) -> {m with FileName = n})
    let label = (fun m -> m.Label), (fun n (m:ErrorModel) -> {m with Label = n})
    let patient = (fun m -> m.Patient), (fun n (m:ErrorModel) -> {m with Patient = n})
    let date = (fun m -> m.Date), (fun n (m:ErrorModel) -> {m with Date = n})
    let notes = (fun m -> m.Notes), (fun n (m:ErrorModel) -> {m with Notes = n})

[<Pojo>]
type Model = {
    Id : int
    ShowUploadModal : bool
    DocumentModel : DocumentModel
    ErrorModel : ErrorModel
    IsValid : bool
}

module ModelLens =
    let documentModel = (fun m -> m.DocumentModel),(fun n m -> {m with DocumentModel = n})
    let errorModel = (fun m -> m.ErrorModel), (fun n m -> {m with ErrorModel = n})

es wird mehr JavaScript-Code generieren

export const ValidateLens = function (__exports) {
  const isValid = __exports.isValid = [function (m) {
    return m.IsValid;
  }, function (n, m_1) {
    return {
      IsValid: n,
      ErrMsg: m_1.ErrMsg
    };
  }];
  const errMsg = __exports.errMsg = [function (m) {
    return m.ErrMsg;
  }, function (n, m_1) {
    return {
      IsValid: m_1.IsValid,
      ErrMsg: n
    };
  }];
  return __exports;
}({});
export const DocumentModelLens = function (__exports) {
  const file = __exports.file = [function (m) {
    return m.File;
  }, function (n, m_1) {
    return {
      File: n,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Status: m_1.Status,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const fileName = __exports.fileName = [function (m) {
    return m.FileName;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: n,
      Label: m_1.Label,
      Status: m_1.Status,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const label = __exports.label = [function (m) {
    return m.Label;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: n,
      Status: m_1.Status,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const status = __exports.status = [function (m) {
    return m.Status;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Status: n,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const patient = __exports.patient = [function (m) {
    return m.Patient;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Status: m_1.Status,
      Patient: n,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const date = __exports.date = [function (m) {
    return m.Date;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Status: m_1.Status,
      Patient: m_1.Patient,
      Date: n,
      Notes: m_1.Notes
    };
  }];
  const note = __exports.note = [function (m) {
    return m.Notes;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Status: m_1.Status,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: n
    };
  }];
  return __exports;
}({});
export const ErrorModelLens = function (__exports) {
  const file_1 = __exports.file = [function (m) {
    return m.File;
  }, function (n, m_1) {
    return {
      File: n,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const fileName_1 = __exports.fileName = [function (m) {
    return m.FileName;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: n,
      Label: m_1.Label,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const label_1 = __exports.label = [function (m) {
    return m.Label;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: n,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const patient_1 = __exports.patient = [function (m) {
    return m.Patient;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Patient: n,
      Date: m_1.Date,
      Notes: m_1.Notes
    };
  }];
  const date_1 = __exports.date = [function (m) {
    return m.Date;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Patient: m_1.Patient,
      Date: n,
      Notes: m_1.Notes
    };
  }];
  const notes = __exports.notes = [function (m) {
    return m.Notes;
  }, function (n, m_1) {
    return {
      File: m_1.File,
      FileName: m_1.FileName,
      Label: m_1.Label,
      Patient: m_1.Patient,
      Date: m_1.Date,
      Notes: n
    };
  }];
  return __exports;
}({});
export const ModelLens = function (__exports) {
  const documentModel = __exports.documentModel = [function (m) {
    return m.DocumentModel;
  }, function (n, m_1) {
    return {
      Id: m_1.Id,
      ShowUploadModal: m_1.ShowUploadModal,
      DocumentModel: n,
      ErrorModel: m_1.ErrorModel,
      IsValid: m_1.IsValid
    };
  }];
  const errorModel = __exports.errorModel = [function (m) {
    return m.ErrorModel;
  }, function (n, m_1) {
    return {
      Id: m_1.Id,
      ShowUploadModal: m_1.ShowUploadModal,
      DocumentModel: m_1.DocumentModel,
      ErrorModel: n,
      IsValid: m_1.IsValid
    };
  }];
  return __exports;
}({});

Also, was wird eine gute Route sein. Ist dieses Ding in Fable Standard? @alfonsogarciacaro

Update2

Ich bin auf halbem Weg, alle Modelle mit Pojo zu modifizieren. Es reduziert den Code; Scheint aber so wenig zu sein. Werde beim Update weiter posten.

Ich denke, es wäre besser, diese Diskussion in einer separaten Ausgabe @kunjee17 zu führen. Versuchen

@MangelMaxime Entschuldigung wurde mitgerissen. Aber ja, wir brauchen leichte Typen oder Richtlinien, um das zu erreichen ...

@michaelsg Ich denke, wir hatten eine ähnliche Diskussion bei der Veröffentlichung von Fable 0.7 oder 1.0. Um die Deserialisierung mit Typinformationen zu unterstützen, haben wir eine globale Variable hinzugefügt, die ein Typwörterbuch enthält. Dies ist die einzige globale Variable in fable-core und ich würde sie gerne für die nächste Version entfernen, da ich noch von niemand anderem gehört habe, der diese Funktion verwendet.

Generika sind weiterhin verfügbar, Sie können also beispielsweise Folgendes tun, um Ihren JSON mit dem Typnamen zu _taggen_:

let inline toJsonWithTypeName (o: 'T) =
    toJson (typeof<'T>.FullName, o) // On server side you would use Newtonsoft.Json

let ofJsonWithTypeName json =
    let (typeName, parsed): string * obj = ofJson json
    if typeName = typeof<MyType1>.FullName then
        let x = inflate<MyType1> parsed
        // Do something with MyType1
        ()
    elif typeName = typeof<MyType2>.FullName then
        let x = inflate<MyType2> parsed
        // Do something with MyType2
        ()
    else
        failwithf "Cannot handle type %s" typeName

Dieser Code ist nicht sehr elegant (wahrscheinlich können wir ihn mit Active Patterns oder einem Typenwörterbuch verbessern), aber es ist ein kleiner Preis, den Sie zahlen müssen, um den Rest Ihres (und allen anderen) generierten Codes zu verbessern. Da Fable-Apps wachsen, stellen wir fest, dass die Bundles ziemlich groß werden, sodass wir einige Laufzeitinformationen entfernen müssen, um gegenüber alternativen Lösungen (wie Reason) wettbewerbsfähig zu bleiben.

Achtung: inflate ist derzeit nicht rekursiv. Aber ich denke, wir können das bei Bedarf beheben.

Zwei weitere Hinweise: Unions und Records sind einfache Objekte, sodass Sie sie nur mit der nativen Browser-API JSON.parse analysieren können. F#-Klassen werden weiterhin in JS-Klassen übersetzt, sodass Sie zur Laufzeit Typtests durchführen können.

@kunjee17 Ja, es ist besser, die Diskussion nach Fulma zu verschieben oder eine neue Ausgabe zu öffnen :) Bitte überprüfen Sie die Größe des gzipped Bundles, da dies die Größe zum Herunterladen wäre, wenn Ihr Server JS-Dateien gzippt, was funktionieren sollte ;) Fable 2.0 wird die Situation verbessern, aber wenn Ihr Bundle zu einem Zeitpunkt zu stark wächst, müssen Sie Ihre App möglicherweise aufteilen.

Es scheint, dass das Emulieren von Klassen mit Eigenschaften nicht immer schneller ist. Wenn die Aufrufhäufigkeit hoch ist (aufrufstark), sind die einfachen alten Javascript-Objekte deutlich (~2x) schneller als Eigenschaften.

Ich habe einen Nachrichtencodec (wo der typische Code wie dieses Fliegengewicht aussieht: https://github.com/fable-compiler/Fable/issues/1352#issuecomment-385976678). Ich habe damit begonnen, meinen F#-Code auf Fable2 zu portieren, da er angeblich schneller als Fable1 ist. Überraschenderweise ist dies bei anruflastigem Code nicht der Fall. Der Fable2-Code wird zusätzlich zu den zusätzlichen indirekten Kosten (sowohl das Fable1- als auch das Fable2-Profil verknüpft) viel Zeit mit Eigenschaftsoperationen verbringen. Die Anwendung ist nicht geöffnet (aber schließlich möchte ich eine vereinfachte, aber repräsentative Version haben, die als Workload-Anwendungsfall geöffnet werden kann - um die Compilerleistung zu verfolgen).

Fable1 (wobei jede Zeile eine abwechselnde Codierungs-/Decodierungsoperation von 1_000_000 Wiederholungen darstellt, die den entsprechenden Codierungs-/Decodierungspuffer wiederverwendet):

0: 3.852000(s) time, 3852.000000(ns/msg) average latency, 259605.399792(msg/s) average throughput - message size: 146 - GC count: 1
0: 4.157000(s) time, 4157.000000(ns/msg) average latency, 240558.094780(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.835000(s) time, 3835.000000(ns/msg) average latency, 260756.192960(msg/s) average throughput - message size: 146 - GC count: 1
1: 4.011000(s) time, 4011.000000(ns/msg) average latency, 249314.385440(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.893000(s) time, 3893.000000(ns/msg) average latency, 256871.307475(msg/s) average throughput - message size: 146 - GC count: 1
2: 4.086000(s) time, 4086.000000(ns/msg) average latency, 244738.130201(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.849000(s) time, 3849.000000(ns/msg) average latency, 259807.742271(msg/s) average throughput - message size: 146 - GC count: 1
3: 4.165000(s) time, 4165.000000(ns/msg) average latency, 240096.038415(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.984000(s) time, 3984.000000(ns/msg) average latency, 251004.016064(msg/s) average throughput - message size: 146 - GC count: 1
4: 4.126000(s) time, 4126.000000(ns/msg) average latency, 242365.487155(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.897000(s) time, 3897.000000(ns/msg) average latency, 256607.646908(msg/s) average throughput - message size: 146 - GC count: 1
5: 4.259000(s) time, 4259.000000(ns/msg) average latency, 234796.900681(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.930000(s) time, 3930.000000(ns/msg) average latency, 254452.926209(msg/s) average throughput - message size: 146 - GC count: 1
6: 4.223000(s) time, 4223.000000(ns/msg) average latency, 236798.484490(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.917000(s) time, 3917.000000(ns/msg) average latency, 255297.421496(msg/s) average throughput - message size: 146 - GC count: 1
7: 4.238000(s) time, 4238.000000(ns/msg) average latency, 235960.358660(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.880000(s) time, 3880.000000(ns/msg) average latency, 257731.958763(msg/s) average throughput - message size: 146 - GC count: 1
8: 4.178000(s) time, 4178.000000(ns/msg) average latency, 239348.970799(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.931000(s) time, 3931.000000(ns/msg) average latency, 254388.196388(msg/s) average throughput - message size: 146 - GC count: 1
9: 4.241000(s) time, 4241.000000(ns/msg) average latency, 235793.444942(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.953000(s) time, 3953.000000(ns/msg) average latency, 252972.426006(msg/s) average throughput - message size: 146 - GC count: 1
10: 4.156000(s) time, 4156.000000(ns/msg) average latency, 240615.976901(msg/s) average throughput - message size: 146 - GC count: 1

Knotenprofil (geschwärzt):
https://gist.github.com/zpodlovics/9d42207536e15efe042a766b1fbbfae2

Fabel2:

0: 7.198000(s) time, 7198.000000(ns/msg) average latency, 138927.479856(msg/s) average throughput - message size: 146 - GC count: 1
0: 7.473000(s) time, 7473.000000(ns/msg) average latency, 133815.067577(msg/s) average throughput - message size: 146 - GC count: 1
1: 7.378000(s) time, 7378.000000(ns/msg) average latency, 135538.086202(msg/s) average throughput - message size: 146 - GC count: 1
1: 7.346000(s) time, 7346.000000(ns/msg) average latency, 136128.505309(msg/s) average throughput - message size: 146 - GC count: 1
2: 7.486000(s) time, 7486.000000(ns/msg) average latency, 133582.687684(msg/s) average throughput - message size: 146 - GC count: 1
2: 7.576000(s) time, 7576.000000(ns/msg) average latency, 131995.776135(msg/s) average throughput - message size: 146 - GC count: 1
3: 7.428000(s) time, 7428.000000(ns/msg) average latency, 134625.740442(msg/s) average throughput - message size: 146 - GC count: 1
3: 7.526000(s) time, 7526.000000(ns/msg) average latency, 132872.707946(msg/s) average throughput - message size: 146 - GC count: 1
4: 7.522000(s) time, 7522.000000(ns/msg) average latency, 132943.366126(msg/s) average throughput - message size: 146 - GC count: 1
4: 7.529000(s) time, 7529.000000(ns/msg) average latency, 132819.763581(msg/s) average throughput - message size: 146 - GC count: 1
5: 7.535000(s) time, 7535.000000(ns/msg) average latency, 132714.001327(msg/s) average throughput - message size: 146 - GC count: 1
5: 7.628000(s) time, 7628.000000(ns/msg) average latency, 131095.962244(msg/s) average throughput - message size: 146 - GC count: 1
6: 7.468000(s) time, 7468.000000(ns/msg) average latency, 133904.659882(msg/s) average throughput - message size: 146 - GC count: 1
6: 7.524000(s) time, 7524.000000(ns/msg) average latency, 132908.027645(msg/s) average throughput - message size: 146 - GC count: 1
7: 7.541000(s) time, 7541.000000(ns/msg) average latency, 132608.407373(msg/s) average throughput - message size: 146 - GC count: 1
7: 7.604000(s) time, 7604.000000(ns/msg) average latency, 131509.731720(msg/s) average throughput - message size: 146 - GC count: 1
8: 7.441000(s) time, 7441.000000(ns/msg) average latency, 134390.538906(msg/s) average throughput - message size: 146 - GC count: 1
8: 7.497000(s) time, 7497.000000(ns/msg) average latency, 133386.688009(msg/s) average throughput - message size: 146 - GC count: 1
9: 7.512000(s) time, 7512.000000(ns/msg) average latency, 133120.340788(msg/s) average throughput - message size: 146 - GC count: 1
9: 7.466000(s) time, 7466.000000(ns/msg) average latency, 133940.530405(msg/s) average throughput - message size: 146 - GC count: 1
10: 7.414000(s) time, 7414.000000(ns/msg) average latency, 134879.956838(msg/s) average throughput - message size: 146 - GC count: 1
10: 7.549000(s) time, 7549.000000(ns/msg) average latency, 132467.876540(msg/s) average throughput - message size: 146 - GC count: 1

Knotenprofil (geschwärzt):
https://gist.github.com/zpodlovics/a454ad7521945f367bc13b69a7230118

Umgebung: Ubuntu 16.04 x86_64
Knoten: v10.9.0
Fabel1: 1.3.17
Fable2: 2.0.0-beta-001

Diese Ergebnisse sind sehr interessant @zpodlovics , danke fürs Teilen! In Ihrem Fall läuft Fable 2 also langsamer als Fable 1. Tatsächlich haben wir in PR #1512 an Inline-Eigenschaftsaufrufen gearbeitet (und außerdem warten wir noch darauf, eine F#-Optimierung hinzuzufügen, die auch Eigenschaften inlinet. Haben wir nicht Ich habe es noch zusammengeführt, weil wir keinen Leistungsgewinn festgestellt haben (wir verwenden normalerweise die REPL selbst, um dies zu messen), aber vielleicht kann Ihr Projekt eine sehr interessante Möglichkeit sein, dies zu testen.

Zur Verdeutlichung führt #1512 bei einigen Workloads wie REPL einen ziemlich großen Leistungsabfall (bis zu 60 %) ein.

@alfonsogarciacaro Ich fürchte, das Inlining von Eigenschaften wird Fable2 hier nicht helfen, da es so aussieht, als würde die Eigenschaft selbst viele zusätzliche Operationen und Indirektionen einführen (und den Daten- und Befehlscache löschen) anstelle eines einfachen Objektfeldzugriffs: (https:/ /gist.github.com/zpodlovics/a454ad7521945f367bc13b69a7230118#file-fable2-profile-txt-L362). Und Inlining ist nicht immer ein Gewinn - da Sie normalerweise viel mehr Code ausführen müssen - anstatt einen Aufruf zu verwenden, der den Hot Instruction & uop Cache wiederverwendet. Kleiner, straffer Code (zB: K-Interpreter) könnte wirklich sehr schnell sein: (http://tech.marksblogg.com/billion-nyc-taxi-kdb.html).

Eigenschaftsbezogene neue Funktionalität aus dem Fable2-Profil:

 [C++ entry points]:
   ticks    cpp   total   name
  45882   43.0%   28.1%  v8::internal::Runtime_DefineDataPropertyInLiteral(int, v8::internal::Object**, v8::internal::Isolate*)

Ein paar eigenschaftsbezogene Funktionalitäts-Overheads aus dem Fable2-Profil:

   5137    3.1%    3.1%  v8::internal::JSObject::MigrateToMap(v8::internal::Handle<v8::internal::JSObject>, v8::internal::Handle<v8::internal::Map>, int)
[..]
   4623    2.8%    2.8%  v8::internal::Runtime_DefineDataPropertyInLiteral(int, v8::internal::Object**, v8::internal::Isolate*)
[..]
   3595    2.2%    2.2%  v8::internal::LookupIterator::WriteDataValue(v8::internal::Handle<v8::internal::Object>, bool)
[..]
   2734    1.7%    1.7%  v8::internal::LookupIterator::ApplyTransitionToDataProperty(v8::internal::Handle<v8::internal::JSReceiver>)
[..]
   2722    1.7%    1.7%  v8::internal::Map::TransitionToDataProperty(v8::internal::Handle<v8::internal::Map>, v8::internal::Handle<v8::internal::Name>, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::internal::PropertyConstness, v8::internal::Object::StoreFromKeyed)
[..]
   2582    1.6%    1.6%  v8::internal::LookupIterator::PrepareTransitionToDataProperty(v8::internal::Handle<v8::internal::JSReceiver>, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::internal::Object::StoreFromKeyed)
[..]
   2399    1.5%    1.5%  v8::internal::TransitionsAccessor::SearchTransition(v8::internal::Name*, v8::internal::PropertyKind, v8::internal::PropertyAttributes)
[..]
   2066    1.3%    1.3%  v8::internal::LookupIterator::State v8::internal::LookupIterator::LookupInRegularHolder<false>(v8::internal::Map*, v8::internal::JSReceiver*)
[..]
   1822    1.1%    1.1%  v8::internal::(anonymous namespace)::UpdateDescriptorForValue(v8::internal::Handle<v8::internal::Map>, int, v8::internal::PropertyConstness, v8::internal::Handle<v8::internal::Object>)
[..]
   1777    1.1%    1.1%  v8::internal::Object::AddDataProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::internal::ShouldThrow, v8::internal::Object::StoreFromKeyed)
[..]
   1743    1.1%    1.1%  v8::internal::Handle<v8::internal::PropertyArray> v8::internal::Factory::CopyArrayAndGrow<v8::internal::PropertyArray>(v8::internal::Handle<v8::internal::PropertyArray>, int, v8::internal::PretenureFlag)
[..]
   1587    1.0%    1.0%  v8::internal::LookupIterator::UpdateProtector() [clone .part.349]
[..]
   1283    0.8%    0.8%  v8::internal::JSObject::DefineOwnPropertyIgnoreAttributes(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::internal::ShouldThrow, v8::internal::JSObject::AccessorInfoHandling)
[..]
   1270    0.8%    0.8%  int v8::internal::BinarySearch<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int, int*)
[..]
   1062    0.7%    0.7%  void v8::internal::LookupIterator::Start<false>()
[..]
   1049    0.6%    0.6%  void v8::internal::LookupIterator::NextInternal<false>(v8::internal::Map*, v8::internal::JSReceiver*)
[..]
    917    0.6%    0.6%  v8::internal::HeapObject::synchronized_set_map(v8::internal::Map*)

Ein paar % hier und noch ein paar % dort und diese kleine Liste plötzlich verantwortlich für: ~22,6%

Danke für die neuen Daten @zpodlovics. Um sicher zu gehen, dass ich es gut verstehe, ist dies die Änderung im generierten JS-Code, die das Leistungsproblem verursacht?

// Fable 1
class Foo {
  get MyProperty() {
     return costlyOperation(this);
  }
}
x.MyProperty // Use example

// Fable 2
function Foo$$get_MyProperty(this$) {
  return costlyOperation(this$);
}
Foo$$get_MyProperty(x) // Use example

Ich hatte gehofft, dass die Verwendung einer Funktion (neben einer besseren Kompatibilität mit JS-Baum-Shaking und -Minifizierung) der JS-Laufzeit helfen würde, einen statischen Dispatch durchzuführen, aber vielleicht lag ich falsch. In beiden Fällen wird costlyOperation jedoch bei jedem Aufruf der Eigenschaft ausgewertet. Wir können das nicht ändern, weil es Teil der F#-Semantik ist. Wenn Sie den Wert zwischenspeichern müssen, müssen Sie dies explizit tun oder eine automatisch implementierte Eigenschaft verwenden member val MyProperty = costlyOperation()

Auf jeden Fall bin ich offen, dem Prototypen erneut Eigenschaften zuzuordnen, wenn dies die Leistung verbessern kann. Wenn wir einen Branch mit dieser Änderung erstellen, würden Sie ihn in Ihrem Projekt testen?

Der Performance-Hit kommt - nach meinem Profiling - vom Fable1-Objekt -> Fable2-Eigenschaftsänderung. Es gibt keine kostspieligen Operationen im Kodierungs-/Dekodierungspfad. Jeder Aufruf ist nichts anderes als ein primitives Lesen/Schreiben (+ manchmal ein paar ( < 100 ) Byte Array Lesen/Schreiben) + minimale Transformation (normalerweise Integer-Bitness-Erweiterung / -Kürzung), falls erforderlich. Es gibt jedoch viele Felder in einem Codierer/Decodierer (nicht nur primitive Typen, sondern auch Strukturen (einschließlich verschachtelter Strukturen und Daten variabler Länge), die aus diesen primitiven Typen aufgebaut sind). Der Codierer/Decodierer wird einmal zugewiesen (mit verschachteltem Codierer/Decodierer für jedes nicht-primitive Feld/Struktur), sodass die Nachrichtencodierung/-decodierung ohne Zuweisung im Hot-Pfad durchgeführt werden könnte.

Ein trivialer Header-Decoder/Encoder mit zwei Feldern:

Fable1 generierte js:
https://gist.github.com/zpodlovics/96013570fe8130628004ee8ed05cbfb1

Fable2 generierte js:
https://gist.github.com/zpodlovics/917dd235b1c726921d44ba16ade3271f

Flamegraphs (mit den früheren Benchmarks - gibt es einen besseren Weg, um SVG zu teilen?):

Fable1-Flammendiagramm:
https://gist.github.com/zpodlovics/8b7a3fa3890388f1ad1e233e85275283

Fable2-Flammendiagramm:
https://gist.github.com/zpodlovics/96b4b4ff169e477d16328abe28138c94

Der Feld-Lese-/Schreibvorgang muss jedes Mal wie erwartet ausgewertet werden. In einer gleichzeitigen Umgebung (js Shared Buffer + Worker) ist nicht einmal das lokale CPU-Caching erlaubt, das Lesen/Schreiben erfolgt über den physischen Speicher (flüchtige Operationen).

Danke für Ihre Unterstützung! Ich helfe gerne, Fable zu verbessern, also erstelle bitte den Branch und ich werde ihn in meinem Projekt testen.

Aktualisieren:
„Dieser Artikel beschreibt einige wichtige Grundlagen, die allen JavaScript-Engines gemeinsam sind – und nicht nur V8, der Engine, an der die Autoren (Benedikt und Mathias) arbeiten. Als JavaScript-Entwickler hilft Ihnen ein tieferes Verständnis der Funktionsweise von JavaScript-Engines beim Nachdenken. die Leistungsmerkmale Ihres Codes."
https://mathiasbynens.be/notes/shapes-ics
https://mathiasbynens.be/notes/prototypes

@alfonsogarciacaro Basierend auf meinen früheren Updates (Mathias Bynens Posts + Konferenzvideos) scheint der Leistungseinbruch von der multiplen Formdefinition und dem Shape Walking (plus der zusätzlichen Umleitung, die durch die Standalone-Funktionen eingeführt wird?) zu kommen. Auch die Verwendung von defineProperty erscheint problematisch. Theoretisch hat V8 einige Inline-Cache-Tracing-Funktionen (zumindest im Debug-Modus --trace-ic , ich werde es später ausprobieren.

const object1 = { x: 1, y: 2 };

vs

const object = {};
object.x = 5;
object.y = 6;

Es lohnt sich wahrscheinlich auch, dies zu überprüfen:
https://medium.com/@bmeurer/surprising -polymorphism-in-react-applications-63015b50abc

@zpodlovics Ich bin nicht sehr an JS-Profiling-Tools

Auf jeden Fall habe ich den properties Zweig erstellt, um mit dem Anhängen von Gettern und Settern (ohne Indizes) an den Prototyp zu experimentieren. Kannst du es bitte anschauen und testen? Anweisungen zum Testen lokaler Builds finden Sie hier: https://github.com/fable-compiler/Fable/tree/properties#using -your-local-build-in-your-projects

Vielleicht kann @ncave es auch

@alfonsogarciacaro Mein Build schlägt mit einem Map/Set-

/usr/bin/yarn run test 
yarn run v1.5.1
$ node ../node_modules/mocha/bin/mocha ../build/tests --reporter dot -t 10000
[..]
  1447 passing (7s)
  24 failing
 TypeError: (0 , _Map.FSharpMap$$get_IsEmpty) is not a function
      at Context.<anonymous> (/tmp/fable-compiler/build/tests/MapTests.js:43:69)

Vollständige Fehlerliste:

https://gist.github.com/zpodlovics/a19bbfba7c1d47796c160604af3e90f3

Update: hat die Tests auskommentiert, kämpft jetzt mit https://github.com/fable-compiler/Fable/issues/1413

Entschuldigung, ich bin ein Idiot. Vergessen, Fable-Core neu zu erstellen, bevor Sie die Tests ausführen. Lassen Sie mich das beheben.

Ich warte immer noch darauf, dass CI fertig ist, aber hoffentlich ist es jetzt behoben :pray:

Die Tests werden bestanden, aber die REPL wird nicht erstellt :/ Wie auch immer, kannst du es bitte versuchen @zpodlovics? Wenn es in Ihrem Fall funktioniert und eine Leistungsverbesserung zeigt, können wir daran arbeiten, um die verbleibenden Probleme zu beheben.

@alfonsogarciacaro Danke! Ich habe es geschafft, es zum Laufen zu bringen (jetzt basiert es auf fable-core.2.0.0-beta-002). Während der Code anders aussieht, bleibt die Leistung in etwa gleich. Ich fürchte, es gibt keinen anderen Weg, als mit den js-Profiling-Tools tiefer zu graben.

0: 7.343000(s) time, 7343.000000(ns/msg) average latency, 136184.120931(msg/s) average throughput - message size: 146 - GC count: 1
0: 7.554000(s) time, 7554.000000(ns/msg) average latency, 132380.195923(msg/s) average throughput - message size: 146 - GC count: 1
1: 7.593000(s) time, 7593.000000(ns/msg) average latency, 131700.250230(msg/s) average throughput - message size: 146 - GC count: 1
1: 7.636000(s) time, 7636.000000(ns/msg) average latency, 130958.617077(msg/s) average throughput - message size: 146 - GC count: 1
2: 7.542000(s) time, 7542.000000(ns/msg) average latency, 132590.824715(msg/s) average throughput - message size: 146 - GC count: 1
2: 7.700000(s) time, 7700.000000(ns/msg) average latency, 129870.129870(msg/s) average throughput - message size: 146 - GC count: 1
3: 7.701000(s) time, 7701.000000(ns/msg) average latency, 129853.265810(msg/s) average throughput - message size: 146 - GC count: 1
3: 7.652000(s) time, 7652.000000(ns/msg) average latency, 130684.788291(msg/s) average throughput - message size: 146 - GC count: 1
4: 7.707000(s) time, 7707.000000(ns/msg) average latency, 129752.173349(msg/s) average throughput - message size: 146 - GC count: 1
4: 7.805000(s) time, 7805.000000(ns/msg) average latency, 128122.998078(msg/s) average throughput - message size: 146 - GC count: 1
5: 7.751000(s) time, 7751.000000(ns/msg) average latency, 129015.610889(msg/s) average throughput - message size: 146 - GC count: 1
5: 7.778000(s) time, 7778.000000(ns/msg) average latency, 128567.755207(msg/s) average throughput - message size: 146 - GC count: 1
6: 7.673000(s) time, 7673.000000(ns/msg) average latency, 130327.121074(msg/s) average throughput - message size: 146 - GC count: 1
6: 7.772000(s) time, 7772.000000(ns/msg) average latency, 128667.009779(msg/s) average throughput - message size: 146 - GC count: 1
7: 7.675000(s) time, 7675.000000(ns/msg) average latency, 130293.159609(msg/s) average throughput - message size: 146 - GC count: 1
7: 8.168000(s) time, 8168.000000(ns/msg) average latency, 122428.991185(msg/s) average throughput - message size: 146 - GC count: 1
8: 7.753000(s) time, 7753.000000(ns/msg) average latency, 128982.329421(msg/s) average throughput - message size: 146 - GC count: 1
8: 7.634000(s) time, 7634.000000(ns/msg) average latency, 130992.926382(msg/s) average throughput - message size: 146 - GC count: 1
9: 7.616000(s) time, 7616.000000(ns/msg) average latency, 131302.521008(msg/s) average throughput - message size: 146 - GC count: 1
9: 7.654000(s) time, 7654.000000(ns/msg) average latency, 130650.640188(msg/s) average throughput - message size: 146 - GC count: 1
10: 7.653000(s) time, 7653.000000(ns/msg) average latency, 130667.712008(msg/s) average throughput - message size: 146 - GC count: 1
10: 7.598000(s) time, 7598.000000(ns/msg) average latency, 131613.582522(msg/s) average throughput - message size: 146 - GC count: 1

https://gist.github.com/zpodlovics/266d35c2064af1ecad1733ecb21a2d56

@alfonsogarciacaro Ihre generischen Parameter / Schnittstellenerklärung hier (https://github.com/fable-compiler/Fable/issues/1547#issuecomment-417596116) gaben mir eine Idee, Fable2 ohne Schnittstellen-bezogene "Magie" auszuprobieren:

Im Codec habe ich den Schnittstellentyp durch einen konkreten Implementierungstyp ersetzt und Viola:

0: 3.847000(s) time, 3847.000000(ns/msg) average latency, 259942.812581(msg/s) average throughput - message size: 146 - GC count: 1
0: 3.824000(s) time, 3824.000000(ns/msg) average latency, 261506.276151(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.822000(s) time, 3822.000000(ns/msg) average latency, 261643.118786(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.821000(s) time, 3821.000000(ns/msg) average latency, 261711.593824(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.827000(s) time, 3827.000000(ns/msg) average latency, 261301.280376(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.855000(s) time, 3855.000000(ns/msg) average latency, 259403.372244(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.925000(s) time, 3925.000000(ns/msg) average latency, 254777.070064(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.833000(s) time, 3833.000000(ns/msg) average latency, 260892.251500(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.825000(s) time, 3825.000000(ns/msg) average latency, 261437.908497(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.827000(s) time, 3827.000000(ns/msg) average latency, 261301.280376(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.841000(s) time, 3841.000000(ns/msg) average latency, 260348.867482(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.818000(s) time, 3818.000000(ns/msg) average latency, 261917.234154(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.803000(s) time, 3803.000000(ns/msg) average latency, 262950.302393(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.794000(s) time, 3794.000000(ns/msg) average latency, 263574.064312(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.825000(s) time, 3825.000000(ns/msg) average latency, 261437.908497(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.836000(s) time, 3836.000000(ns/msg) average latency, 260688.216893(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.853000(s) time, 3853.000000(ns/msg) average latency, 259538.022320(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.838000(s) time, 3838.000000(ns/msg) average latency, 260552.371027(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.851000(s) time, 3851.000000(ns/msg) average latency, 259672.812257(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.833000(s) time, 3833.000000(ns/msg) average latency, 260892.251500(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.849000(s) time, 3849.000000(ns/msg) average latency, 259807.742271(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.904000(s) time, 3904.000000(ns/msg) average latency, 256147.540984(msg/s) average throughput - message size: 146 - GC count: 1

Oh wirklich cool @zpodlovics. Wenn ich richtig gelesen habe, hast du hier sogar eine Leistungssteigerung.

Darf ich Sie bitten, ein Beispiel für einen Code vor / nach Ihrem Punkt zu teilen:

ohne schnittstellenbezogene "Magie" mit Fable2

So können wir später darauf zurückgreifen und vielleicht finden andere Leute einen nützlichen "Trick" :)

@MangelMaxime Nichts wirklich, nur den Schnittstellentyp durch einen konkreten Typ ersetzt (aber Sie müssen komplexere Dinge tun, um den Anruf-Overhead korrekt zu messen).

[<Interface>]
type IFunc1 =
    abstract member Func1: int32 -> int32

type MyFunc1() = 
  member this.Func1(x:int32) = x+2
  interface IFunc1 with
    member this.Func1(x: int32) = this.Func1(x)

[<Class>]
type TestInterface(instance: IFunc1) =
    member this.Call(x: int32) =
        instance.Func1(x)

type TestGeneric<'T when 'T:> IFunc1>(instance: 'T) =
    member this.Call(x: int32) =
        instance.Func1(x)

type TestSpecialized(instance: MyFunc1) =
    member this.Call(x: int32) =
        instance.Func1(x)

let myF = MyFunc1()
let myCI = TestInterface(myF)
let myCG = TestGeneric(myF)
let myCS = TestSpecialized(myF)

Interessanter, dynamischer Versand erhebt seinen hässlichen Kopf :)
SRTPs zur Rettung!

Vielen Dank, dass Sie dies weiter untersucht haben @zpodlovics , der Leistungsverlust kommt also vom Interface-Casting (in Fable 1 gab es kein Casting, da Interface-Mitglieder direkt an das Objekt angehängt wurden) anstelle der eigenständigen Eigenschaften. Dies ist etwas zu erwarten, aber wir müssen die Benutzer darauf aufmerksam machen.

Eine andere Alternative könnte sein, Objektausdrücke zu verwenden, um die Schnittstelle anstelle eines Typs zu implementieren, diese müssen nicht umgewandelt werden.

Ist es Casting oder dynamischer Versand? Scheint wichtig zu sein, die Unterscheidung zu kennen.

@et1975 Es ist eher wie

Ja, wir haben keine virtuellen Tabellen, aber Fable 2 transformiert (eigentlich umhüllt) Objekte, wenn es in Schnittstellen umgewandelt wird. Da dieser Vorgang beim Übergeben eines Objekts an eine Funktion, die die Schnittstelle akzeptiert, implizit ist, kann es sein, dass er zu oft auftritt und die Leistung beeinträchtigt. Vielleicht können wir einen Weg finden, diese Fälle zu erkennen und entweder eine Problemumgehung zu finden oder den Benutzer zu informieren.

Wäre es möglich, es zu einem faulen Wert zu machen und nur einmal zu berechnen?

Tolle Idee @Nhowka! Ich habe einen interface-map Zweig erstellt, der Objekten eine Map hinzufügt, um die Schnittstellen-Wrapper zu speichern, damit sie nicht mehrmals generiert werden müssen. @zpodlovics Wenn Sie Zeit haben, können Sie sich bitte diesen Zweig versuchen ? (unter Verwendung von Schnittstellen wie ursprünglich)

@alfonsogarciacaro Es sieht ungefähr gleich aus:

0: 7.440000(s) time, 7440.000000(ns/msg) average latency, 134408.602151(msg/s) average throughput - message size: 146 - GC count: 1
0: 7.578000(s) time, 7578.000000(ns/msg) average latency, 131960.939562(msg/s) average throughput - message size: 146 - GC count: 1
1: 7.634000(s) time, 7634.000000(ns/msg) average latency, 130992.926382(msg/s) average throughput - message size: 146 - GC count: 1
1: 7.642000(s) time, 7642.000000(ns/msg) average latency, 130855.796912(msg/s) average throughput - message size: 146 - GC count: 1
2: 7.903000(s) time, 7903.000000(ns/msg) average latency, 126534.227509(msg/s) average throughput - message size: 146 - GC count: 1
2: 7.785000(s) time, 7785.000000(ns/msg) average latency, 128452.151574(msg/s) average throughput - message size: 146 - GC count: 1
3: 7.620000(s) time, 7620.000000(ns/msg) average latency, 131233.595801(msg/s) average throughput - message size: 146 - GC count: 1
3: 7.655000(s) time, 7655.000000(ns/msg) average latency, 130633.572828(msg/s) average throughput - message size: 146 - GC count: 1
4: 7.612000(s) time, 7612.000000(ns/msg) average latency, 131371.518655(msg/s) average throughput - message size: 146 - GC count: 1
4: 7.629000(s) time, 7629.000000(ns/msg) average latency, 131078.778346(msg/s) average throughput - message size: 146 - GC count: 1
5: 7.567000(s) time, 7567.000000(ns/msg) average latency, 132152.768601(msg/s) average throughput - message size: 146 - GC count: 1
5: 7.676000(s) time, 7676.000000(ns/msg) average latency, 130276.185513(msg/s) average throughput - message size: 146 - GC count: 1
6: 7.585000(s) time, 7585.000000(ns/msg) average latency, 131839.156229(msg/s) average throughput - message size: 146 - GC count: 1
6: 7.679000(s) time, 7679.000000(ns/msg) average latency, 130225.289751(msg/s) average throughput - message size: 146 - GC count: 1
7: 7.705000(s) time, 7705.000000(ns/msg) average latency, 129785.853342(msg/s) average throughput - message size: 146 - GC count: 1
7: 7.665000(s) time, 7665.000000(ns/msg) average latency, 130463.144162(msg/s) average throughput - message size: 146 - GC count: 1
8: 7.767000(s) time, 7767.000000(ns/msg) average latency, 128749.839063(msg/s) average throughput - message size: 146 - GC count: 1
8: 7.771000(s) time, 7771.000000(ns/msg) average latency, 128683.567108(msg/s) average throughput - message size: 146 - GC count: 1
9: 8.000000(s) time, 8000.000000(ns/msg) average latency, 125000.000000(msg/s) average throughput - message size: 146 - GC count: 1
9: 7.819000(s) time, 7819.000000(ns/msg) average latency, 127893.592531(msg/s) average throughput - message size: 146 - GC count: 1
10: 7.670000(s) time, 7670.000000(ns/msg) average latency, 130378.096480(msg/s) average throughput - message size: 146 - GC count: 1
10: 7.632000(s) time, 7632.000000(ns/msg) average latency, 131027.253669(msg/s) average throughput - message size: 146 - GC count: 1

Auch der Flammengraph hat sich etwas geändert:
https://gist.github.com/zpodlovics/8990c388b390aef83ef357d607d8683c

Hmm, vielleicht überwiegen die Kosten für die Verwendung einer Karte den Vorteil eines Caches. Nun, das Wichtigste ist, dass wir einen potenziellen Engpass identifiziert haben, damit wir ihn in die Dokumentation schreiben können und die Benutzer dann versuchen können, die Schnittstellen wie in Ihrem Fall nach konkreten Typen zu ändern.

@ncave Vielleicht könnten wir bei REPL repariert , aber das Bankprojekt muss wahrscheinlich auch

@alfonsogarciacaro Es scheint, dass jede eingeführte anruflastigen Umgebung

Generische Typparameter / flexible Typen sollten diese Art der Spezialisierung ermöglichen (ungefähr genauso wie .NET):
https://github.com/fable-compiler/Fable/issues/1547#issuecomment -417607515

Update: Der neue attach-interfaces Zweig hat jetzt ungefähr die gleiche Leistung:

Keine Schnittstelle (Schnittstellennamen durch den konkreten Implementierungstyp ersetzt):

0: 3.703000(s) time, 3703.000000(ns/msg) average latency, 270051.309749(msg/s) average throughput - message size: 146 - GC count: 1
0: 3.776000(s) time, 3776.000000(ns/msg) average latency, 264830.508475(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.716000(s) time, 3716.000000(ns/msg) average latency, 269106.566200(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.734000(s) time, 3734.000000(ns/msg) average latency, 267809.319764(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.759000(s) time, 3759.000000(ns/msg) average latency, 266028.198989(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.800000(s) time, 3800.000000(ns/msg) average latency, 263157.894737(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.726000(s) time, 3726.000000(ns/msg) average latency, 268384.326355(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.704000(s) time, 3704.000000(ns/msg) average latency, 269978.401728(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.695000(s) time, 3695.000000(ns/msg) average latency, 270635.994587(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.704000(s) time, 3704.000000(ns/msg) average latency, 269978.401728(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.702000(s) time, 3702.000000(ns/msg) average latency, 270124.257158(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.702000(s) time, 3702.000000(ns/msg) average latency, 270124.257158(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.702000(s) time, 3702.000000(ns/msg) average latency, 270124.257158(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.702000(s) time, 3702.000000(ns/msg) average latency, 270124.257158(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.735000(s) time, 3735.000000(ns/msg) average latency, 267737.617135(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.834000(s) time, 3834.000000(ns/msg) average latency, 260824.204486(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.782000(s) time, 3782.000000(ns/msg) average latency, 264410.364886(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.774000(s) time, 3774.000000(ns/msg) average latency, 264970.853206(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.714000(s) time, 3714.000000(ns/msg) average latency, 269251.480883(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.779000(s) time, 3779.000000(ns/msg) average latency, 264620.269913(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.797000(s) time, 3797.000000(ns/msg) average latency, 263365.815117(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.804000(s) time, 3804.000000(ns/msg) average latency, 262881.177708(msg/s) average throughput - message size: 146 - GC count: 1

Leistungsstatistik:

 Performance counter stats for '/usr/local/stow/node-v10.9.0-linux-x64/bin/node out/App.js':

      82064.278305      task-clock (msec)         #    0.995 CPUs utilized          
            87,527      context-switches          #    0.001 M/sec                  
               722      cpu-migrations            #    0.009 K/sec                  
             5,982      page-faults               #    0.073 K/sec                  
   280,989,430,699      cycles                    #    3.424 GHz                      (83.33%)
     6,108,103,111      stalled-cycles-frontend   #    2.17% frontend cycles idle     (83.35%)
    64,063,336,685      stalled-cycles-backend    #   22.80% backend cycles idle      (33.32%)
   513,504,567,372      instructions              #    1.83  insn per cycle         
                                                  #    0.12  stalled cycles per insn  (49.98%)
   112,707,374,241      branches                  # 1373.404 M/sec                    (66.64%)
       314,186,288      branch-misses             #    0.28% of all branches          (83.32%)

      82.496763182 seconds time elapsed

Schnittstelle (Typ TestInterface(Instanz: IFunc1) style) unter Verwendung des Attach-Interfaces-Zweigs:

0: 3.610000(s) time, 3610.000000(ns/msg) average latency, 277008.310249(msg/s) average throughput - message size: 146 - GC count: 1
0: 3.733000(s) time, 3733.000000(ns/msg) average latency, 267881.060809(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.747000(s) time, 3747.000000(ns/msg) average latency, 266880.170803(msg/s) average throughput - message size: 146 - GC count: 1
1: 3.702000(s) time, 3702.000000(ns/msg) average latency, 270124.257158(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.757000(s) time, 3757.000000(ns/msg) average latency, 266169.816343(msg/s) average throughput - message size: 146 - GC count: 1
2: 3.776000(s) time, 3776.000000(ns/msg) average latency, 264830.508475(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.712000(s) time, 3712.000000(ns/msg) average latency, 269396.551724(msg/s) average throughput - message size: 146 - GC count: 1
3: 3.676000(s) time, 3676.000000(ns/msg) average latency, 272034.820457(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.777000(s) time, 3777.000000(ns/msg) average latency, 264760.391845(msg/s) average throughput - message size: 146 - GC count: 1
4: 3.743000(s) time, 3743.000000(ns/msg) average latency, 267165.375367(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.824000(s) time, 3824.000000(ns/msg) average latency, 261506.276151(msg/s) average throughput - message size: 146 - GC count: 1
5: 3.767000(s) time, 3767.000000(ns/msg) average latency, 265463.233342(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.796000(s) time, 3796.000000(ns/msg) average latency, 263435.194942(msg/s) average throughput - message size: 146 - GC count: 1
6: 3.721000(s) time, 3721.000000(ns/msg) average latency, 268744.961032(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.824000(s) time, 3824.000000(ns/msg) average latency, 261506.276151(msg/s) average throughput - message size: 146 - GC count: 1
7: 3.811000(s) time, 3811.000000(ns/msg) average latency, 262398.320651(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.811000(s) time, 3811.000000(ns/msg) average latency, 262398.320651(msg/s) average throughput - message size: 146 - GC count: 1
8: 3.807000(s) time, 3807.000000(ns/msg) average latency, 262674.021539(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.866000(s) time, 3866.000000(ns/msg) average latency, 258665.287118(msg/s) average throughput - message size: 146 - GC count: 1
9: 3.822000(s) time, 3822.000000(ns/msg) average latency, 261643.118786(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.861000(s) time, 3861.000000(ns/msg) average latency, 259000.259000(msg/s) average throughput - message size: 146 - GC count: 1
10: 3.817000(s) time, 3817.000000(ns/msg) average latency, 261985.852764(msg/s) average throughput - message size: 146 - GC count: 1

Perf-Zustand:

 Performance counter stats for '/usr/local/stow/node-v10.9.0-linux-x64/bin/node ../out/App.js':

      82623.523614      task-clock (msec)         #    0.994 CPUs utilized          
            89,557      context-switches          #    0.001 M/sec                  
               841      cpu-migrations            #    0.010 K/sec                  
             6,313      page-faults               #    0.076 K/sec                  
   281,714,381,415      cycles                    #    3.410 GHz                      (83.33%)
     6,714,688,151      stalled-cycles-frontend   #    2.38% frontend cycles idle     (83.34%)
    64,922,007,973      stalled-cycles-backend    #   23.05% backend cycles idle      (33.33%)
   513,781,341,985      instructions              #    1.82  insn per cycle         
                                                  #    0.13  stalled cycles per insn  (49.98%)
   112,755,544,325      branches                  # 1364.691 M/sec                    (66.64%)
       330,090,577      branch-misses             #    0.29% of all branches          (83.32%)

      83.108369641 seconds time elapsed

https://github.com/fable-compiler/Fable/issues/1547#issuecomment -419070695

@alfonsogarciacaro Können wir dieses Problem schließen?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen