2009-08-26 14 views
91

मेमोरी उपयोग और सीपीयू खपत - शब्दकोश या वस्तु के संदर्भ में पायथन में और अधिक कुशल क्या है?शब्दकोश बनाम वस्तु - जो अधिक कुशल है और क्यों?

पृष्ठभूमि: मुझे पाइथन में बड़ी मात्रा में डेटा लोड करना होगा। मैंने एक ऑब्जेक्ट बनाया जो सिर्फ एक फ़ील्ड कंटेनर है। 4 एम उदाहरण बनाना और उन्हें एक शब्दकोश में डालने में लगभग 10 मिनट और ~ 6 जीबी मेमोरी लग गई। शब्दकोश तैयार होने के बाद, इसे एक्सेस करना एक आंख का झपकी है।

उदाहरण: प्रदर्शन मैं दो सरल प्रोग्राम हैं जो भी ऐसा ही लिखा है की जाँच करने के लिए - एक, अन्य शब्दकोश वस्तुओं का उपयोग कर रहा है:

वस्तु (निष्पादन समय ~ 18sec):

class Obj(object): 
    def __init__(self, i): 
    self.i = i 
    self.l = [] 
all = {} 
for i in range(1000000): 
    all[i] = Obj(i) 

शब्दकोश (निष्पादन समय ~ 12sec):

all = {} 
for i in range(1000000): 
    o = {} 
    o['i'] = i 
    o['l'] = [] 
    all[i] = o 

प्रश्न: क्या मैं कुछ गलत कर रहा हूं या शब्दकोश वस्तु से बस तेज है? यदि वास्तव में शब्दकोश बेहतर प्रदर्शन करता है, तो क्या कोई समझा सकता है क्यों?

+9

इस तरह के बड़े अनुक्रम उत्पन्न करते समय आपको वास्तव में सीमा के बजाय xrange का उपयोग करना चाहिए। बेशक, चूंकि आप निष्पादन समय के सेकंड से निपट रहे हैं, इससे कोई फर्क नहीं पड़ता है, लेकिन फिर भी, यह एक अच्छी आदत है। –

उत्तर

129

क्या आपने __slots__ का उपयोग करने का प्रयास किया है?

प्रलेखन http://docs.python.org/reference/datamodel.html#slots से:।

"डिफ़ॉल्ट रूप से, दोनों पुराने और नए शैली वर्गों के उदाहरण विशेषता भंडारण के लिए एक शब्दकोश बहुत कुछ उदाहरण चर होने वस्तुओं के लिए इस कचरे जगह है अंतरिक्ष की खपत तीव्र बन सकता है। जब उदाहरणों की बड़ी संख्या का निर्माण।

डिफ़ॉल्ट एक नई शैली वर्ग परिभाषा __slots__ को परिभाषित करते हुए अधिरोहित जा सकता है। __slots__ घोषणा से प्रत्येक के लिए एक मूल्य धारण करने के लिए प्रत्येक उदाहरण में उदाहरण चर और भंडार सिर्फ पर्याप्त जगह का एक अनुक्रम लेता है परिवर्तनीय। अंतरिक्ष बचाया गया है क्योंकि __dict__ cre नहीं है प्रत्येक उदाहरण के लिए पैदा। "

तो इस बार को बचाने में भी स्मृति के रूप में करता है?

अपने कंप्यूटर पर तीन दृष्टिकोण तुलना:

test_slots.py:

class Obj(object): 
    __slots__ = ('i', 'l') 
    def __init__(self, i): 
    self.i = i 
    self.l = [] 
all = {} 
for i in range(1000000): 
    all[i] = Obj(i) 

test_obj.py:

class Obj(object): 
    def __init__(self, i): 
    self.i = i 
    self.l = [] 
all = {} 
for i in range(1000000): 
    all[i] = Obj(i) 

test_dict।py:

all = {} 
for i in range(1000000): 
    o = {} 
    o['i'] = i 
    o['l'] = [] 
    all[i] = o 

test_namedtuple.py (2.6 में समर्थित):

import collections 

Obj = collections.namedtuple('Obj', 'i l') 

all = {} 
for i in range(1000000): 
    all[i] = Obj(i, []) 

भागो बेंचमार्क (CPython 2.5 का प्रयोग करके):

$ lshw | grep product | head -n 1 
      product: Intel(R) Pentium(R) M processor 1.60GHz 
$ python --version 
Python 2.5 
$ time python test_obj.py && time python test_dict.py && time python test_slots.py 

real 0m27.398s (using 'normal' object) 
real 0m16.747s (using __dict__) 
real 0m11.777s (using __slots__) 

CPython 2.6.2, नामित टपल सहित का उपयोग करना परीक्षण:

$ python --version 
Python 2.6.2 
$ time python test_obj.py && time python test_dict.py && time python test_slots.py && time python test_namedtuple.py 

real 0m27.197s (using 'normal' object) 
real 0m17.657s (using __dict__) 
real 0m12.249s (using __slots__) 
real 0m12.262s (using namedtuple) 

तो हाँ (नहीं वास्तव में एक आश्चर्य), __slots__ का उपयोग एक प्रदर्शन अनुकूलन है। नामित टुपल का उपयोग करना __slots__ पर समान प्रदर्शन है।

+1

यह बहुत अच्छा है - धन्यवाद! मैंने अपनी मशीन पर कोशिश की है - __slots__ के साथ ऑब्जेक्ट सबसे कुशल दृष्टिकोण है (मुझे ~ 7sec मिला है)। – tkokoszka

+5

स्लॉट वाले ऑब्जेक्ट्स के लिए टुपल्स, http://docs.python.org/library/collections.html#collections.namedtuple नामक एक क्लास फैक्ट्री भी नामित हैं। यह निश्चित रूप से neater और शायद और अधिक अनुकूलित भी हो सकता है। –

+0

मैंने परीक्षण टुपल्स का भी परीक्षण किया, और परिणामों के साथ जवाब अपडेट किया। – codeape

11

किसी ऑब्जेक्ट में विशेषता पहुंच दृश्यों के पीछे शब्दकोश पहुंच का उपयोग करती है - इसलिए विशेषता पहुंच का उपयोग करके आप अतिरिक्त ओवरहेड जोड़ रहे हैं। इसके अलावा ऑब्जेक्ट केस में, आप अतिरिक्त ओवरहेड कर रहे हैं क्योंकि उदा। अतिरिक्त स्मृति आवंटन और कोड निष्पादन (उदाहरण के लिए __init__ विधि)।

अपने कोड में, अगर o एक Obj उदाहरण है, o.attr अतिरिक्त भूमि के ऊपर की एक छोटी राशि के साथ o.__dict__['attr'] के बराबर है।

+0

एह? डाउनवोट क्यों? –

+0

क्या आपने इसका परीक्षण किया? 'ओ .__ dict __ [" attr "] 'अतिरिक्त ओवरहेड वाला एक है, एक अतिरिक्त बाइटकोड सेशन ले रहा है; obj.attr तेज है। (बेशक विशेषता पहुंच सदस्यता की तुलना में धीमी नहीं होगी - यह एक महत्वपूर्ण, भारी अनुकूलित कोड पथ है।) –

+0

जाहिर है यदि आप वास्तव में ** ओ .__ dict __ ["attr"] धीमा हो जाएंगे - मुझे केवल यह कहना था कि यह इसके बराबर था, न कि यह वास्तव में इस तरह लागू किया गया था। मुझे लगता है कि यह मेरे शब्द से स्पष्ट नहीं है। मैंने स्मृति आवंटन, कन्स्ट्रक्टर कॉल टाइम इत्यादि जैसे अन्य कारकों का भी उल्लेख किया। –

3
from datetime import datetime 

ITER_COUNT = 1000 * 1000 

def timeit(method): 
    def timed(*args, **kw): 
     s = datetime.now() 
     result = method(*args, **kw) 
     e = datetime.now() 

     print method.__name__, '(%r, %r)' % (args, kw), e - s 
     return result 
    return timed 

class Obj(object): 
    def __init__(self, i): 
     self.i = i 
     self.l = [] 

class SlotObj(object): 
    __slots__ = ('i', 'l') 
    def __init__(self, i): 
     self.i = i 
     self.l = [] 

@timeit 
def profile_dict_of_dict(): 
    return dict((i, {'i': i, 'l': []}) for i in xrange(ITER_COUNT)) 

@timeit 
def profile_list_of_dict(): 
    return [{'i': i, 'l': []} for i in xrange(ITER_COUNT)] 

@timeit 
def profile_dict_of_obj(): 
    return dict((i, Obj(i)) for i in xrange(ITER_COUNT)) 

@timeit 
def profile_list_of_obj(): 
    return [Obj(i) for i in xrange(ITER_COUNT)] 

@timeit 
def profile_dict_of_slotobj(): 
    return dict((i, SlotObj(i)) for i in xrange(ITER_COUNT)) 

@timeit 
def profile_list_of_slotobj(): 
    return [SlotObj(i) for i in xrange(ITER_COUNT)] 

if __name__ == '__main__': 
    profile_dict_of_dict() 
    profile_list_of_dict() 
    profile_dict_of_obj() 
    profile_list_of_obj() 
    profile_dict_of_slotobj() 
    profile_list_of_slotobj() 

परिणाम:

[email protected]:~$ python ~/Dropbox/src/StackOverflow/1336791.py 
profile_dict_of_dict ((), {}) 0:00:08.228094 
profile_list_of_dict ((), {}) 0:00:06.040870 
profile_dict_of_obj ((), {}) 0:00:11.481681 
profile_list_of_obj ((), {}) 0:00:10.893125 
profile_dict_of_slotobj ((), {}) 0:00:06.381897 
profile_list_of_slotobj ((), {}) 0:00:05.860749 
2

वहाँ कोई सवाल ही नहीं है।
आपके पास कोई अन्य विशेषता नहीं है (कोई विधि नहीं, कुछ भी नहीं)। इसलिए आपके पास डेटा कंटेनर है (इस मामले में, एक शब्दकोश)।

मैं आमतौर पर डेटा मॉडलिंग के संदर्भ में सोचना पसंद करता हूं। अगर कुछ बड़ा प्रदर्शन मुद्दा है, तो मैं अमूर्तता में कुछ छोड़ सकता हूं, लेकिन केवल बहुत अच्छे कारणों से।
प्रोग्रामिंग जटिलता के प्रबंधन के बारे में है, और को बनाए रखने के लिए सही abstraction अक्सर इस तरह के परिणाम प्राप्त करने का सबसे उपयोगी तरीका है।

कारण कारण एक वस्तु धीमी है, मुझे लगता है कि आपका माप सही नहीं है।
आप लूप के अंदर बहुत कम असाइनमेंट कर रहे हैं, और इसलिए आप जो देखते हैं वहां एक तीर (आंतरिक वस्तु) और "कस्टम" ऑब्जेक्ट को तुरंत चालू करने के लिए आवश्यक समय होता है। हालांकि भाषा परिप्रेक्ष्य से वे समान हैं, उनके पास काफी अलग कार्यान्वयन है।
उसके बाद, असाइनमेंट समय लगभग दोनों के लिए समान होना चाहिए, क्योंकि अंतिम सदस्यों को एक शब्दकोश के अंदर बनाए रखा जाता है।

7

क्या आपने namedtuple का उपयोग करने पर विचार किया है? (link for python 2.4/2.5)

यह संरचित डेटा का प्रतिनिधित्व करने का नया मानक तरीका है जो आपको एक टुपल और कक्षा की सुविधा का प्रदर्शन देता है।

शब्दकोशों की तुलना में यह केवल नकारात्मक है कि (टुपल्स की तरह) यह आपको सृजन के बाद विशेषताओं को बदलने की क्षमता नहीं देता है।

+0

@Eloims क्षमा करें, अस्पष्ट शब्द का उपयोग करता है। मैंने इसे ठीक कर दिया है। –

2

यहां पाइथन 3.6.1 के लिए @hughdbrown उत्तर की एक प्रति है, मैंने 5x बड़ा गिनती की है और प्रत्येक रन के अंत में पायथन प्रक्रिया की स्मृति पदचिह्न का परीक्षण करने के लिए कुछ कोड जोड़ा है।

डाउनवॉटर के पास होने से पहले, सलाह दीजिये कि वस्तुओं के आकार की गिनती करने की यह विधि सटीक नहीं है।

from datetime import datetime 
import os 
import psutil 

process = psutil.Process(os.getpid()) 


ITER_COUNT = 1000 * 1000 * 5 

RESULT=None 

def makeL(i): 
    # Use this line to negate the effect of the strings on the test 
    # return "Python is smart and will only create one string with this line" 

    # Use this if you want to see the difference with 5 million unique strings 
    return "This is a sample string %s" % i 

def timeit(method): 
    def timed(*args, **kw): 
     global RESULT 
     s = datetime.now() 
     RESULT = method(*args, **kw) 
     e = datetime.now() 

     sizeMb = process.memory_info().rss/1024/1024 
     sizeMbStr = "{0:,}".format(round(sizeMb, 2)) 

     print('Time Taken = %s, \t%s, \tSize = %s' % (e - s, method.__name__, sizeMbStr)) 

    return timed 

class Obj(object): 
    def __init__(self, i): 
     self.i = i 
     self.l = makeL(i) 

class SlotObj(object): 
    __slots__ = ('i', 'l') 
    def __init__(self, i): 
     self.i = i 
     self.l = makeL(i) 

from collections import namedtuple 
NT = namedtuple("NT", ["i", 'l']) 

@timeit 
def profile_dict_of_nt(): 
    return [NT(i=i, l=makeL(i)) for i in range(ITER_COUNT)] 

@timeit 
def profile_list_of_nt(): 
    return dict((i, NT(i=i, l=makeL(i))) for i in range(ITER_COUNT)) 

@timeit 
def profile_dict_of_dict(): 
    return dict((i, {'i': i, 'l': makeL(i)}) for i in range(ITER_COUNT)) 

@timeit 
def profile_list_of_dict(): 
    return [{'i': i, 'l': makeL(i)} for i in range(ITER_COUNT)] 

@timeit 
def profile_dict_of_obj(): 
    return dict((i, Obj(i)) for i in range(ITER_COUNT)) 

@timeit 
def profile_list_of_obj(): 
    return [Obj(i) for i in range(ITER_COUNT)] 

@timeit 
def profile_dict_of_slot(): 
    return dict((i, SlotObj(i)) for i in range(ITER_COUNT)) 

@timeit 
def profile_list_of_slot(): 
    return [SlotObj(i) for i in range(ITER_COUNT)] 

profile_dict_of_nt() 
profile_list_of_nt() 
profile_dict_of_dict() 
profile_list_of_dict() 
profile_dict_of_obj() 
profile_list_of_obj() 
profile_dict_of_slot() 
profile_list_of_slot() 

और ये मेरी परिणाम हैं

Time Taken = 0:00:07.018720, provile_dict_of_nt,  Size = 951.83 
Time Taken = 0:00:07.716197, provile_list_of_nt,  Size = 1,084.75 
Time Taken = 0:00:03.237139, profile_dict_of_dict, Size = 1,926.29 
Time Taken = 0:00:02.770469, profile_list_of_dict, Size = 1,778.58 
Time Taken = 0:00:07.961045, profile_dict_of_obj, Size = 1,537.64 
Time Taken = 0:00:05.899573, profile_list_of_obj, Size = 1,458.05 
Time Taken = 0:00:06.567684, profile_dict_of_slot, Size = 1,035.65 
Time Taken = 0:00:04.925101, profile_list_of_slot, Size = 887.49 

मेरा निष्कर्ष यह है:

  1. स्लॉट सबसे अच्छा स्मृति पदचिह्न और गति पर उचित हैं।
  2. डिक्ट्स सबसे तेज़ हैं, लेकिन अधिकांश मेमोरी का उपयोग करें।
+0

मैन, आपको इसे एक प्रश्न में बदलना चाहिए। मैंने इसे अपने कंप्यूटर पर भी चलाया, बस यह सुनिश्चित करने के लिए (मैंने psutil स्थापित नहीं किया था, इसलिए मैंने उस भाग को बाहर निकाला)। वैसे भी, यह मेरे लिए परेशान है, और इसका मतलब है कि मूल प्रश्न का पूरी तरह उत्तर नहीं दिया गया है। अन्य सभी उत्तरों "नेमटूपल महान हैं" और "__slots__ का उपयोग करें" की तरह हैं, और जाहिर है कि हर बार एक नया नया dict ऑब्जेक्ट ऑब्जेक्ट उनके से तेज़ है? मुझे लगता है कि डिक्ट्स वास्तव में अच्छी तरह अनुकूलित हैं? – Multihunter

+0

ऐसा लगता है कि एक स्ट्रिंग लौटने वाले मेकएल फ़ंक्शन का परिणाम प्रतीत होता है। यदि आप एक खाली सूची लौटते हैं, इसके बजाय, परिणाम लगभग ह्यूडब्रॉउन के साथ python2 से मेल खाते हैं। स्लॉटऑबज से नामांकित अपवाद हमेशा धीमे होते हैं :( – Multihunter

संबंधित मुद्दे