2009-06-05 13 views
39

मुझे एक बड़ी फ़ाइल को कई छोटी फाइलों में विभाजित करना है। प्रत्येक गंतव्य फ़ाइलों को ऑफसेट और लंबाई द्वारा बाइट्स की संख्या के रूप में परिभाषित किया जाता है। मैं निम्नलिखित कोड का उपयोग कर रहा:सी # में सुपर-फास्ट फ़ाइल-स्ट्रीमिंग कोड कैसे लिखें?

private void copy(string srcFile, string dstFile, int offset, int length) 
{ 
    BinaryReader reader = new BinaryReader(File.OpenRead(srcFile)); 
    reader.BaseStream.Seek(offset, SeekOrigin.Begin); 
    byte[] buffer = reader.ReadBytes(length); 

    BinaryWriter writer = new BinaryWriter(File.OpenWrite(dstFile)); 
    writer.Write(buffer); 
} 

ध्यान में रखते हुए मैं इस समारोह लगभग 100,000 बार कॉल करने के लिए है, यह उल्लेखनीय धीमी है।

  1. क्या रायटर को सीधे रीडर से कनेक्ट करने का कोई तरीका है? (यानी, वास्तव में स्मृति में बफर में सामग्री को लोड किए बिना।)
+0

File.OpenRead और File.OpenWrite 100,000 धीमी गति से ठीक हो जाएगा ... –

+0

आप पूरी तरह से फ़ाइल बंटवारे रहे हैं, यानी तुम सिर्फ सभी छोटे में शामिल होने से बड़ी फाइल के पुनर्निर्माण सकता है एक साथ फाइलें? यदि ऐसा है तो वहां बचत होगी। यदि नहीं, तो छोटी फ़ाइलों की श्रेणियां ओवरलैप करें? क्या वे ऑफसेट के क्रम में क्रमबद्ध हैं? – jamie

उत्तर

45

मैं वहाँ नेट के भीतर कुछ भी स्मृति में बफरिंग के बिना एक फ़ाइल के एक हिस्से को कॉपी अनुमति देने के लिए है विश्वास नहीं है। हालांकि, यह मुझे मारता है कि यह वैसे भी अक्षम है, क्योंकि इसे इनपुट फ़ाइल खोलने और कई बार तलाशने की आवश्यकता है। आप सिर्फ फ़ाइल विभाजित कर रहे हैं, क्यों नहीं इनपुट फ़ाइल एक बार खोलने के लिए, और फिर बस की तरह कुछ लिखें:

public static void CopySection(Stream input, string targetFile, int length) 
{ 
    byte[] buffer = new byte[8192]; 

    using (Stream output = File.OpenWrite(targetFile)) 
    { 
     int bytesRead = 1; 
     // This will finish silently if we couldn't read "length" bytes. 
     // An alternative would be to throw an exception 
     while (length > 0 && bytesRead > 0) 
     { 
      bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); 
      output.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
    } 
} 

यह प्रत्येक मंगलाचरण पर एक बफर बनाने में एक छोटी सी अक्षमता है - आप चाहते हो सकता है एक बार बफर बना सकते हैं और पारित उस विधि में भी करने के लिए:

public static void CopySection(Stream input, string targetFile, 
           int length, byte[] buffer) 
{ 
    using (Stream output = File.OpenWrite(targetFile)) 
    { 
     int bytesRead = 1; 
     // This will finish silently if we couldn't read "length" bytes. 
     // An alternative would be to throw an exception 
     while (length > 0 && bytesRead > 0) 
     { 
      bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); 
      output.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
    } 
} 

ध्यान दें कि यह भी (कथन का उपयोग के कारण) उत्पादन धारा बंद कर देता है जो अपने मूल कोड नहीं किया।

महत्वपूर्ण बात यह है कि यह ऑपरेटिंग सिस्टम फ़ाइल को अधिक कुशलतापूर्वक बफरिंग का उपयोग करेगा, क्योंकि शुरुआत में फ़ाइल को फिर से खोलने और फिर मांगने के बजाय आप उसी इनपुट स्ट्रीम का पुन: उपयोग करते हैं।

मुझे लगता है कि यह काफी तेजी हो जाएगा, लेकिन स्पष्ट रूप से आप इसे देखने की कोशिश करनी होगी ...

यह सन्निहित हिस्सा मान लिया गया है, निश्चित रूप से। यदि आपको फ़ाइल के बिट्स को छोड़ना है, तो आप इसे विधि के बाहर से कर सकते हैं। साथ ही, यदि आप बहुत छोटी फाइलें लिख रहे हैं, तो आप उस स्थिति के लिए अनुकूलित भी कर सकते हैं - ऐसा करने का सबसे आसान तरीका शायद इनपुट स्ट्रीम को BufferedStream पेश करना होगा।

+0

मुझे पता है कि यह दो साल पुरानी पोस्ट है, बस आश्चर्य हुआ ... क्या यह अभी भी सबसे तेज़ तरीका है? (यानी कुछ भी नया नहीं है। पता होना चाहिए?)। साथ ही, क्या लूप में प्रवेश करने से पहले 'Math.Min' करने के लिए तेज़ होगा? या बेहतर अभी तक, लंबाई पैरामीटर को हटाने के लिए क्योंकि इसे बफर के माध्यम से गणना की जा सकती है? Picky और necro होने के लिए खेद है! अग्रिम में धन्यवाद। – Smudge202

+2

@ Smudge202: यह देखते हुए कि यह आईओ प्रदर्शन कर रहा है, गणित के लिए कॉल। मेरा निश्चित रूप से * प्रदर्शन के संदर्भ में प्रासंगिक नहीं होगा। लंबाई पैरामीटर और बफर लंबाई दोनों होने का बिंदु आपको संभावित रूप से oversized बफर का पुन: उपयोग करने की अनुमति देना है। –

+0

गोचा, और मेरे पास वापस आने के लिए धन्यवाद। मुझे एक नया सवाल शुरू करने से नफरत होगी जब यहां पर्याप्त अच्छा जवाब हो, लेकिन आप कहेंगे कि अगर आप बड़ी संख्या में फाइलों के पहले * एक्स * बाइट्स को पढ़ना चाहते हैं (हथियाने के उद्देश्य के लिए बड़ी संख्या में फाइलों से एक्सएमपी मेटाडाटा), उपर्युक्त दृष्टिकोण (कुछ ट्वीकिंग के साथ) की अभी भी सिफारिश की जाएगी? – Smudge202

6

length कितना बड़ा है? आप एक निश्चित आकार (मध्यम रूप से बड़े, लेकिन अश्लील नहीं) बफर का पुनः उपयोग करने के लिए बेहतर कर सकते हैं, और BinaryReader भूल जाएं ... बस Stream.Read और Stream.Write का उपयोग करें।

(संपादित करें) कुछ की तरह:

private static void copy(string srcFile, string dstFile, int offset, 
    int length, byte[] buffer) 
{ 
    using(Stream inStream = File.OpenRead(srcFile)) 
    using (Stream outStream = File.OpenWrite(dstFile)) 
    { 
     inStream.Seek(offset, SeekOrigin.Begin); 
     int bufferLength = buffer.Length, bytesRead; 
     while (length > bufferLength && 
      (bytesRead = inStream.Read(buffer, 0, bufferLength)) > 0) 
     { 
      outStream.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
     while (length > 0 && 
      (bytesRead = inStream.Read(buffer, 0, length)) > 0) 
     { 
      outStream.Write(buffer, 0, bytesRead); 
      length -= bytesRead; 
     } 
    }   
} 
+1

अंत में फ्लश के लिए कोई कारण? इसे बंद करना चाहिए। इसके अलावा, मुझे लगता है कि आप पहली लूप में लंबाई से घटाना चाहते हैं :) –

+0

अच्छी आंखें जॉन! फ्लश आदत का बल था; बहुत सारे कोड से जब मैं विधि में उन्हें खोलने/बंद करने के बजाय स्ट्रीम पास करता हूं - यह लौटने से पहले इसे फ़्लश करने के लिए सुविधाजनक है (यदि डेटा की गैर-छोटी मात्रा लिखना)। –

3

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

offset = 1234, length = 34 
offset = 1300, length = 40 
offset = 1350, length = 1000 
:

लंबाई बहुत बड़ा नहीं हैं, तो आप भी समूह कई प्रति कॉल करने के लिए ऑफसेट है कि एक दूसरे के निकट हैं समूहीकरण और पूरे ब्लॉक आप उनके लिए की जरूरत है पढ़ने उदाहरण के लिए, द्वारा कोशिश कर सकते हैं

एक पढ़ने के लिए बांटा जा सकता है:

offset = 1234, length = 1074 

तो आप केवल अपने बफर में "तलाश" करने के लिए है और फिर से पढ़ने के लिए बिना वहां से तीन नई फ़ाइलें लिख सकते हैं।

1

पहली बात जो मैं अनुशंसा करता हूं वह माप लेना है। आप अपना समय कहां खो रहे हैं? क्या यह पढ़ा गया है, या लिखना?

100,000 से अधिक एक्सेस (समय की राशि): बफर सरणी आवंटित करने में कितना समय व्यतीत किया जाता है? पढ़ने के लिए फ़ाइल खोलने में कितना समय व्यतीत होता है (क्या यह हर बार एक ही फाइल है?) पढ़ने और लिखने के संचालन में कितना समय व्यतीत होता है?

यदि आप फ़ाइल पर किसी प्रकार का परिवर्तन नहीं कर रहे हैं, तो क्या आपको बाइनरीवाइटर चाहिए, या क्या आप लिखने के लिए फाइलस्ट्रीम का उपयोग कर सकते हैं? काफी संभवतः यह करने के लिए स्मृति मैप की गई फ़ाइलों का उपयोग करने के लिए किया जाएगा सबसे तेज़ तरीका है (ताकि मुख्य रूप से स्मृति को कॉपी (यह कोशिश, तो आप समान उत्पादन मिलता है? यह समय की बचत करता है?)

-1

(भविष्य में संदर्भ के लिए।)

, और फ़ाइल को संभालने वाला ओएस अपने पेजिंग/मेमोरी प्रबंधन के माध्यम से पढ़ता/लिखता है)।

मेमोरी मैप की गई फ़ाइलें .NET 4.0 में प्रबंधित कोड में समर्थित हैं।

लेकिन जैसा कि ध्यान दिया गया है, आपको प्रोफ़ाइल की आवश्यकता है, और अधिकतम प्रदर्शन के लिए देशी कोड पर स्विच करने की उम्मीद है।

+1

मेमोरी मैप की गई फ़ाइलें पृष्ठ गठबंधन हैं इसलिए वे बाहर हैं। यहां समस्या डिस्क एक्सेस समय की अधिक संभावना है, और स्मृति मैप की गई फ़ाइलों को वैसे भी मदद नहीं करेगा। ओएस कैशिंग फाइलों को प्रबंधित करने जा रहा है चाहे वे स्मृति मैप किए गए हों या नहीं। – jamie

0

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

+0

थ्रेडिंग मदद कर सकती है, लेकिन उसकी बाधा निश्चित रूप से I/O पर है - सीपीयू शायद डिस्क पर प्रतीक्षा करने में काफी समय व्यतीत कर रहा है। यह कहना नहीं है कि थ्रेडिंग कोई फर्क नहीं पड़ता है (उदाहरण के लिए, यदि लिखने के लिए अलग-अलग स्पिंडल हैं, तो वह एक बेहतर प्रदर्शन को बढ़ावा दे सकता है अगर वह सभी डिस्क पर होता तो) – JMarsch

3

क्या आपने सीसीआर का उपयोग करने पर विचार किया है क्योंकि आप अलग-अलग फ़ाइलों को लिखने के लिए लिख रहे हैं, आप समांतर (पढ़ना और लिखना) में सब कुछ कर सकते हैं और सीसीआर इसे करने में बहुत आसान बनाता है।

static void Main(string[] args) 
    { 
     Dispatcher dp = new Dispatcher(); 
     DispatcherQueue dq = new DispatcherQueue("DQ", dp); 

     Port<long> offsetPort = new Port<long>(); 

     Arbiter.Activate(dq, Arbiter.Receive<long>(true, offsetPort, 
      new Handler<long>(Split))); 

     FileStream fs = File.Open(file_path, FileMode.Open); 
     long size = fs.Length; 
     fs.Dispose(); 

     for (long i = 0; i < size; i += split_size) 
     { 
      offsetPort.Post(i); 
     } 
    } 

    private static void Split(long offset) 
    { 
     FileStream reader = new FileStream(file_path, FileMode.Open, 
      FileAccess.Read); 
     reader.Seek(offset, SeekOrigin.Begin); 
     long toRead = 0; 
     if (offset + split_size <= reader.Length) 
      toRead = split_size; 
     else 
      toRead = reader.Length - offset; 

     byte[] buff = new byte[toRead]; 
     reader.Read(buff, 0, (int)toRead); 
     reader.Dispose(); 
     File.WriteAllBytes("c:\\out" + offset + ".txt", buff); 
    } 

इस कोड को एक सीसीआर बंदरगाह जो एक थ्रेड का कारण बनता है के लिए पदों ऑफसेट स्प्लिट विधि में कोड निष्पादित करने के लिए बनाया जाना। यह आपको फ़ाइल को कई बार खोलने का कारण बनता है लेकिन सिंक्रनाइज़ेशन की आवश्यकता से छुटकारा पाता है। आप इसे और अधिक मेमोरी कुशल बना सकते हैं लेकिन आपको गति बलिदान करना होगा।

+1

इस के साथ याद रखें (या कोई भी थ्रेडिंग समाधान) आप एक मंच को हिट कर सकते हैं जहां आप अपने आईओ को अधिकतम कर देंगे: आप अपना सर्वश्रेष्ठ थ्रूपुट दबाएंगे (यानी यदि एक ही समय में सैकड़ों/हजारों छोटी फाइलें लिखने का प्रयास करते हैं, तो कई बड़ी फाइलें आदि)।मैंने हमेशा पाया है कि यदि मैं एक फ़ाइल को कुशलता से पढ़/लिख सकता हूं तो समानांतरता से इसे सुधारने के लिए मैं बहुत कुछ कर सकता हूं (असेंबली बहुत मदद कर सकती है, असेंबलर में पढ़/लिख सकती है और यह शानदार हो सकती है, आईओ तक सीमाएं, हालांकि यह लिखने में दर्द हो सकता है, और आपको यह सुनिश्चित करने की ज़रूरत है कि आप अपने डिवाइस पर प्रत्यक्ष हार्डवेयर या BIOS स्तर तक पहुंच चाहते हैं – GMasucci

1

फ़ाइलस्ट्रीम + स्ट्रीमवाइटर का उपयोग करके मुझे पता है कि कम समय में बड़ी फ़ाइलों को बनाना संभव है (1 मिनट से कम 30 सेकंड)। मैं उस तकनीक का उपयोग कर एक फ़ाइल से 700+ मेगाबाइट्स की कुल तीन फाइलें उत्पन्न करता हूं।

आपके द्वारा उपयोग किए जा रहे कोड के साथ आपकी प्राथमिक समस्या यह है कि आप हर बार एक फ़ाइल खोल रहे हैं। वह फाइल I/O ओवरहेड बना रहा है।

यदि आप फ़ाइलों के नामों को जानते थे जो आप समय से पहले उत्पन्न करेंगे, तो आप फ़ाइल को खोल सकते हैं। ओपनवाइट एक अलग विधि में निकालें; यह गति में वृद्धि होगी।कोड को देखे बिना यह निर्धारित करता है कि आप फ़ाइलों को कैसे विभाजित कर रहे हैं, मुझे नहीं लगता कि आप बहुत तेज हो सकते हैं।

21

सी # से फ़ाइल I/O करने का सबसे तेज़ तरीका विंडोज रीडफाइल और लिखेंफ़ाइल फ़ंक्शंस का उपयोग करना है। मैंने एक सी # कक्षा लिखी है जो इस क्षमता के साथ-साथ बेंचमार्किंग प्रोग्राम को भी समाहित करती है जो बाइनरी रीडर और बाइनरीवाइटर समेत अलग-अलग I/O विधियों को देखती है। पर मेरे ब्लॉग पोस्ट देखें:

http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

+0

विस्तृत ब्लॉग जानकारी के लिए धन्यवाद। 'अच्छा जवाब' बैज लें! – ouflak

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