Question

J'ai une signature de méthode C ++ qui ressemble à ceci:

    static extern void ImageProcessing(
        [MarshalAs(UnmanagedType.LPArray)]ushort[] inImage,
        [MarshalAs(UnmanagedType.LPArray)]ushort[] outImage,
        int inYSize, int inXSize);

J'ai intégré la fonction à des méthodes de minutage, internes et externes. En interne, la fonction tourne à 0,24 s. En externe, la fonction s'exécute en 2.8s, soit environ 12 fois plus lentement. Que se passe-t-il? Est-ce que le ralentissement me ralentit autant? Si c'est le cas, comment puis-je contourner cela? Devrais-je aller à un code non sécurisé et utiliser des pointeurs ou quelque chose? Je suis un peu déconcerté de savoir d'où vient le coût du temps supplémentaire.

Était-ce utile?

La solution 3

La réponse est malheureusement beaucoup plus banale que ces suggestions, bien qu’elles soient utiles. Fondamentalement, je me suis trompé avec la façon dont je faisais le temps.

Le code de synchronisation que j'utilisais était le suivant:

Ipp32s timer;
ippGetCpuFreqMhz(&timer);
Ipp64u globalStart = ippGetCpuClocks();
globalStart = ippGetCpuClocks() *2 - globalStart; //use this method to get rid of the overhead of getting clock ticks

      //do some stuff

Ipp64u globalEnd = ippGetCpuClocks(); 
globalEnd = ippGetCpuClocks() *2 - globalEnd;
std::cout << "total runtime: " << ((Ipp64f)globalEnd - (Ipp64f)globalStart)/((Ipp64f)timer *1000000.0f) << " seconds" << std::endl;

Ce code est spécifique au compilateur intel et est conçu pour donner des mesures de temps extrêmement précises. Malheureusement, cette précision extrême représente un coût d'environ 2,5 secondes par cycle. Supprimer le code de synchronisation a supprimé cette contrainte de temps.

Il semble toujours y avoir un retard de l'exécution, cependant-- le code rapporterait 0,24 s avec ce code de temporisation, et signale maintenant une temporisation d'environ 0,35 s, ce qui signifie un coût de vitesse d'environ 50%.

Changer le code en ceci:

  static extern void ImageProcessing(
     IntPtr inImage, //[MarshalAs(UnmanagedType.LPArray)]ushort[] inImage,
     IntPtr outImage, //[MarshalAs(UnmanagedType.LPArray)]ushort[] outImage,
     int inYSize, int inXSize);

et appelé comme:

        unsafe {
            fixed (ushort* inImagePtr = theInputImage.DataArray){
                fixed (ushort* outImagePtr = theResult){
                    ImageProcessing((IntPtr)inImagePtr,//theInputImage.DataArray,
                        (IntPtr)outImagePtr,//theResult,
                        ysize,
                        xsize);
                }
            }
        }

ramène le temps d'exécution à 0,3 s (moyenne de trois exécutions). Encore trop lent à mon goût, mais une amélioration de la vitesse 10 fois est certainement acceptable pour mon patron.

Autres conseils

Consultez cet article . Bien que l'accent soit mis sur le Compact Framework, les principes généraux s'appliquent également au poste de travail. Une citation pertinente de la section d’analyse est la suivante:

  

L'appel géré n'appelle pas directement la méthode native. Au lieu de cela, il appelle une méthode de remplacement JITted qui doit exécuter certaines routines de traitement, telles que des appels, pour déterminer l'état de la préemption du GC (pour déterminer si un GC est en attente et si nous devons attendre). Il est également possible que du code de marshalling soit également inséré dans le stub. Tout cela prend du temps.

Modifier : cet article mérite également d'être lu. sur perf du code JITted - encore une fois, spécifique aux FC, mais toujours pertinent. Il existe également un article sur la profondeur de la pile d'appels et son impact sur la performance , bien que celui-ci soit probablement spécifique aux FC (non testé sur le bureau).

Avez-vous essayé de basculer les deux paramètres de tableau sur IntPtr? PInvoke est le plus rapide absolu lorsque tous les types de la signature de marshalling sont blittables. Cela signifie que Pinvoke se résume à une simple mémoire pour obtenir les données dans les deux sens.

Dans mon équipe, nous avons trouvé le moyen le plus performant de gérer notre couche PInvoke:

  1. Garantissez que tout ce qui est Marshall'd est acceptable
  2. Payez le prix aux types de marshal manuellement, tels que les tableaux, en manipulant une classe IntPtr selon les besoins. Ceci est très trivial car nous avons beaucoup de méthodes / classes de wrapper.

Comme pour tout "cela sera plus rapide" répondre, vous aurez besoin de profiler ceci est votre propre base de code. Nous ne sommes parvenus à cette solution qu'après plusieurs méthodes considérées et profilées.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top