Test sur manipulation de pixels

MalaMala Membre, Modérateur
juin 2014 modifié dans Objective-C, Swift, C, C++ #1

Le post "Swift et types de base" a un peu largement glissé sur le questionnement des perfs qu'on peut attendre avec Swift. Dans le domaine qui m'intéresse par rapport à  mes logiciels de traitement d'image, la manipulation de buffers de pixel est un sujet sensible. Je me suis donc fait la main sur un cas d'école: ajustement des seuils min et max d'une image.


Réponses

  • MalaMala Membre, Modérateur

    Histoire de suivre un peu l'évolution de Swift, je viens de mettre au goût du jour mon projet de test pour Xcode 6...


  • FKDEVFKDEV Membre
    février 2015 modifié #3
    Ce qui semble prendre du temps ce sont les conversion de type.

    UInt8 vers float et float vers UInt8, (et Float vers int lors du test <255).


    Pour éviter les conversions superflues, je préciserais le type de newValue.

    Et, comme type, j'essaierais les types UInt8 et Int.

    La conversion float vers Int est sans doute moins violente que Float vers UInt8.


    EDIT: en fait j'éviterais carrément le passage par le type Float.
  • Pas mal d'optimisations/stabilisation dans la nouvelle version de Swift 1.2, peut être qu'il vaut mieux refaire les tests avec la dernière version. 


  • DrakenDraken Membre
    février 2015 modifié #5


     


    Si quelqu'un à  une idée pour optimiser les choses?




    Télécharger Xcode 6.3 Beta et recommencer le test !


    EDIT : Grilled par Samir ..


  • MalaMala Membre, Modérateur

    Oui, je viens de voir ça pour Swift 1.2. On va voir ce que ça donne.


  • MalaMala Membre, Modérateur
    février 2015 modifié #7


    EDIT: en fait j'éviterais carrément le passage par le type Float.




    Je viens de tenter vite fait (pas encore sous Xcode 6.3). C'est un peu mieux mais c'est encore loin d'être ça avec 14 Fps.


     


    Le soucis c'est que les float sont très utiles en traitement d'image. C'est typiquement le cas de figure où le passage en entier peu générer des effets de crénelage sur des dégradés subtiles.


  • AliGatorAliGator Membre, Modérateur
    Et en utilisant quand même des types flottants, mais directement les types C / ObJC et pas les types Swift ? Genre CGFloat et pas Float, etc ?
  • MalaMala Membre, Modérateur

    On peux utiliser de vrais types C/Obj-C en Swift maintenant? Si c'est le cas tu m'intéresses car c'est justement là  le fond du problème pour accéder aux pixels de manière efficace. 


     


    Moi j'en suis resté à  ça...


    https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html


     


    Téléchargement d'Xcode 6.3 en cours...


  • AliGatorAliGator Membre, Modérateur
    Bien sûr. On peut bien utiliser NSString et pas juste String si on veut, CGFloat au lieu de Float, etc...

    C'est pas conseillé si tu veux faire du pur Swift bien sûr, mais si tu as besoin d'inter-opérabilité entre Swift et ObjC c'est tout à  fait possible (depuis la toute première version de Swift, pas besoin d'attendre la fin du download de Xcode 6.3 ^^)
  • MalaMala Membre, Modérateur

    Je pense qu'on ne se comprend pas. Pourquoi me parles-tu de NSString et de string? Je te parles d'accéder à  des types C de base et non à  des objets.


     


    CGFloat en C, c'est juste un type C redéfinissant un nombre réel en double précision sur architecture 64bits et restant en simple précision sur architecture 32bits. En Swift ça devient un objet à  part entière avec toute la lourdeur que cela entraine.


     


    Hors de ce que je vois en l'état, tous les accès à  des données brutes d'octets (getBytes de NSData aussi par exemple) passent par des conversions avec Swift et c'est extrêmement pénalisant.


  • AliGatorAliGator Membre, Modérateur

    Je pense qu'on ne se comprend pas. Pourquoi me parles-tu de NSString et de string? Je te parles d'accéder à  des types C de base et non à  des objets.
     
    CGFloat en C, c'est juste un type C redéfinissant un nombre réel en double précision sur architecture 64bits et restant en simple précision sur architecture 32bits. En Swift ça devient un objet à  part entière avec toute la lourdeur que cela entraine.

    Je te parle de NSString et de String juste pour dire que c'est le cas pour tous les types. Pour te donner tout plein d'exemples, pas que CGFloat, mais aussi tout plein d'autres types.
    On peut utiliser quasi tous les types de base qu'on a en ObjC " que ce soit des types C comme float/double/uint8_t/... ou des types ObjC comme NSString " depuis Swift. On peut utiliser CGFloat aussi depuis Swift, et ce sera alors un CGFloat au même sens qu'en C/ObjC, c'est à  dire un float en 32 bits et double en 64 bits, tout pareil.
  • MalaMala Membre, Modérateur


    et ce sera alors un CGFloat au même sens qu'en C/ObjC, c'est à  dire un float en 32 bits et double en 64 bits, tout pareil.




    Non justement pas tout pareil. En Swift CGFloat est un objet et en C c'est juste un espace mémoire.


     


    En Swift...



    struct CGFloat {
     
        /// The native type used to store the CGFloat, which is Float on
        /// 32-bit architectures and Double on 64-bit architectures.
        typealias NativeType = Double
        init()
        init(_ value: Float)
        init(_ value: Double)
     
        /// The native value.
        var native: NativeType
    }

    En C/Obj-C



    #if defined(__LP64__) && __LP64__
    # define CGFLOAT_TYPE double
    # define CGFLOAT_IS_DOUBLE 1
    # define CGFLOAT_MIN DBL_MIN
    # define CGFLOAT_MAX DBL_MAX
    #else /* !defined(__LP64__) || !__LP64__ */
    # define CGFLOAT_TYPE float
    # define CGFLOAT_IS_DOUBLE 0
    # define CGFLOAT_MIN FLT_MIN
    # define CGFLOAT_MAX FLT_MAX
    #endif /* !defined(__LP64__) || !__LP64__ */
     
    typedef CGFLOAT_TYPE CGFloat;

    S'ils ont fonctionnellement le même rôle, c'est juste le jour et la nuit en terme d'accès.

  • PyrohPyroh Membre
    février 2015 modifié #14

    @Mala: Ne te fie pas aux pseudo header que tu as avec cmd+click dans Xcode. Ils sont générés automatiquement pour des raisons pratiques et pour dire au compilo comment appréhender le code. Les implémentations qui sont derrière restent écrites en C++. Cocoa et Fondation n'ont pas bougé d'un iota vis-à -vis de Swift (source: lu de Chris Lattner sur le dev-forum, mais pour retrouver ça...)


     


    En changeant tous les Double en CGFloat et en modifiant un peu la fonction redraw j'ai ~35 FPS sur un MBP 13" mid 2010 (une machine de guerre quoi...). Le tout en 1.2 -Ofast .



    func redraw(minValue min:CGFloat, maxValue max:CGFloat) {
    // Get bitmap buffers
    let pixPtrOri:UnsafePointer<UInt8> = UnsafePointer<UInt8>(self.bitmapImageRepOri.bitmapData)
    let pixPtrCpy:UnsafeMutablePointer<UInt8> = self.bitmapImageRepCpy.bitmapData

    let bufferLength = self.bitmapImageRepOri.pixelsHigh*bitmapImageRepOri.pixelsWide

    var pix = 0;
    while pix < bufferLength {
    // New value = (value - min)*255/(max-min)
    let val = CGFloat(pixPtrOri[pix])
    var newValue = (val - min) * 255.0 / (max - min)

    // Clamp value
    newValue = newValue < 0 ? 0 : newValue > 255 ? 255 : newValue

    // Update destination
    pixPtrCpy[pix] = uint_fast8_t(newValue)

    // Next pixel
    pix++
    }
    }

    Niveau utilisation ça donne ça :


  • MalaMala Membre, Modérateur
  • C'est l'occasion de passer à  Yosemite, non ?

  • MalaMala Membre, Modérateur


    C'est l'occasion de passer à  Yosemite, non ?




    J'évite d'aller trop vite sur ma config de prod. Je viens de m'installer une partition pour les tests.

  • MalaMala Membre, Modérateur


    @Mala: Ne te fie pas aux pseudo header que tu as avec cmd+click dans Xcode. Ils sont générés automatiquement pour des raisons pratiques et pour dire au compilo comment appréhender le code. Les implémentations qui sont derrière restent écrites en C++. Cocoa et Fondation n'ont pas bougé d'un iota vis-à -vis de Swift (source: lu de Chris Lattner sur le dev-forum, mais pour retrouver ça...)




    Ca j'entends bien que c'est du simili bridging automatisé pour les classes. Cela ne me choque pas et c'est même logique. Mais dès qu'on touche aux types de bases ce n'est plus la même.


     


    Bon sinon je viens de tester sous Yosemite et dernière Beta 6.3 d'Xcode:


    Float: 14,34 Fps


    CGFloat: 12,3 Fps (logique on perd en perfs en double)


     


    Donc c'est un peu mieux. On revient à  peu près aux perfs de l'idée de FKDEV de passer en Int (voir un chouilla mieux) mais pour l'instant c'est toujours une belle branlée d'un facteur 20x en faveur du code C...

  • MalaMala Membre, Modérateur


    En changeant tous les Double en CGFloat et en modifiant un peu la fonction redraw j'ai ~35 FPS sur un MBP 13" mid 2010 (une machine de guerre quoi...). Le tout en 1.2 -Ofast .



    func redraw(minValue min:CGFloat, maxValue max:CGFloat) {
    // Get bitmap buffers
    let pixPtrOri:UnsafePointer<UInt8> = UnsafePointer<UInt8>(self.bitmapImageRepOri.bitmapData)
    let pixPtrCpy:UnsafeMutablePointer<UInt8> = self.bitmapImageRepCpy.bitmapData

    let bufferLength = self.bitmapImageRepOri.pixelsHigh*bitmapImageRepOri.pixelsWide

    var pix = 0;
    while pix < bufferLength {
    // New value = (value - min)*255/(max-min)
    let val = CGFloat(pixPtrOri[pix])
    var newValue = (val - min) * 255.0 / (max - min)

    // Clamp value
    newValue = newValue < 0 ? 0 : newValue > 255 ? 255 : newValue

    // Update destination
    pixPtrCpy[pix] = uint_fast8_t(newValue)

    // Next pixel
    pix++
    }
    }

    Niveau utilisation ça donne ça :


    attachicon.gifScreen Shot 2015-02-10 at 15.42.24.png




     


    Quelques observations en testant ton code:


    - Le projet n'est pas en double mais bien en float. On utilise d'ailleurs très rarement des double en traitement d'image car on perd en perfs pour un gain en précision qui ne se justifie plus à  ce stade.


    - Ta modification de mon clamping en factorisant en opérateur ternaire fait perdre presque 1Fps chez moi. ;)


    - uint_fast8_t() semble équivalent en performance à  CUnsignedChar() chez moi.


  • Quelques observations en testant ton code:

    - Le projet n'est pas en double mais bien en float. On utilise d'ailleurs très rarement des double en traitement d'image car on perd en perfs pour un gain en précision qui ne se justifie plus à  ce stade.

    - Ta modification de mon clamping en factorisant en opérateur ternaire fait perdre presque 1Fps chez moi. ;)

    - uint_fast8_t() semble équivalent en performance à  CUnsignedChar() chez moi.




    Oui effectivement, maintenant est-ce que ça a une influence particulière sur un os et un cpu 64bit ? (je me pose réellement la question, hein)


     


    Le clamping fait comme ça me fait gagner 1 fps chez moi, ça doit dépendre du proc (j'ai un Core2Duo @ 2.4Ghz). Et instruments me donnait moins de % d'utilisation pour cette ligne en particulier que la somme de l'utilisation des boucles if.


     


    uint_fast8_t est strictement identique à  CUnsignedChar et UInt8 (typealias)


  • MalaMala Membre, Modérateur


    Oui effectivement, maintenant est-ce que ça a une influence particulière sur un os et un cpu 64bit ? (je me pose réellement la question, hein)




    D'expérience c'est de l'ordre de 6 à  10% de pertes sur mes algos de traitement de signal. Ce qu'Apple a bien oublié de dire en nous vendant le passage au 64bits c'est que le gain n'était réel que pour des algos déjà  entièrement en double précision. Hors d'une part, peu de calculs justifient une telle précision et d'autre part les mêmes algos en simple précision sont toujours plus performant...


     


     


    L'usage de CGFloat est vraiment un non sens selon moi de par son ambiguà¯té 32/64bits. 

  • Vous n'avez pas reparlé de la grosse différence entre le premier test swift et les tests suivants avec Xcode 6, qui sont beaucoup plus lents. N'est-ce pas étrange ? Cela voudrait-il dire que le nouveau swift est moins abouti que le précédent ?


     


     


    Apple nous a vendu swift comme beaucoup plus rapide. D'après les tests, il semble que ce soit faux. Si on considère que swift est une langage plus haut niveau que objective-c (tout est objet, si j'ai bien compris), c'est logique que ce soit moins performant.


     


    Finalement, ce qu'on doit attendre de swift ce ne sont pas les perfs mais le confort dans le code, non ?


  • Absolument !
  • MalaMala Membre, Modérateur
    février 2015 modifié #24

    Plus que le confort, je dirais la sécurité. Mais elle a un prix. En même temps, est-ce illogique que dans les faits Swift perde en performances à  mesure qu'il se sécurise? Après nul doute, qu'ils font les benchs qu'ils veulent comme toujours. Moi je me contente de cas concrets qui me concernent. Je ne suis sans doute pas représentatif de la masse.


     


    Il ne faut pas non plus oublier qu'Apple compare Swift à  l'Obj-C et non au C... Vous avez dit hypocrisie de commercial? Ah non c'est moi qui pense trop fort pardon.  ;D


  • Maintenant la question à  se poser c'est si il faut utiliser un langage haut niveau pour des opérations bas niveau. Pas sûr.


    J'ai commencé à  m'intéresser aux parser, vu les premiers tests swift est out.


    Je coderai le lexer/parser en C. Le type-safety coûte trop cher quand on a un texte de 100000 caractères unicode.


  • AliGatorAliGator Membre, Modérateur

    Apple nous a vendu swift comme beaucoup plus rapide. D'après les tests, il semble que ce soit faux. Si on considère que swift est une langage plus haut niveau que objective-c (tout est objet, si j'ai bien compris), c'est logique que ce soit moins performant.
     
    Finalement, ce qu'on doit attendre de swift ce ne sont pas les perfs mais le confort dans le code, non ?

    Non, Swift est plus performant quand tu ne fais que du code Swift. Et pour plusieurs raisons, mais une des principales étant que c'est un langage à  résolution statique.
    - Une fois que tu as compilé ton code Swift, tous les symboles sont déjà  résolus.
    - Alors qu'en Objective-C, c'est hautement dynamique. Ce ne sont pas des appels de fonctions, mais des envois de message. Avec une indirection de dispatch-table supplémentaire, une résolution des messages dynamique. Ca offre des possibilités en + (on peut imaginer construire dynamiquement une NSString contenant le nom d'une méthode, la convertir en @selector, et appeler ce dernier, par exemple), mais de la sécurité ET de la performance en moins.

    Là  où Swift ajoute de la lenteur, c'est quand il n'est pas tout seul, mais qu'il est utilisé en corrélation avec Objective-C. Dans ce cas, non seulement il y a perte de performances à  cause du bridging et du passage d'un monde à  l'autre, mais en plus aussi une perte à  cause du passage par le monde dynamique en injectant de l'Objective-C.

    Le jour où Objective-C disparaà®tra et qu'on fera des applications Swift qui n'ont plus du tout de code dynamique (type implémentation ObjC) sous le capot, mais que des appels de fonctions directs et plus d'envoi de messages, la différence sera alors bien + flagrante.
  • MalaMala Membre, Modérateur


    Le jour où Objective-C disparaà®tra et qu'on fera des applications Swift qui n'ont plus du tout de code dynamique (type implémentation ObjC) sous le capot, mais que des appels de fonctions directs et plus d'envoi de messages, la différence sera alors bien + flagrante.




    Sauf que pour l'instant Swift ne sait toujours pas gérer tout seul un simple tableau d'octets de manière efficace. C'est une sacrée limitation dans bien des domaines. Espérons qu'Apple ait une vraie solution dans les cartons mais dans l'immédiat on doit faire avec ce qu'on a.


     


    Du coup, je continue un peu mes investigations pour découvrir les possibilités du language. Swift permettant de faire du bridging de manière relativement transparente (ça c'est un sacré confort) entre Swift et Obj-C, j'ai décidé de tenter le coup pour voir.


     


    D'abord avec une catégorie Obj-C appelée depuis le code Swift...


Connectez-vous ou Inscrivez-vous pour répondre.