28 janv. 2024

[Dynamo += Python] fonctions Python VS fonctions .Net

 





Lors de la manipulation d'objets .NET, il est parfois nécessaire de choisir entre les fonctions BuiltIn Python et les méthodes d'extension LINQ. Cependant, la meilleure approche ne dépend pas toujours de la méthode à laquelle on pense initialement, surtout avec l'évolution récente de .NET.


#DynamoBIM #Revit  #Net #Python #IronPython #LINQ
if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )



Dans le cadre de cet article, nous allons effectuer un test comparatif  entre quelques méthodes BuiltIn Python et les méthodes d'extension LINQ avec Dynamo 3.1.0 (.NET 8). Il est important de noter que le choix optimal dépendra du contexte spécifique et des objectifs finaux. 



Dans notre test, nous avons évalué les performances des méthodes BuiltIn Python par rapport aux méthodes d'extension LINQ. Les résultats, que nous détaillerons ci-dessous, peuvent fournir des informations précieuses sur l'efficacité de chaque approche.

Tout dépendra de votre contexte et du but final, mais ce n'est pas forcément à la méthode à laquelle on pense qui est forcément la meilleure.
 

  • Dans certains cas, l'utilisation d'Énumérables et de méthodes d'extension LINQ peut s'avérer avantageuse.

  • Lorsque le nombre d'objets est très élevé, la conversion d'un Énumérable en Net Collection avec la méthode ToList()  est à éviter si possible (idem avec la méthode ToElements() d'un objet FilteredElementCollector de l'API Revit)

  • Parfois, les méthodes Python auront de meilleures performances (exemple min() et max())

  • L'utilisation du module python operator, présente des avantages.

  • Dans le cas de traitement de données ou de géométries, il pourrait être plus utile d'utiliser les bibliothèques Python numpy et/ou pandas 


Note:

En anticipant le ciblage d'IronPython3 pour Net 8, nous pouvons envisager des améliorations potentielles en termes de performances avec LINQ.


Voici un exemple de code Python pour effectuer des Tests


#Load the Python Standard and DesignScript Libraries
import sys
print(sys.implementation.name + " : " + sys.version)
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

net_clr_runtime = "Net Runtime : " + str(System.Environment.Version)

ipy3_assembly = System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(lambda a : a.GetName().Name == 'IronPython.Modules')
assembly_ipy3_Directory = System.IO.Path.GetDirectoryName( ipy3_assembly.Location )
parent_ipy3_Directory = System.IO.Directory.GetParent(assembly_ipy3_Directory).FullName
print(parent_ipy3_Directory)
sys.path.append(parent_ipy3_Directory + "\\lib")

import datetime
import operator

def decotimeit(n_try):
    def timeit(func):
        def timeit_wrapper(*args, **kwargs):
            a = datetime.datetime.now()
            for i in range(n_try):
                result = func(*args, **kwargs)
            b = datetime.datetime.now() - a
            mean_time = b.microseconds / n_try
            if "py" in func.__name__:
                print("Python Function {} with {} points, average time {} microseconds ({} tests) | {}".format(func.__name__, len(args[0]), mean_time, n_try, net_clr_runtime))
                
            else:
                print("LINQ Function {} with {} points, average time {} microseconds ({} tests) | {}".format(func.__name__, len(args[0]), mean_time, n_try, net_clr_runtime))
            return result
        return timeit_wrapper
    return timeit

@decotimeit(n_try = 20)
def Test_AddVector_NetLinq(lst_pts):
    return lst_pts.Select(lambda pt : pt.Add(Vector.ByCoordinates(1,1,1))).AsEnumerable()
    
@decotimeit(n_try = 20)
def Test_AddVector_py(lst_pts):
    return [pt.Add(Vector.ByCoordinates(1,1,1)) for pt in lst_pts]
    
@decotimeit(n_try = 20)
def Test_OrderBy_NetLinq(lst_pts):
    return lst_pts.OrderBy(lambda pt : pt.X)
    
@decotimeit(n_try = 20)
def Test_OrderBy_py(lst_pts):
    lst_pts.sort(key = lambda pt : pt.X)
    return lst_pts
    
@decotimeit(n_try = 20)
def Test_OrderBy_attrgetter_py(lst_pts):
    keyfun= operator.attrgetter("X")
    lst_pts.sort(key = keyfun)
    return lst_pts
    
@decotimeit(n_try = 20)
def Test_MinBy_NetLinq(lst_pts):
    return lst_pts.MinBy(lambda pt : (pt.Z))
    
@decotimeit(n_try = 20)
def Test_MinBy_py(lst_pts):
    return min(lst_pts, key = lambda pt : (pt.Z))
    
@decotimeit(n_try = 20)
def Test_MinZ_NetLinq(lst_pts):
    return lst_pts.MinBy(lambda pt : pt.Z).Z
    
@decotimeit(n_try = 20)
def Test_MinZ_py(lst_pts):
    return min(lst_pts, key = lambda pt : (pt.Z)).Z
    
lst_pts = IN[0]
print("-" * 10)
result_add_linq = Test_AddVector_NetLinq(lst_pts)
result_add_py = Test_AddVector_py(lst_pts)
print("-" * 10)
result_sort_linq = Test_OrderBy_NetLinq(lst_pts)
result_sort_py = Test_OrderBy_py(lst_pts)
result_sort_attrgetter_py = Test_OrderBy_attrgetter_py(lst_pts)
print("-" * 10)
result_minZ_linq = Test_MinZ_NetLinq(lst_pts)
result_minZ_py = Test_MinZ_py(lst_pts)

OUT =   [result_add_linq.ElementAt(i) for i in range(5)], \
        result_add_py[:5], \
        [result_sort_linq.ElementAt(i) for i in range(5)], \
        result_sort_py[:5], \
        result_minZ_linq, \
        result_minZ_py

Pour ceux qui auraient des interrogations sur la fonction décoratrice decotimeit, vous pouvez consulter mon précédent article à ce sujet


Résultats

| Test                                       | Méthode                         | Nbr Points Testés | Temps Moyen (Microsecondes) | Nombre de Tests  | .NET Runtime |
|--------------------------------------------|---------------------------------|-------------------|-----------------------------|------------------|--------------|
| LINQ Function Test_AddVector_NetLinq       | Net LINQ                        | 300 000           | 50.0                        | 20               | 8.0.0        |
| Python Function Test_AddVector_py          | BuiltIn Python                  | 300 000           | 10550.0                     | 20               | 8.0.0        |
|                                            |                                 |                   |                             |                  |              |
| LINQ Function Test_OrderBy_NetLinq         | Net LINQ                        | 300 000           | 50.0                        | 20               | 8.0.0        |
| Python Function Test_OrderBy_py            | BuiltIn Python                  | 300 000           | 12200.0                     | 20               | 8.0.0        |
| Python Function Test_OrderBy_attrgetter_py | Python Operator                 | 300 000           | 9200.0                      | 20               | 8.0.0        |
|                                            |                                 |                   |                             |                  |              |
| LINQ Function Test_MinZ_NetLinq            | Net LINQ                        | 300 000           | 18450.0                     | 20               | 8.0.0        |
| Python Function Test_MinZ_py               | BuiltIn Python                  | 300 000           | 8800.0                      | 20               | 8.0.0        |

Ici les tests ont été faits sous IronPython3, je mettrais l'article ultérieurement avec CPython3/PythonNet s'il y a des gains significatifs.  





"Ce n'est pas le plus fort de l'espèce qui survit, ni le plus intelligent. C'est celui qui sait le mieux s'adapter au changement."

Charles Darwin

0 commentaires:

Enregistrer un commentaire