Fable: Road to Fable 2.0 : types légers ?

Créé le 10 janv. 2018  ·  57Commentaires  ·  Source: fable-compiler/Fable

L'une des directives lors de la conception de Fable était de générer un JS aussi standard que possible qui puisse être facilement utilisé à partir d'un autre code JS. Pour cette raison, les types F# (y compris les enregistrements et les unions) ont été compilés au plus près des types _real_ dans les classes JS modernes: ES2015 (avec des informations de réflexion).

Cela fonctionne bien et cela signifie également que vous pouvez exporter le type vers JS et appeler normalement toutes les propriétés et méthodes. Cependant, cela signifie également que les types deviennent quelque peu _cher_ (principalement en termes de taille de code) dans JS, où il est plus courant de définir moins de classes et d'utiliser plus d'objets littéraux (alias, simples objets JS anciens ou _pojo_). Exemple où 2 lignes de F# donnent 36 lignes de JS . Cela devient particulièrement évident dans les applications Elmish où vous déclarez généralement de nombreux petits types pour définir des messages et des modèles. Fulma est un exemple de bibliothèque très utile qui ajoute de nombreux KB à l'app bundle, ce qui peut être un gros inconvénient pour certains utilisateurs.

Pour contrer cela, nous essayons d'optimiser la compilation de certains types avec un attribut spécial (voir #1308 #1316). Cela donne de bons résultats mais cela oblige l'utilisateur à se souvenir d'utiliser l'attribut et ses règles spéciales, et aussi à tolérer une sémantique d'exécution légèrement différente pour le même type selon le décorateur (et nous avons déjà trop de tels attributs ! Erase , Pojo ...). Une approche différente pour une nouvelle version majeure consisterait à compiler tous les enregistrements et unions de manière plus légère afin que la sémantique soit plus prévisible et que chaque utilisateur bénéficie des avantages sans utiliser d'attributs spéciaux. Une proposition initiale pourrait être de faire quelque chose de similaire à ce que font Reason/Bucklescript

  • Compilez les unions sous forme de tableaux JS : Ainsi par exemple Bar(6, "foo") devient [0, 6, "foo"] (le premier élément est l'index de la balise). Les unions sans aucun champ de données (par exemple type Foo = A | B | C ) pourraient être compilées sous forme d'entiers (comme les énumérations). C'est ce que font Reason/Bucklescript et étant donné la fréquence à laquelle les unions sont utilisées dans les programmes F# pour modéliser les messages, etc., cela devrait réduire beaucoup de code car la syntaxe des tableaux est très légère en JS, ainsi que les gains de performances (les moteurs JS sont très optimisés pour traiter les tableaux).

Il est également possible de compiler tous les cas d'union sans données en un entier (je pense que Bucklescript le fait) mais tous les guides de performances JS que j'ai lus recommandent d'éviter les méthodes polymorphes, donc si une fonction attend un type d'union, il devrait être préférable de le nourrir toujours soit un tableau, soit un entier, pas chacun selon le cas.

  • Compilez les enregistrements en tant que tableaux JS (comme le font Reason/Bucklescript) ou en tant que pojos : la première option devrait fournir de meilleures performances et tailles de code (également des vérifications d'égalité d'exécution plus faciles car nous avons déjà une égalité structurelle pour les tableaux) tandis que la seconde devrait permettre un meilleur débogage expérience (vous pouvez lire les champs d'objet dans les devtools du navigateur) et la compatibilité avec des bibliothèques comme React.

Dans le second cas, nous devrons probablement ajouter une propriété supplémentaire pour distinguer les enregistrements F# (avec égalité structurelle) des objets JS _normal_, à moins que nous ne voulions implémenter l'égalité structurelle par défaut pour tous les objets de Fable.

Notez que cela signifie qu'il n'y aura pas de classe/fonction générée dans JS pour représenter le type. L'instance et les membres statiques du type deviendront des méthodes du module englobant. En fait, c'est ce que le compilateur F# fait par défaut, mais Fable monte les membres dans le type.

Les tuples seront toujours compilés en tant que tableaux et classes JS resteront probablement plus ou moins tels qu'ils sont maintenant.

Cela demandera beaucoup de travail et sera incompatible avec certaines des fonctionnalités actuelles de Fable, en particulier autour de la réflexion et des tests de type. Nous devons décider avec soin si les avantages l'emportent sur les inconvénients :

Avantages

  • Tailles des bundles réduites, améliorant les temps de téléchargement et d'analyse pour votre application
  • Meilleure secousse de l'arbre (à mesure que les méthodes de type deviennent des fonctions de module)
  • (Probablement) meilleures performances, car l'indexation d'un tableau devrait être plus rapide que l'accès à une propriété nommée

Les inconvénients

  • Les tests de type et l'obtention d'informations de réflexion à partir d'un objet en boîte ne seront plus possibles pour les DU et les enregistrements

La même chose se produira lors de l'envoi d'un type à JS : le code externe ne pourra pas accéder aux membres du type. Ce n'est peut-être pas un gros problème car Fable n'est pas beaucoup utilisé pour envoyer des types F# à JS AFAIK.

  • Il y aura des défis avec l'égalité/comparaison d'exécution et la conversion de chaînes
  • Un objet devra être généré lorsqu'un type est transtypé vers une interface (pour attacher les méthodes d'interface)
  • Code JS moins lisible ?
dev2.0 discussion

Commentaire le plus utile

comme le font Reason/Bucklescript

Bucklescript est une très bonne source pour ce genre de choses.

Ainsi, par exemple, Bar(6, "foo") devient [0, 6, "foo"]

L'un des principaux arguments de vente de Fable est la lisibilité du code, et quelqu'un qui s'y fie ne mettra jamais à niveau.

Dans Babel, nous avons généralement une option pour régler la sortie, et si Fable ajoute un indicateur de compilation qui active les optimisations. L'inconvénient est de devoir maintenir deux chemins de code faisant essentiellement la même chose, pour éviter cela, je recommanderais d'utiliser une autre passe AST (si l'optimisation est activée) pour transformer : Bar(6, "foo") en [0, 6, "foo"] .

Dans le cas où l'optimisation est désactivée, vous pouvez toujours déboguer ou lire le code comme vous le pouvez actuellement.

Tous les 57 commentaires

De tous mes cas d'utilisation, je n'ai jamais eu besoin de réflexion lors de l'utilisation de Fable donc pour moi c'est un oui.

Après, peut-être pouvons-nous encore soutenir la réflexion en utilisant des attributs si cela est vraiment nécessaire. Mais en général, je préfère éviter l'utilisation de la réflexion, je ne pense pas que ce soit une bonne pratique et n'aide pas à écrire du code propre et robuste.

Afin de rendre le code JS plus lisible, nous pourrions générer des commentaires comme quelque chose comme : [0 /* Bar */, 6, "foo"] .
Nous pourrions soit générer cela en mode DEBUG , soit toujours et laisser des plugins comme uglifyJS supprimer les commentaires de la sortie.

Je ne suis pas sûr de comprendre cette partie :

Il y aura des défis avec l'égalité/comparaison d'exécution et la conversion de chaînes

@MangelMaxime

Mais en général, je préfère éviter l'utilisation de la réflexion, je ne pense pas que ce soit une bonne pratique et n'aide pas à écrire du code propre et robuste.

Pas une bonne pratique ? Tout type de métaprogrammation (en dehors de l'interopérabilité non sécurisée) ne sera pas possible sans Reflection et puisque Fable ne prend pas en charge les qoutations, Reflection est le seul moyen. De nombreux implémenteurs de bibliothèques utilisant Reflection s'attendent à pouvoir lire les métadonnées des types fournis par les utilisateurs.

@Zaid-Ajaj Je viens de dire cela parce que, chaque fois que j'utilise Reflection, je suis finalement revenu à une autre solution. :)

Parce qu'en général l'usage de la réflexion est moins fréquent c'est aussi pour ça, je propose de le supporter via un attribut si on ne peut pas le supporter par un autre moyen. :)
Ainsi, le code est optimisé par défaut.

Quelques notes :)

  • La réflexion ne sera désactivée que pour obj , elle sera toujours disponible pour les types connus à la compilation ou génériques (si vous inlinez la fonction ou utilisez l'attribut PassGenerics ). Je pense que cela devrait couvrir la plupart des cas d'utilisation actuels. Cela peut être délicat pour certaines fonctions du noyau de la fable qui reposent sur des informations de réflexion au moment de l'exécution, comme ofJson et keyValueList mais j'espère que nous pourrons trouver une solution de contournement.

  • Oui, les commentaires sont une bonne idée. Ils sont automatiquement supprimés en production par UglifyJS donc ça devrait aller.

  • À propos de la conversion de chaîne , par exemple, Fable vérifie d'abord si l'objet implémente ToString , puis le type de l'objet . Je ne sais pas comment cela pourrait être fait sans les informations de type à l'exécution (et s'il sera possible ou non de remplacer ToString ). Nous pouvons obtenir des informations sur les objets racine au moment de la compilation, mais pour les membres imbriqués, c'est plus difficile.

Compiler les enregistrements en tant que tableaux JS (comme Reason/Bucklescript le font) ou en tant que pojos

Pojos est le gagnant pour moi en tant que "compatibilité avec des bibliothèques comme React" et le débogage est un gros plus.

comme le font Reason/Bucklescript

Bucklescript est une très bonne source pour ce genre de choses.

Ainsi, par exemple, Bar(6, "foo") devient [0, 6, "foo"]

L'un des principaux arguments de vente de Fable est la lisibilité du code, et quelqu'un qui s'y fie ne mettra jamais à niveau.

Dans Babel, nous avons généralement une option pour régler la sortie, et si Fable ajoute un indicateur de compilation qui active les optimisations. L'inconvénient est de devoir maintenir deux chemins de code faisant essentiellement la même chose, pour éviter cela, je recommanderais d'utiliser une autre passe AST (si l'optimisation est activée) pour transformer : Bar(6, "foo") en [0, 6, "foo"] .

Dans le cas où l'optimisation est désactivée, vous pouvez toujours déboguer ou lire le code comme vous le pouvez actuellement.

  • J'aime l'approche d'optimisation opt-in.
  • Égalité structurelle - je garderais la sémantique F# ; si les documents F# disent que les enregistrements sont structurellement égaux par défaut, Fable ne devrait pas changer cela (il existe déjà un attribut pour cela, si vous souhaitez vous désinscrire).
  • Unions - les unions sont déjà plus difficiles à lire au moment de l'exécution, peut-être pourrions-nous revenir à l'implémentation d'origine de Fable (où le cas était lisible par l'homme) pour la sortie Debug/non optimisée et les tableaux pour Release/optimised ?
  • Records = Pojos + membres du module 👍
  • Réflexion/Métaprogrammation - Je prendrais le support des devis plutôt que la réflexion n'importe quel jour. Si nous avons besoin de réflexion pour les fonctionnalités existantes - très bien, mais sinon, je serais d'accord pour ne pas l'avoir.

Dans Babel, nous avons généralement une option pour régler la sortie, et si Fable ajoute un indicateur de compilation qui active les optimisations.

Eh bien, je pense que cela résume juste mon point de vue. ??

Fulma est un exemple de bibliothèque très utile qui ajoute de nombreux KB à l'app bundle, ce qui peut être un gros inconvénient pour certains utilisateurs

Nous avons besoin de chiffres

Quoi qu'il en soit, tant que les applications Fable sont faciles à déboguer, ça ira pour moi car à la longue, c'est toujours la maintenance sur l'ancien code qui devient coûteuse.

@whitetigle citant @alfonsogarciacaro

Environ, une application Fable SPA normale est d'environ 100 Ko minifiée mais Fulma ajoute 200 Ko supplémentaires

Et nous avons du travail pour supprimer environ 100 Ko à Fulma, avec une nouvelle version de Fable en ajoutant manuellement [<Fable.Core.CompileAsArray>] (nouvel attribut)

Je ne sais pas si vous avez vu cela, mais les versions récentes de la V8 ont changé leur approche de l'optimisation du code :

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

Salut tout le monde,

  • L'ajout d'attributs spécifiques doit être évité , l'optimisation de l'opt-in semble être une meilleure approche
  • Égalité structurelle : cela devrait fonctionner comme F# standard

  • Tableau/Obj
    Un tableau ou un objet n'est peut-être pas si important puisque nous avons toujours une bonne carte source pour le débogage .
    Je ne veux pas déboguer/lire js.

  • La taille du groupe est importante
    Les utilisateurs naviguent plus souvent maintenant sur un appareil mobile avec la 3G ou la 4G. c'est toujours pénible d'attendre une ou deux minutes pour obtenir l'APP.

FWIW, je n'ai utilisé la réflexion que récemment et uniquement en dernier recours. Je ne l'utilise que sur une fonction générique (avec PassGenerics ) donc je pense que ces changements n'auront pas d'effet.

👍 sur l'allègement des types d'unions/enregistrements compilés

Si cela doit être contrôlé avec un commutateur global, nous avons probablement besoin de quelque chose pour remplacer ce comportement par type, module ou même projet (fichier *.fsproj où vient un fichier). Au cas où vous voudriez explicitement que certains types soient des classes.

Serait-il judicieux d'introduire cette fonctionnalité dans Fable 1.4 (?) pour les types non publics uniquement, puis de l'étendre à toutes les API publiques de Fable 2 ?

Merci beaucoup pour les commentaires! Ils sont très utiles pour faire le design de Fable 2.0, j'écrirai un résumé plus tard :+1: Merci aussi pour le lien @jgrund , le post était très bien.

À propos des indicateurs de compilation pour les optimisations, je pense que cela peut trop augmenter le coût de maintenance si cela modifie la sémantique d'exécution, ce qui peut empêcher certaines fonctions de fable-core de fonctionner comme prévu. Cependant, il devrait être possible d'apporter des modifications spécifiques pour faciliter le débogage : comme l'utilisation de chaînes pour les balises d'union pendant le développement et les entiers en production.

La performance est une fonctionnalité et je pense que Fable devrait offrir de meilleures optimisations d'exécution. L'inscription via les attributs rend le code moins lisible et également plus difficile à partager avec le serveur .net. Ainsi, ma préférence serait les commutateurs compilateur/projet pour les fonctionnalités d'optimisation tout en conservant l'ancien comportement (au moins jusqu'à ce qu'il soit prouvé qu'il est obsolète). L'inscription est toujours un bon choix pour l'application sélectionnée.

J'ai également pensé que l'utilisation de tableaux et de listes F# pourrait éventuellement être optimisée en tableaux JS simples pour des raisons de performances/taille.

Les performances de

Mis à part, la réflexion est la voie de tant de mal, vous devriez en sevrer les gens, s'ils en ont besoin, ils peuvent rester sur l'ancienne version pendant qu'ils trouvent un meilleur moyen de mettre en œuvre sans réflexion. Si ce n'était de la réflexion et de quelques autres joyaux .net, nous serions en mesure de faire un bien meilleur aplatissement et alignement du graphe d'exécution dans le compilateur (fsc).

Les déclarations de changement de projet vont être un cauchemar pour maintenir et diviser le projet dans deux directions de toute façon, il y a toujours les versions historiques pour ceux qui ont besoin de l'ancienne méthode comme version alternative, mais je pense que vous devez choisir une direction et aller avec.

la performance n'est pas si importante alors pourquoi s'embêter, un homme sage m'a dit une fois que l'expérience de développement est plus importante

c'était moi ? ;P

Les optimisations de performances sont importantes lorsque quelqu'un utilise réellement le projet... et qu'il a en fait un problème de performances. Je serais très prudent en introduisant des optimisations prématurées qui pourraient aggraver l'expérience du développeur juste pour quelques points de repère.

Le point principal des "types légers" n'est pas la performance. C'est la taille du bundle si j'ai bien compris ma discussion avec @alfonsogarciacaro .

Les performances, ne sont qu'une conséquence de ce point :)

En général, je regarde rarement les problèmes de performances car c'est assez rare de les voir. (je parle juste de mon expérience :) )

@Krzysztof-Cieslak, eh bien, vous avez une apparence à un moment donné, mais je faisais référence à @alfonsogarciacaro dans ce cas ... ne vous inquiétez pas, vous

Je suis tout à fait d'accord, la performance est importante dans les bibliothèques où les utilisateurs, certains qui rencontreront inévitablement des problèmes de performance. Je pense que l'équilibre important est de rendre l'API de développement aussi simple/élégante que possible et de faire tout le travail de performance en coulisses, ce n'est pas une application LOB où nous pouvons/devrions avoir des abstractions simples et claires pour une logique complexe... C'est une bibliothèque de base cela devrait être magique et performant sous l'API simple/élégante, cachant les complexités de Dev.

Le point principal des "types légers" n'est pas la performance. C'est la taille du bundle si j'ai bien compris ma discussion avec @alfonsogarciacaro .
Les performances, ne sont qu'une conséquence de ce point :)

En raison de la taille du cache du processeur et de la bande passante des E/S, les deux sont fortement corrélés 😄

@gerardtoconnor Hehe, l'expérience de développement est très importante, c'est ce que nous avons fait ces deux dernières années et grâce à cela, nous avons maintenant des projets impressionnants comme Ionide, Elmish ou Fulma. Maintenant, nous pouvons nous concentrer sur les performances :wink: De plus, maintenant que nous avons une base d'utilisateurs plus large, nous pouvons mieux voir comment Fable est utilisé dans la nature et quelles fonctionnalités pouvons-nous supprimer : ces optimisations signifieront que vous ne pourrez pas effectuer de tests de type sur obj plus (et probablement pas non plus d'égalité personnalisée pour les DU), mais si cela convient à la plupart des utilisateurs, nous pouvons essayer.

Dans tous les cas, vous avez raison sur la difficulté de maintenir différentes générations de code, il faudra plutôt définir un chemin clair pour Fable 2.0. À propos de la réflexion, Fable n'autorise qu'une petite partie, mais il est utile pour la sérialisation et d'autres astuces (comme la conversion des DU en objets JS ou des options de liste déroulante). Cela sera maintenu (pour les types connus au moment de la compilation ou qui utilisent PassGenerics ) mais il sera disponible via les fonctions du module afin qu'il puisse être supprimé en secouant l'arbre s'il n'est pas invoqué.

Je transmets des objets sous forme de messages entre Fable et Akka.Net/F# sur le serveur. Parce que le type du message identifie le message, pouvoir inclure le type dans la sérialisation et pouvoir désérialiser quelque chose dans Fable sans savoir quel pourrait être le type, restera important pour moi. Après désérialisation d'un message sur le client, son type d'exécution :? (instanceof) est utilisé pour acheminer le message.
Ce serait bien de devoir choisir de conserver le typeinfo et le vrai type JS/runtime afin que cela continue de fonctionner. Cependant, la majorité des types n'en ont pas besoin, donc la suppression de la prolifération de [<Pojo>] serait la bienvenue.

Je viens d'atteindre la taille de 1 Mo pour le prod bundle... Maintenant, c'est OK pour l'internet haut débit, mais pas pour autre chose. Et encore une fois c'est en effet une grande taille. Donc, si quelque chose vient comme réduire la taille. Je suis partant.

Juste pour donner une idée. Où je ne peux même pas utiliser [<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})

Maintenant, ça va augmenter la taille comme n'importe quoi. Les fonctions statiques sont là pour utiliser lences - aether pour modifier les types d'enregistrement.

Et visiblement de nombreux opérateurs de pêche manquent pour la lisibilité...

@ kunjee17 Vous ne pouvez pas non plus utiliser de membre statique pour prendre en charge les lentilles. Vous pouvez les écrire comme :

type Model = {
  Id : int }
}

let modelIdLens = ...

// or

module ModelLens =

let id = ...

Et ainsi pouvoir utiliser les attributs [<Pojo>] .

Utilisez-vous le CDN dans votre application ou regroupez-vous tout comme votre code et vos bibliothèques ?

@MangelMaxime Je regroupe tout dès maintenant. Voici les bibliothèques que j'utilise actuellement

"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"

Je vais essayer ce que vous avez dit mais en mettant les choses dans le module au lieu du membre statique

Mise à jour1
@MangelMaxime pour le même code que j'ai partagé dans le commentaire ci-dessus. Le code généré fait 50 lignes de moins dans la réponse Fable et il n'y avait pas non plus d'inclusion si j'utilise des modules avec Pojo au lieu de fonctions statiques avec type.

Pour le code ci-dessus, voici le code généré

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);

Et voici le code mis à jour

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})

cela générera plus de code JavaScript

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;
}({});

Alors, quelle sera la bonne route à prendre. Est-ce que cette chose peut être par défaut dans la fable ? @alfonsogarciacaro

Mise à jour2

Je suis à mi-chemin de la modification de tous les modèles avec Pojo. C'est réduire le code; Mais semble si peu. Continuera à publier sur la mise à jour.

Je pense qu'il serait préférable de garder cette discussion dans un numéro distinct @kunjee17 essayez simplement de garder celui-ci une discussion :)

@MangelMaxime désolé de

@michaelsg Je pense que nous avons eu une discussion similaire lors de la sortie de Fable 0.7 ou 1.0. Pour prendre en charge la désérialisation avec les informations de type, nous avons ajouté une variable globale pour contenir un dictionnaire de types. C'est la seule variable globale dans fable-core et j'aimerais la supprimer pour la prochaine version, car je n'ai entendu parler de personne d'autre utilisant cette fonctionnalité.

Les génériques seront toujours disponibles, vous pouvez donc faire quelque chose comme ce qui suit pour _tag_ votre JSON avec le nom de type :

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

Ce code n'est pas très élégant (nous pouvons probablement l'améliorer en utilisant Active Patterns ou un dictionnaire de types) mais c'est un petit prix à payer pour améliorer le reste de votre code généré (et celui de tous les autres). Au fur et à mesure que les applications Fable se développent, nous remarquons que les offres groupées deviennent assez volumineuses, nous devons donc supprimer certaines informations d'exécution afin de rester compétitifs par rapport aux solutions alternatives (comme Reason).

Attention, inflate n'est actuellement pas récursif. Mais je suppose que nous pouvons corriger cela si nécessaire.

Deux autres remarques : les unions et les enregistrements seront des objets simples, vous pourrez donc les analyser uniquement avec l'API du navigateur natif JSON.parse . Les classes F# seront toujours traduites en classes JS, de sorte que les tests de type seront possibles lors de l'exécution.

@kunjee17 Oui, il vaut mieux déplacer la discussion vers Fulma ou ouvrir un nouveau numéro :) Veuillez vérifier la taille du bundle gzippé car ce serait la taille à télécharger si votre serveur gzipait les fichiers JS, ce qui devrait faire l'affaire ;) Fable 2.0 améliorera la situation, mais si votre offre se développe trop à un moment donné, vous devrez peut-être diviser votre application.

Il semble que l'émulation de classes avec des propriétés ne soit pas toujours plus rapide. Lorsque la fréquence des appels est élevée (appels lourds), les anciens objets javascript simples sont considérablement (~2x) plus rapides que les propriétés.

J'ai un codec de message (où le code typique ressemble à ce poids mouche : https://github.com/fable-compiler/Fable/issues/1352#issuecomment-385976678). J'ai commencé à porter mon code F# vers Fable2 car il a été signalé qu'il était plus rapide que Fable1. Étonnamment, ce n'est pas un cas avec un code d'appel lourd. Le code Fable2 passera beaucoup de temps à effectuer des opérations de propriété en plus du coût d'indirection supplémentaire (à la fois le profil Fable1 et le profil Fable2 liés). L'application n'est pas ouverte (mais j'aimerais éventuellement avoir une version simplifiée mais représentative à ouvrir en tant que cas d'utilisation de charge de travail - pour suivre les performances du compilateur).

Fable1 (où chaque ligne représente une opération d'encodage/décodage alternée de 1_000_000 répétitions en réutilisant le tampon d'encodage/décodage correspondant) :

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

Profil de nœud (caviardé) :
https://gist.github.com/zpodlovics/9d42207536e15efe042a766b1fbbfae2

Fable2 :

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

Profil de nœud (caviardé) :
https://gist.github.com/zpodlovics/a454ad7521945f367bc13b69a7230118

Environnement : Ubuntu 16.04 x86_64
Nœud : v10.9.0
Fable1 : 1.3.17
Fable2 : 2.0.0-beta-001

Ces résultats sont très intéressants @zpodlovics , merci pour le partage ! Donc, dans votre cas, Fable 2 s'exécute plus lentement que Fable 1. En fait, nous avons travaillé dans PR #1512 pour insérer des appels de propriétés (et en plus de cela, nous attendons également d'ajouter une optimisation F# qui intègre également des propriétés. Nous n'avons pas l'avons encore fusionné car nous n'avons pas vu de gain de performances (nous utilisons généralement le REPL lui-même pour le mesurer), mais peut-être que votre projet peut être un moyen très intéressant de tester cela.

Pour clarifier, #1512 introduit une dégradation des performances assez importante (jusqu'à 60%) sur certaines charges de travail comme REPL.

@alfonsogarciacaro Je crains que l'incorporation de la propriété n'aide pas Fable2 ici car il semble que la propriété elle-même introduira de nombreuses opérations et indirections supplémentaires (et supprimera le cache de données et d'instructions) au lieu d'un simple accès au champ d'objet : (https :/ /gist.github.com/zpodlovics/a454ad7521945f367bc13b69a7230118#file-fable2-profile-txt-L362). Et l'inlining n'est pas toujours une victoire - car vous devez généralement exécuter beaucoup plus de code - au lieu d'utiliser un appel réutilisant l'instruction chaude et le cache uop. Un petit code serré (par exemple : interprète K) pourrait être vraiment très rapide : (http://tech.marksblogg.com/billion-nyc-taxi-kdb.html).

Nouvelle fonctionnalité liée à la propriété du profil Fable2 :

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

Quelques surcharges de fonctionnalités liées aux propriétés du profil Fable2 :

   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*)

Quelques % par-ci et encore quelques % par-là et cette petite liste soudainement responsable de : ~22,6%

Merci pour les nouvelles données @zpodlovics. Pour être sûr de bien le comprendre, est-ce le changement dans le code JS généré qui cause le problème de performances ?

// 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

J'espérais que l'utilisation d'une fonction (en plus d'une meilleure compatibilité avec le secouage et la minification de l'arbre JS) aiderait l'environnement d'exécution JS à effectuer un envoi statique, mais j'avais peut-être tort. Cependant, dans les deux cas, costlyOperation est évalué chaque fois que la propriété est appelée. Nous ne pouvons pas changer cela car cela fait partie de la sémantique F#. Si vous devez mettre en cache la valeur, vous devez le faire explicitement ou utiliser une propriété implémentée automatiquement member val MyProperty = costlyOperation()

Dans tous les cas, je suis prêt à rattacher des propriétés au prototype si cela peut améliorer les performances. Si nous créons une branche avec ce changement, le testeriez-vous dans votre projet ?

Le succès de performance - selon mon profilage - vient de l'objet Fable1 -> changement de propriété Fable2. Il n'y a pas d'opération coûteuse dans le chemin de codage/décodage. Chaque appel n'est rien de plus qu'une lecture/écriture de type primitif (+ parfois lecture/écriture d'un tableau de quelques ( < 100 ) octets) + transformation minimale (généralement expansion/troncation de nombres entiers) si nécessaire. Cependant, il y a beaucoup de champs dans un encodeur/décodeur (pas seulement des types primitifs, mais des structures (y compris des structures imbriquées et des données de longueur variable) qui sont construites à partir de ces types primitifs). L'encodeur/décodeur alloué une fois (avec encodeur/décodeur imbriqué pour chaque champ/structure non primitif), de sorte que l'encodage/décodage du message pourrait être effectué sans allocation dans le chemin chaud.

Un décodeur/encodeur d'en-tête trivial avec deux champs :

Js généré par Fable1 :
https://gist.github.com/zpodlovics/96013570fe8130628004ee8ed05cbfb1

Js généré par Fable2 :
https://gist.github.com/zpodlovics/917dd235b1c726921d44ba16ade3271f

Flamegraphs (en utilisant les références précédentes - existe-t-il un meilleur moyen que celui-ci pour partager svg ?) :

Flamegraph Fable1 :
https://gist.github.com/zpodlovics/8b7a3fa3890388f1ad1e233e85275283

Flamegraph Fable2 :
https://gist.github.com/zpodlovics/96b4b4ff169e477d16328abe28138c94

L'opération de lecture/écriture de champ doit être évaluée à chaque fois comme prévu. Dans un environnement concurrent (tampons partagés js + workers), même la mise en cache du processeur local n'est pas autorisée, la lecture/écriture se fait à l'aide de la mémoire physique (opérations volatiles).

Merci pour votre soutien! Je suis heureux d'aider à améliorer Fable, alors veuillez créer la branche et je la testerai dans mon projet.

Mettre à jour:
"Cet article décrit certains principes fondamentaux communs à tous les moteurs JavaScript - et pas seulement V8, le moteur sur lequel les auteurs (Benedikt et Mathias) travaillent. En tant que développeur JavaScript, avoir une compréhension plus approfondie du fonctionnement des moteurs JavaScript vous aide à raisonner. les caractéristiques de performance de votre code."
https://mathiasbynens.be/notes/shapes-ics
https://mathiasbynens.be/notes/prototypes

@alfonsogarciacaro Sur la base de mes mises à jour précédentes (messages de Mathias Bynens + vidéos de conférence), il semble que l' --trace-ic , je le vérifierai plus tard.

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

vs

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

Cela vaut probablement aussi la peine de vérifier ceci:
https://medium.com/@bmeurer/surprising -polymorphism-in-react-applications-63015b50abc

@zpodlovics Je ne suis pas très habitué aux outils de profilage JS mais j'étais conscient qu'il vaut mieux éviter le polymorphisme pour aider le moteur JS à optimiser le code. Cependant, j'espérais que les fonctions autonomes pourraient être optimisées car elles sont utilisées avec le même type.

Dans tous les cas, j'ai créé la branche properties pour expérimenter l'attachement de getters et setters (sans index) au prototype. Pouvez-vous s'il vous plaît le vérifier et le tester? Vous pouvez trouver des instructions sur la façon de tester les versions locales ici : https://github.com/fable-compiler/Fable/tree/properties#using -your-local-build-in-your-projects

Peut-être que @ncave peut aussi

@alfonsogarciacaro Ma construction échoue avec une erreur de test 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)

Liste complète des erreurs :

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

Mise à jour : a commenté les tests, maintenant en combat avec https://github.com/fable-compiler/Fable/issues/1413

Désolé, je suis un idiot. J'ai oublié de reconstruire fable-core avant d'exécuter les tests. Laissez-moi régler ça.

J'attends toujours que CI se termine, mais j'espère que c'est corrigé maintenant :pray:

Les tests passent mais le REPL ne se construit pas :/ Quoi qu'il en soit, pouvez-vous s'il vous plaît essayer @zpodlovics ? Si cela fonctionne dans votre cas et montre une amélioration des performances, nous pouvons travailler dessus pour résoudre les problèmes restants.

@alfonsogarciacaro Merci ! J'ai réussi à le faire fonctionner (maintenant il est basé sur fable-core.2.0.0-beta-002). Bien que le code semble différent, les performances restent à peu près les mêmes. J'ai bien peur qu'il n'y ait pas d'autre moyen que de creuser plus profondément en utilisant les outils de profilage js.

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 Vos paramètres génériques / explication de l'interface ici (https://github.com/fable-compiler/Fable/issues/1547#issuecomment-417596116) m'a donné une idée à essayer sans aucune interface liée à la "magie" avec Fable2 :

Dans le codec, j'ai remplacé le type d'interface par un type d'implémentation concret, et alto :

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 vraiment cool @zpodlovics. Si j'ai bien lu, vous avez même une amélioration des performances ici.

Puis-je vous demander de partager un exemple de code avant/après lié à votre point :

sans aucune interface liée à la "magie" avec Fable2

Comme ça on pourra s'y référer plus tard, et peut-être que d'autres personnes pourront le trouver "truc" utile :)

@MangelMaxime Rien de vraiment, je viens de remplacer le type d'interface par un type concret (mais vous devez faire des choses plus complexes que cela pour mesurer correctement la surcharge d'appel).

[<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)

Intéressant, l'envoi dynamique dresse sa tête laide :)
Les SRTP à la rescousse !

Merci d'avoir étudié cela plus en détail @zpodlovics , donc la perte de performances provient du transtypage de l'interface (dans Fable 1, il n'y avait pas de transtypage car les membres de l'interface étaient directement attachés à l'objet) au lieu des propriétés autonomes. C'est quelque peu attendu, mais nous devons en informer les utilisateurs.

Une autre alternative pourrait être d'utiliser des expressions d'objet pour implémenter l'interface au lieu d'un type, celles-ci n'ont pas besoin de transtypage.

Est-ce un casting ou une répartition dynamique ? Il semble que la distinction serait importante à connaître.

@et1975 Cela ressemble plus à la création de formes et à la migration/transformation de formes - au moins sur la base du profilage /flamegraph.

Oui, nous n'avons pas quelque chose comme des tables virtuelles, mais Fable 2 transforme (en fait encapsule) les objets lorsqu'ils sont convertis en interfaces. Comme cette opération est implicite lors de la transmission d'un objet à une fonction acceptant l'interface, il se peut que cela se produise trop souvent, ce qui nuit aux performances. Peut-être pouvons-nous trouver un moyen de détecter ces cas et de trouver une solution de contournement ou d'informer l'utilisateur.

Serait-il possible de le faire comme une valeur paresseuse et calculé une seule fois ?

Super idée @Nhowka ! J'ai créé une branche interface-map qui ajoute une carte aux objets pour enregistrer les wrappers d'interface afin qu'ils n'aient pas besoin d'être générés plusieurs fois. @zpodlovics Si vous avez le temps, pouvez-vous s'il vous plaît consulter cette branche et

@alfonsogarciacaro Cela ressemble à peu près à la même chose :

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

Le Flamegraph a aussi un peu changé :
https://gist.github.com/zpodlovics/8990c388b390aef83ef357d607d8683c

Hmm, alors peut-être que le coût d'utilisation d'une carte l'emporte sur l'avantage d'avoir un cache. Eh bien, la chose importante est que nous avons identifié un goulot d'étranglement potentiel afin que nous puissions l'écrire dans la documentation et ensuite les utilisateurs peuvent essayer de changer les interfaces par types concrets comme dans votre cas.

@ncave Peut-être ferait une différence. J'ai réparé le bundle, mais le projet de banc a probablement besoin d'être réparé aussi.

@alfonsogarciacaro Il semble que toute indirection introduite intensifs . Cependant, la réécriture - à la main - de nombreux codes partagés existants et futurs (.NET + Fable) n'est pas vraiment une solution.

Les paramètres de type génériques / types flexibles devraient permettre ce type de spécialisation (à peu près de la même manière que .NET) :
https://github.com/fable-compiler/Fable/issues/1547#issuecomment -417607515

Mise à jour : la nouvelle branche attach-interfaces a maintenant à peu près les mêmes performances :

Pas d'interface (noms d'interface remplacés par le type d'implémentation concret) :

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

statistiques de performances :

 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

Interface (tapez TestInterface(instance : IFunc1) style) en utilisant la branche attach-interfaces :

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

état de perf :

 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 Pouvons-nous clore ce problème ?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

et1975 picture et1975  ·  3Commentaires

ncave picture ncave  ·  3Commentaires

MangelMaxime picture MangelMaxime  ·  3Commentaires

rommsen picture rommsen  ·  3Commentaires

jwosty picture jwosty  ·  3Commentaires