2014-05-08 11 views
14

मैं डॉक्टरेट (http://docs.doctrine-project.org/en/2.0.x/reference/batch-processing.html) में बैच प्रोसेसिंग के लिए इटरेटर का उपयोग करने में देख रहा हूं। मेरे पास 20,000 छवियों वाला डेटाबेस है जो मैं फिर से शुरू करना चाहता हूं।सिद्धांत बैच प्रोसेसिंग इटरेट हाई मेमोरी उपयोग

मैं समझता हूं कि एक इटरेटर का उपयोग करना सिद्धांत को हर पंक्ति को स्मृति में लोड करने से रोकना है। हालांकि दो उदाहरणों के बीच स्मृति उपयोग लगभग वही है। मैं (memory_get_usage()/1024) का उपयोग करने से पहले और बाद में स्मृति उपयोग की गणना कर रहा हूं।

$query = $this->em->createQuery('SELECT i FROM Acme\Entities\Image i'); 
$iterable = $query->iterate(); 

while (($image = $iterable->next()) !== false) { 
    // Do something here! 
} 

इटरेटर के लिए मेमोरी उपयोग।

Memory usage before: 2823.36328125 KB 
Memory usage after: 50965.3125 KB 

यह दूसरा उदाहरण पूरे findAll विधि का उपयोग कर स्मृति में सेट परिणाम लोड करता है।

$images = $this->em->getRepository('Acme\Entities\Image')->findAll(); 

findAll के लिए मेमोरी उपयोग।

Memory usage before: 2822.828125 KB 
Memory usage after: 51329.03125 KB 

उत्तर

-1

परिणाम समान हो सकता है क्योंकि डाटाबेस ग्राहक अतिरिक्त मेमोरी आप नहीं कर सकते देखने के आवंटन हो सकता है। इसके अलावा आपका कोड 'IterableResult' का उपयोग करता है जिसे '$ query-> iterate()' रूप दिया जाता है; यह स्मृति समस्याओं के बिना बड़े परिणामों को संसाधित करने की अनुमति देता है। बस त्वरित विचारों की उम्मीद है कि इससे थोड़ा सा मदद मिलेगी।

+0

यह उत्तर बिल्कुल मदद नहीं करता है। – naitsirch

24

सिद्धांत के साथ बैच प्रसंस्करण iterate() और IterableResult की सहायता से, यह लगता है की तुलना में अधिक कठिन है।

जैसा कि आपको उम्मीद है कि IterableResult का सबसे बड़ा लाभ यह है कि यह सभी तत्वों को स्मृति में लोड नहीं करता है, और दूसरा लाभ यह है कि यह आपके द्वारा लोड की गई इकाइयों के संदर्भ नहीं रखता है, इस प्रकार IterableResult जीसी को रोकता नहीं है अपनी इकाई से स्मृति मुक्त करने से।

हालांकि वहाँ किसी अन्य वस्तु सिद्धांत के EntityManager (खासतौर पर UnitOfWork) जो प्रत्येक वस्तु जो आप स्पष्ट या परोक्ष (EAGER संघों) पूछे सभी संदर्भों को आयोजित करता है।

सरल शब्दों में, जब भी आप भी DQL प्रश्नों और भी IterableResult के माध्यम से findAll()findOneBy() द्वारा लौटाए गए किसी भी संस्था (एँ) मिलता है, तो उन संस्थाओं में से प्रत्येक के लिए एक संदर्भ सिद्धांत के अंदर सहेजा गया है। संदर्भ बस एक Assoc सरणी में संग्रहीत किया जाता है, यहाँ स्यूडोकोड है: $identityMap['Acme\Entities\Image'][0] = $image0;

तो क्योंकि आपके पाश से प्रत्येक यात्रा पर, अपने पिछले छवियों (पाश की गुंजाइश या IterableResult के दायरे में मौजूद नहीं होने के बावजूद) अभी भी अंदर मौजूद हैं इस identityMap में, जीसी उन्हें साफ़ नहीं कर सकता है और आपकी स्मृति खपत वही है जब आप findAll() पर कॉल कर रहे थे।

अब कोड के माध्यम से जाना और देखते हैं कि वास्तव में क्या हो रहा है

$query = $this->em->createQuery('SELECT i FROM Acme\Entities\Image i'); 

// यहाँ सिद्धांत केवल क्वेरी वस्तु, यहाँ

$iterable = $query->iterate(); 

// कोई डाटाबेस पहुँच findAll विपरीत बनाता जाने(), इस कॉल पर कोई डीबी एक्सेस नहीं होती है। // यहाँ क्वेरी उद्देश्य केवल एक इटरेटर

while (($image_row = $iterable->next()) !== false) { 
    // now upon the first call to next() the DB WILL BE ACCESSED FOR THE FIRST TIME 
    // the first resulting row will be returned 
    // row will be hydrated into Image object 
    // ----> REFERENCE OF OBJECT WILL BE SAVED INSIDE $identityMap <---- 
    // the row will be returned to you via next() 

    // to access actual Image object, you need to take [0]th element of the array        


    $image = $image_row[0]; 
    // Do something here! 
    write_image_data_to_file($image,'myimage.data.bin'); 

    //now as the loop ends, the variables $image (and $image_row) will go out of scope 
    // and from what we see should be ready for GC 
    // however because reference to this specific image object is still held 
    // by the EntityManager (inside of $identityMap), GC will NOT clean it 
} 
// and by the end of your loop you will consume as much memory 
// as you would have by using `findAll()`. 

में लपेटा जाता है तो सबसे पहले समाधान वास्तव में सिद्धांत EntityManager बताने के लिए $identityMap से वस्तु को अलग करने के लिए है। मैंने इसे और अधिक पठनीय बनाने के लिए while लूप को foreach पर भी बदल दिया।

foreach($iterable as $image_row){ 
    $image = $image_row[0]; 

    // do something with the image 
    write_image_data_to_file($image); 

    $entity_manager->detach($image); 
    // this line will tell doctrine to remove the _reference_to_the_object_ 
    // from identity map. And thus object will be ready for GC 
} 

हालांकि उपरोक्त उदाहरण कुछ खामियां है, भले ही यह doctrine's documentation on batch processing में चित्रित किया है। यह अच्छी तरह से काम करता है, अगर आपकी इकाई ImageEAGER किसी भी संगठन के लिए लोड नहीं कर रही है। लेकिन अगर आप किसी भी संगठन को ईमानदारी से लोड कर रहे हैं उदाहरण के लिए। :

/* 
    @ORM\Entity 
*/ 
class Image { 

    /* 
    @ORM\Column(type="integer") 
    @ORM\Id 
    */ 
    private $id; 

    /* 
    @ORM\Column(type="string") 
    */ 
    private $imageName; 

    /* 
    @ORM\ManyToOne(targetEntity="Acme\Entity\User", fetch="EAGER") 
    This association will be automatically (EAGERly) loaded by doctrine 
    every time you query from db Image entity. Whether by findXXX(),DQL or iterate() 
    */ 
    private $owner; 

    // getters/setters left out for clarity 
} 

तो अगर हम के रूप में ऊपर कोड के एक ही हिस्से का उपयोग करें, पर

foreach($iterable as $image_row){ 
    $image = $image_row[0]; 
    // here becuase of EAGER loading, we already have in memory owner entity 
    // which can be accessed via $image->getOwner() 

    // do something with the image 
    write_image_data_to_file($image); 

    $entity_manager->detach($image); 
    // here we detach Image entity, but `$owner` `User` entity is still 
    // referenced in the doctrine's `$identityMap`. Thus we are leaking memory still. 

} 

संभव समाधान EntityManager::clear() का उपयोग करने के बजाय या EntityManager::detach() जो पूरी तरह से साफ हो जाएगा पहचान मानचित्र हो सकता है।

foreach($iterable as $image_row){ 
    $image = $image_row[0]; 
    // here becuase of EAGER loading, we already have in memory owner entity 
    // which can be accessed via $image->getOwner() 

    // do something with the image 
    write_image_data_to_file($image); 

    $entity_manager->clear(); 
    // now ``$identityMap` will be cleared of ALL entities it has 
    // the `Image` the `User` loaded in this loop iteration and as as 
    // SIDE EFFECT all OTHER Entities which may have been loaded by you 
    // earlier. Thus you when you start this loop you must NOT rely 
    // on any entities you have `persist()`ed or `remove()`ed 
    // all changes since the last `flush()` will be lost. 

} 

तो उम्मीद है कि यह सिद्धांत पुनरावृत्ति को थोड़ा सा समझने में मदद करता है।

+0

विस्तृत स्पष्टीकरण के लिए धन्यवाद। मैं अपने एक्सपेरेंस से जोड़ूंगा, कि '$ iterable = $ query-> iterate(); क्वेरी क्वेरी बफरिंग के कारण भी स्मृति की मात्रा का उपभोग कर सकता है। http://php.net/manual/en/mysqlinfo.concepts.buffering.php – gatisl

+0

@dimitri_k विवरण के लिए धन्यवाद, बीटीडब्ल्यू हैरेटर के साथ HYDRATE_SCALAR का उपयोग करने का कोई तरीका है? यदि हां तो क्या यह 'पहचान मैप' मुद्दे से निपटने का एक और विकल्प होगा? – Stphane

+0

@Stphane जो HYDRATE_SCALAR का उपयोग करने के लिए एक दिलचस्प सुझाव है।मूल रूप से मैंने इसे व्यवहार्य विकल्प के रूप में नहीं सोचा था क्योंकि जब आप HYDRATE_SCALAR का उपयोग करते हैं - आपको ऑब्जेक्ट्स को एक अच्छा ट्रैवर्स ऑब्जेक्ट ग्राफ़ में वापस नहीं किया जाएगा। आप 'ऑब्जेक्ट-> getUser() -> getProfession() -> getSalary() 'में एक ऑब्जेक्ट से दूसरे ऑब्जेक्ट में रिलेशनशिप का पालन नहीं कर पाएंगे। वे बड़े संघ सरणी के रूप में वापस आ जाएंगे (जैसे 'चयन करें ... जॉइन')। जब आपके पास फ्लैट ऑब्जेक्ट रिलेशनशिप (1 या 2 स्तर) होते हैं तो HYDRATE_SCALAR आपके लिए काम कर सकता है। मुझे यकीन नहीं है कि जब आप बाद में ऑब्जेक्ट में अधिक रिश्तों को जोड़ते हैं तो यह कैसे काम करता है ... –

2

मुझे दृढ़ विश्वास है कि सिद्धांत के साथ बैच प्रसंस्करण या MySQL (पीडीओ या माइस्क्ली) के साथ किसी भी प्रकार के पुनरावृत्ति केवल भ्रम हैं।

@ dimitri-k ने विशेष रूप से काम की इकाई के बारे में एक अच्छी व्याख्या प्रदान की। समस्या मिस अग्रणी है: "$ क्वेरी-> iterate()" जो वास्तव में डेटा स्रोत पर पुन: सक्रिय नहीं होता है। यह केवल एक \ Traversable wrapper पहले से ही को पूरी तरह से डेटा स्रोत लाया गया है।

का प्रदर्शन भी है कि तस्वीर से पूरी तरह से सिद्धांत अमूर्त परत को हटाने, हम अभी भी स्मृति में चलेंगे एक उदाहरण मुद्दों:

echo 'Starting with memory usage: ' . memory_get_usage(true)/1024/1024 . " MB \n"; 

$pdo = new \PDO("mysql:dbname=DBNAME;host=HOST", "USER", "PW"); 
$stmt = $pdo->prepare('SELECT * FROM my_big_table LIMIT 100000'); 
$stmt->execute(); 

while ($rawCampaign = $stmt->fetch()) { 
    // echo $rawCampaign['id'] . "\n"; 
} 

echo 'Ending with memory usage: ' . memory_get_usage(true)/1024/1024 . " MB \n"; 

आउटपुट:

Starting with memory usage: 6 MB 
Ending with memory usage: 109.46875 MB 

यहाँ, निराशाजनक getIterator() विधि:

namespace Doctrine\DBAL\Driver\Mysqli\MysqliStatement 

/** 
* {@inheritdoc} 
*/ 
public function getIterator() 
{ 
    $data = $this->fetchAll(); 

    return new \ArrayIterator($data); 
} 

आप मेरी छोटी लाइब्रेरी का उपयोग पर वास्तव में PHP सिद्धांत या डीक्यूएल या केवल शुद्ध एसक्यूएल का उपयोग कर स्ट्रीम भारी तालिकाओं का उपयोग कर सकते हैं। हालांकि आपको उपयुक्त लगता है: https://github.com/EnchanterIO/remote-collection-stream

+0

आपने यह नहीं बताया कि आपकी लाइब्रेरी बेहतर क्यों है – 0x13a

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