La copia profunda () es extremadamente lenta.

Tengo un estado de juego en Python con aproximadamente 1000 objetos (sistemas planetarios + estrellas + planetas), y necesito copiarlo y aplicarle un montón de transformaciones cuando se lo solicite. Sin embargo, en aproximadamente 1 solicitud / segundo, esto ocupa el 24.63% de mi tiempo de ejecución . ¿Cómo puedo hacerlo ir rápido? Tenga en cuenta que copiar menos no es una opción, ya que las transformaciones tocan casi todo.

EDIT : se redujo a 8% con una implementación juiciosa de __deepcopy__ en las cosas. Aún así, no es lo suficientemente bueno. (Lo suficientemente bueno es 1% o menos, planeo lanzar muchas más cosas a esto.) timeit dice 41.8ms por deepcopy() .

En realidad, la copia profunda es muy lenta. Pero podemos usar json, ujson o cPickle. podemos usar json / cPickle para volcar un objeto y cargarlo más tarde. Esta es mi prueba:

 Total time: 3.46068 s File: test_deepcopy.py Function: test at line 15 Line # Hits Time Per Hit % Time Line Contents ============================================================== 15 @profile 16 def test(): 17 100 957585 9575.9 27.7 b = deepcopy(a) 18 100 862 8.6 0.0 c = copy(a) 19 100 42295 422.9 1.2 d = ujson.loads(ujson.dumps(a)) 20 100 85040 850.4 2.5 e = json.loads(json.dumps(a)) 21 100 2323465 23234.7 67.1 f = pickle.loads(pickle.dumps(a, -1)) 22 100 51434 514.3 1.5 g = cPickle.loads(cPickle.dumps(a, -1)) 

como podemos ver, json / ujson / cPickle es más rápido que deepcopy, pero pickle …

Si crea su propia clase para contener estos objetos, puede crear sus propios métodos que funcionen con copia y copia profunda. http://www.rafekettler.com/magicmethods.html#copying (enlace roto)

Nuevo enlace para un repository de github https://github.com/RafeKettler/magicmethods

 class MyClass(): def __copy__(self): copy_object = MyClass() return copy_object def __deepcopy__(self, memodict={}): copy_object = MyClass() copy_object.value = self.value return copy_object if __name__ == "__main__": my_inst = MyClass() print(copy.deepcopy(my_inst)) 

Aquí hay una descripción similar del enlace roto anterior.

Proceso de copiar

A veces, especialmente cuando se trata de objetos mutables, desea poder copiar un objeto y realizar cambios sin afectar lo que copió. Aquí es donde entra en juego la copia de Python. Sin embargo (afortunadamente), los módulos de Python no son sensibles, por lo que no tenemos que preocuparnos por un levantamiento de un robot basado en Linux, pero sí tenemos que decirle a Python cómo copiar las cosas de manera eficiente.

__copy__(self)

Define el comportamiento de copy.copy () para instancias de su clase. copy.copy () devuelve una copia superficial de su objeto; esto significa que, si bien la instancia en sí misma es una nueva instancia, se hace referencia a todos sus datos, es decir, el objeto en sí se copia, pero sus datos siguen siendo referenciados ( y por lo tanto, los cambios a los datos en una copia superficial pueden causar cambios en el original).

__deepcopy__(self, memodict={})

Define el comportamiento de copy.deepcopy () para instancias de su clase. copy.deepcopy () devuelve una copia profunda de su objeto, el objeto y sus datos se copian. memodict es un caché de objetos previamente copiados: esto optimiza la copia y evita la recursión infinita cuando se copian estructuras de datos recursivas. Cuando desee copiar en profundidad un atributo individual, llame a copy.deepcopy () en ese atributo con memodict como el primer argumento. ¿Cuáles son algunos casos de uso para estos métodos mágicos? Como siempre, en cualquier caso en el que necesite un control más preciso que el que le proporciona el comportamiento predeterminado. Por ejemplo, si está intentando copiar un objeto que almacena un caché como un diccionario (que podría ser grande), es posible que no tenga sentido copiar el caché también, si el caché se puede compartir en la memoria entre instancias, entonces debería ser.

Hice un experimento rápido comparando deepcopy / json / ujson para varios casos y mis resultados contradicen los de @ cherish en ciertos casos, publicando el pequeño experimento aquí:

 import ujson import timeit import json import random import string import copy import ujson import sys def random_string(N): return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N)) def random_json(width=5, height=5, levels=1): dct = {} lst = [random_string(4) for i in range(width)] lst2 = [random.randint(0, 10000) for i in range(width)] lst3 = [bool(random.randint(0, 1)) for i in range(width)] for j in range(height): dct[str(j)] = lst dct[str(width+j)] = lst2 dct[str(2*width+j)] = lst3 for i in range(levels): new_dct = {} for j in range(height): new_dct[str(j)] = dct dct = json.loads(json.dumps(new_dct)) return new_dct if __name__ == "__main__": print(sys.version) levels = 3 for i in range(15): dataset = random_json(i, i, levels) print("Comparing deepcopy/ujson/json using random dataset({},{},{}), length {}".format(i,i,levels, len(json.dumps(dataset)))) print(timeit.timeit('copy.deepcopy(dataset)', setup='from __main__ import copy, dataset', number=10)) print(timeit.timeit('ujson.loads(ujson.dumps(dataset))', setup='from __main__ import ujson, dataset', number=10)) print(timeit.timeit('json.loads(json.dumps(dataset))', setup='from __main__ import json, dataset', number=10)) print() 

Y los resultados serían:

 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)] Comparing deepcopy/ujson/json using random dataset(0,0,3), length 2 2.6842977659931844e-05 0.00012039864979822371 7.776568527950847e-05 Comparing deepcopy/ujson/json using random dataset(1,1,3), length 63 0.0002731667726569534 3.552747043226263e-05 0.00012987264191349377 Comparing deepcopy/ujson/json using random dataset(2,2,3), length 1106 0.0011858280130946362 0.00034974820892205325 0.0007093651596308467 Comparing deepcopy/ujson/json using random dataset(3,3,3), length 6834 0.0042218477363672215 0.0021178319874343293 0.003378267688436718 Comparing deepcopy/ujson/json using random dataset(4,4,3), length 26572 0.011379054029782284 0.006288757016181971 0.009920059244030693 Comparing deepcopy/ujson/json using random dataset(5,5,3), length 79210 0.028879491215043435 0.027906433274870912 0.029595961868760734 Comparing deepcopy/ujson/json using random dataset(6,6,3), length 183678 0.047142979515255284 0.04682125853300759 0.06791747047568517 Comparing deepcopy/ujson/json using random dataset(7,7,3), length 395528 0.08239215142913198 0.09871347134571351 0.15347433002098887 Comparing deepcopy/ujson/json using random dataset(8,8,3), length 764920 0.1351954464835896 0.19448842613700734 0.3020533693660834 Comparing deepcopy/ujson/json using random dataset(9,9,3), length 1356570 0.24560258734724671 0.44074906118659407 0.5705849913806413 Comparing deepcopy/ujson/json using random dataset(10,10,3), length 2287770 0.3237815755327835 0.61104051671153 0.8698565598118777 Comparing deepcopy/ujson/json using random dataset(11,11,3), length 3598750 0.4958284828467452 0.9472223636741877 1.2514314609961668 Comparing deepcopy/ujson/json using random dataset(12,12,3), length 5636414 0.6261448233909714 1.4066722957969802 1.8636325417418167 Comparing deepcopy/ujson/json using random dataset(13,13,3), length 8220800 0.8396582099444547 2.061675688670409 2.755659427352441 Comparing deepcopy/ujson/json using random dataset(14,14,3), length 12018290 1.0951926990258762 2.96703050743886 4.088875914783021 

La conclusión de este pequeño experimento es:

  • Cuando los diccionarios son pequeños, time(ujson)
  • Cuando los diccionarios son grandes los time(deepcopy)

Por lo tanto, depende de la cantidad de copias que realice por segundo y del tipo de diccionario con el que esté trabajando, preferirá cambiar entre deepcopy o ujson.

Puede proporcionar sus propias funciones de copia a los objetos de modo que no necesite una copia profunda. Copia profunda inspecciona cada objeto para verificar lo que necesita ser copiado. Esta es una operación costosa.

base en el progtwig de prueba de @ BPL y agregue mariscal en mi procesador compatible con ARMv6

 print(timeit.timeit('marshal.loads(marshal.dumps(dataset))', setup='from __main__ import marshal, dataset', number=1)) 

Marshal es más rápido que ujson y el conjunto de soporte y la tupla

 2.7.14 (default, Mar 6 2019, 13:27:55) [GCC 7.3.0] Comparing deepcopy/marshal/ujson/json using random dataset(0,0,1), length 2 0.000588178634644 0.000134944915771 0.000258922576904 0.00113606452942 () Comparing deepcopy/marshal/ujson/json using random dataset(0,0,3), length 2 0.000546932220459 0.000134944915771 0.000180006027222 0.00120401382446 () Comparing deepcopy/marshal/ujson/json using random dataset(0,0,5), length 2 0.000545978546143 0.000128984451294 0.000185966491699 0.00106000900269 () Comparing deepcopy/marshal/ujson/json using random dataset(0,2,1), length 50 0.00154900550842 0.000281810760498 0.000414848327637 0.00174903869629 () Comparing deepcopy/marshal/ujson/json using random dataset(0,2,3), length 242 0.00655102729797 0.000789880752563 0.00133085250854 0.00432300567627 () Comparing deepcopy/marshal/ujson/json using random dataset(0,2,5), length 1010 0.0514280796051 0.0015549659729 0.00413513183594 0.0148711204529 () Comparing deepcopy/marshal/ujson/json using random dataset(0,4,1), length 172 0.00250005722046 0.000365018844604 0.000761985778809 0.00263404846191 () Comparing deepcopy/marshal/ujson/json using random dataset(0,4,3), length 2892 0.0329101085663 0.00363397598267 0.0110101699829 0.0262169837952 () Comparing deepcopy/marshal/ujson/json using random dataset(0,4,5), length 46412 0.616458892822 0.0826110839844 0.189103841782 0.504135131836 () Comparing deepcopy/marshal/ujson/json using random dataset(2,0,1), length 2 0.000693082809448 0.000132083892822 0.000182867050171 0.00107002258301 () Comparing deepcopy/marshal/ujson/json using random dataset(2,0,3), length 2 0.000566005706787 0.000132083892822 0.000180959701538 0.00107598304749 () Comparing deepcopy/marshal/ujson/json using random dataset(2,0,5), length 2 0.000562906265259 0.000128984451294 0.000184059143066 0.00118517875671 () Comparing deepcopy/marshal/ujson/json using random dataset(2,2,1), length 258 0.00405406951904 0.000534057617188 0.00124287605286 0.00309610366821 () Comparing deepcopy/marshal/ujson/json using random dataset(2,2,3), length 1058 0.026270866394 0.00180387496948 0.00363302230835 0.0096640586853 () Comparing deepcopy/marshal/ujson/json using random dataset(2,2,5), length 4338 0.0778729915619 0.00682806968689 0.0151469707489 0.0468928813934 () Comparing deepcopy/marshal/ujson/json using random dataset(2,4,1), length 716 0.00720596313477 0.00100684165955 0.0215280056 0.0062358379364 () Comparing deepcopy/marshal/ujson/json using random dataset(2,4,3), length 11468 0.112984895706 0.0238728523254 0.0448131561279 0.0874760150909 () Comparing deepcopy/marshal/ujson/json using random dataset(2,4,5), length 183628 1.83552503586 0.407335042953 0.617804050446 1.65498495102 () Comparing deepcopy/marshal/ujson/json using random dataset(4,0,1), length 2 0.000571012496948 0.000132083892822 0.000189781188965 0.00121593475342 () Comparing deepcopy/marshal/ujson/json using random dataset(4,0,3), length 2 0.000757932662964 0.000131130218506 0.000180959701538 0.00144195556641 () Comparing deepcopy/marshal/ujson/json using random dataset(4,0,5), length 2 0.00056791305542 0.000132083892822 0.000184059143066 0.00107407569885 () Comparing deepcopy/marshal/ujson/json using random dataset(4,2,1), length 430 0.00451302528381 0.00053596496582 0.00142502784729 0.00343203544617 () Comparing deepcopy/marshal/ujson/json using random dataset(4,2,3), length 1730 0.0259549617767 0.00232696533203 0.00387692451477 0.0187470912933 () Comparing deepcopy/marshal/ujson/json using random dataset(4,2,5), length 7026 0.112207174301 0.0119769573212 0.0211799144745 0.0547370910645 () Comparing deepcopy/marshal/ujson/json using random dataset(4,4,1), length 1684 0.00609397888184 0.00121903419495 0.00452899932861 0.00959086418152 () Comparing deepcopy/marshal/ujson/json using random dataset(4,4,3), length 26828 0.19367814064 0.0293428897858 0.0688338279724 0.140627145767 () Comparing deepcopy/marshal/ujson/json using random dataset(4,4,5), length 433484 3.54843020439 0.590909004211 1.09412097931 2.72070598602