windows - एक मेमोरी मैप की डिस्क को फ़्लश करने से कैसे रोकें, जो विंडोज़ पर खुलती है




winapi language-agnostic (2)

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

नतीजतन, मेरा मानना ​​है कि मुझे अस्थायी, हटाए जाने वाली फाइलों पर प्रक्रियाओं के बीच मेमोरी मैप्स को प्राप्त करने का एक तरीका मिल गया है, जो कि डिस्क में फ्लश नहीं किए जाते हैं जब वे बंद होते हैं।

मूल विचार में मेमोरी का नक्शा बनाना शामिल होता है जब एक अस्थायी फ़ाइल एक मैप नाम से बनाई गई है जिसे OpenFileMapping लिए कॉल में इस्तेमाल किया जा सकता है:

// build a unique map name from the file name.
auto map_name = make_map_name(file_name); 

// Open or create the mapped file.
auto mh = ::OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, map_name.c_str());
if (mh == 0 || mh == INVALID_HANDLE_VALUE) {
    // existing map could not be opened, create the file.
    auto fh = ::CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, CREATE_NEW, kTempFlags, 0);
    if (fh != INVALID_HANDLE_VALUE) {
        // set its size.
        LARGE_INTEGER newpos;
        newpos.QuadPart = desired_size;
        ::SetFilePointerEx(fh, newpos, 0, FILE_BEGIN);
        ::SetEndOfFile(fh);
        // create the map
        mh = ::CreateFileMappingA(mh, nullptr, PAGE_READWRITE, 0, 0, map_name.c_str());
        // close the file handle
        // from now on there will be no accesses using file handles.
        ::CloseHandle(fh);
    }
}

इस प्रकार, फ़ाइल हैंडल केवल तभी उपयोग की जाती है जब फाइल नई बनाई जाती है, और मानचित्र के बाद तुरंत बंद हो जाता है, जबकि मानचित्र संभाल स्वतः खुले रहता है, मैपिंग खोलने की अनुमति देने के लिए फ़ाइल हैंडल तक पहुँच की आवश्यकता के बिना। ध्यान दें कि एक दौड़ की स्थिति यहाँ मौजूद है, हमें किसी भी "वास्तविक कोड" से निपटने की आवश्यकता होगी (साथ ही साथ सभ्य त्रुटि जांच और हैंडलिंग)।

इसलिए यदि हमें एक वैध मानचित्र हैंडल मिला, तो हम दृश्य बना सकते हैं :

auto map_ptr = MapViewOfFile(mh, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (map_ptr) {
    // determine its size.
    MEMORY_BASIC_INFORMATION mbi;
    if (::VirtualQuery(map_ptr, &mbi, sizeof(MEMORY_BASIC_INFORMATION)) > 0) 
        map_size = mbi.RegionSize;
}

कब, कुछ समय बाद में एक मैप की गई फ़ाइल को बंद कर दिया गया: दृश्य को अनमाप करने से पहले नक्शा संभाल बंद करें:

if (mh == 0 || mh == INVALID_HANDLE_VALUE) {
    ::CloseHandle(mh);
    mh = INVALID_HANDLE_VALUE;
}
if (map_ptr) {
    ::UnmapViewOfFile(map_ptr);
    map_ptr = 0;
    map_size = 0;
}

और, मैंने अब तक किए गए परीक्षण के अनुसार, यह घनिष्ठ पृष्ठों को बंद करने के लिए फ्लश करने का कारण नहीं है, समस्या हल हो गई है । अच्छी तरह से आंशिक रूप से वैसे भी, फिर भी एक क्रॉस-सत्र मानचित्र नाम साझाकरण समस्या हो सकती है।

2 / टीएल अद्यतन; डॉ

क्या इन फाइलों पर खुलने वाले मेमोरी मैप्स के बंद होने के परिणामस्वरूप एक गंदे पन्ने को विंडोज़ अस्थायी डिलीट-ऑन-क्लोज़ फाइल से फंसाया जा रहा है।

हाँ। अगर आपको अपनी शुरुआती निर्माण के बाद फाइलों के साथ कुछ भी करने की ज़रूरत नहीं है और आप कुछ नामकरण सम्मेलनों को लागू करते हैं, तो इस उत्तर के अनुसार समझाया गया रणनीति संभव है।

नोट: मैं अभी भी इसके कारणों का पता लगाने में काफी दिलचस्पी ले रहा हूं कि नतीजे कैसे तैयार किए जाते हैं और निपटान / अनमापिंग के आदेश के आधार पर व्यवहार में इतना अंतर क्यों है?

मैं एक अंतर-प्रक्रिया साझा मेमोरी डेटा संरचना के लिए कुछ रणनीतियों की तलाश कर रहा हूं जो "मेमोरी खंड" की एक श्रृंखला का उपयोग करके खिड़कियों पर अपनी प्रतिबद्ध क्षमता को बढ़ाना और सिकुड़ने की अनुमति देता है

एक संभावित तरीका पृष्ठफ़ाइल का उपयोग स्मृति नामित मेमोरी मैप्स के रूप में चक मेमोरी के रूप में करना है। इस रणनीति का एक फायदा है कि SEC_RESERVE को मेमोरी एड्रेस स्पेस का एक बड़ा हिस्सा आरक्षित करने के लिए और VirtualAlloc का प्रयोग करके इसे VirtualAlloc साथ आवंटित रूप से आवंटित करने की MEM_COMMIT । नुकसान (ए) Global\ नेमस्पेस में एक साझा करने योग्य नाम का उपयोग करने की अनुमति देने के लिए SeCreateGlobalPrivilege अनुमतियों की आवश्यकता है और (बी) यह तथ्य है कि सभी प्रतिबद्ध स्मृति सिस्टम कमिट शुल्क में योगदान करती है।

इन नुकसानों को दूर करने के लिए, मैंने अस्थायी फ़ाइल बैकड मेमोरी मैप्स के उपयोग की जांच करना शुरू कर दिया। FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY का उपयोग कर बनाई गई फ़ाइलों पर Ie मेमोरी मानचित्र। FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY झंडे संयोजन यह एक अनुशंसित रणनीति प्रतीत होती है, जैसे कि इस ब्लॉग पोस्ट को मैप किए गए मेमोरी को डिस्क में फ्लश करना चाहिए (जब तक कि मेमोरी दबाव के कारण खराब मैप किए गए पेजों को दबाया जा सके)।

मैं हालांकि देख रहा हूं कि स्वामित्व प्रक्रिया से बाहर निकलने से पहले नक्शा / फाइल संभाल बंद करने से गंदी पन्नों को डिस्क पर फ्लेवर किया जाता है। ऐसा तब भी होता है जब दृश्य / फाइल हैंडल एक नहीं है जिसके माध्यम से गंदे पृष्ठ बनाए गए थे और जब इन दृश्यों / फ़ाइल हैंडल खोले गए थे, तो पृष्ठ अलग-अलग दृश्य में 'गंदे' थे।

ऐसा प्रतीत होता है कि निपटान के क्रम को बदलना (यानी पहले दृश्य को अनमाइप करना या फ़ाइल संभाल को पहले बंद करना) उस समय कुछ प्रभाव पड़ता है जब डिस्क फ्लश शुरू हो जाता है, लेकिन इस तथ्य पर नहीं कि फ्लशिंग होता है

तो मेरे सवाल हैं:

  • क्या अस्थायी फ़ाइल बैकड मेमोरी मैप्स का उपयोग करने और नक्शा / फाइल बंद होने पर गंदी पन्नों को फ्लश करने से रोकने के लिए कोई तरीका है, यह ध्यान में रखते हुए कि किसी प्रक्रिया / एकाधिक प्रक्रियाओं के भीतर कई थ्रेडों को ऐसी फ़ाइल में खुले हैंडल / दृश्य हो सकते हैं?
  • यदि नहीं, तो मनाया व्यवहार का क्या कारण हो सकता है?
  • क्या आप एक वैकल्पिक रणनीति के बारे में जानते हैं जिसे मैं अनदेखी कर सकता हूं?

कुछ अतिरिक्त जानकारी अपडेट करें : दो विभिन्न (स्वतंत्र) प्रक्रियाओं में नीचे दिए गए नमूना कोड के "arena1" और "arena2" भागों को चलाने पर, "arena1" प्रक्रिया के साथ साझा स्मृति क्षेत्र और "isna2" जो कि खुलता है उन्हें, निम्न व्यवहार नक्शे / विखंडू के लिए मनाया जाता है जिनके गंदे पन्नों हैं:

  • यदि "एनाना 1" प्रक्रिया में फ़ाइल को संभाल करने से पहले दृश्य को बंद करना है, तो इससे प्रत्येक डिस्क को (आंशिक रूप से) सिंक्रोनस प्रक्रिया (यानी यह कई सेकंड के लिए डिस्प्लेक्शन धागा को अवरुद्ध करता है) में डिस्क में फ्लश करता है, या नहीं "अखाड़ा" प्रक्रिया शुरू हुई थी।
  • अगर दृश्य से पहले फाइल संभाल बंद करने से, डिस्क फ्लश केवल उन मैप्स / भाग के लिए होते हैं जो "एरेना 1" प्रक्रिया में बंद हो जाते हैं, जबकि "एनाएना" प्रक्रिया अभी भी उन हिस्सों के लिए एक खुली संभाल होती है, और वे 'एसिंक्रोनस' , अर्थात् आवेदन धागा को अवरुद्ध नहीं करना।

नीचे दिए गए (c ++) नमूना कोड को देखें जो मेरे सिस्टम (x64, Win7) पर समस्या को पुन: प्रस्तुत करने की अनुमति देता है:

static uint64_t start_ts;
static uint64_t elapsed() {
    return ::GetTickCount64() - start_ts;
}

class PageArena {
public:
    typedef uint8_t* pointer;

    PageArena(int id, const char* base_name, size_t page_sz, size_t chunk_sz, size_t n_chunks, bool dispose_handle_first) :
        id_(id), base_name_(base_name), pg_sz_(page_sz), dispose_handle_first_(dispose_handle_first) {
        for (size_t i = 0; i < n_chunks; i++) 
            chunks_.push_back(new Chunk(i, base_name_, chunk_sz, dispose_handle_first_));
    }        
    ~PageArena() {
        for (auto i = 0; i < chunks_.size(); ++i) {
            if (chunks_[i])
                release_chunk(i);
        }
        std::cout << "[" << ::elapsed() << "] arena " << id_ << " destructed" << std::endl;
    }

    pointer alloc() {
        auto ptr = chunks_.back()->alloc(pg_sz_);
        if (!ptr) {
            chunks_.push_back(new Chunk(chunks_.size(), base_name_, chunks_.back()->capacity(), dispose_handle_first_));
            ptr = chunks_.back()->alloc(pg_sz_);
        }
        return ptr;
    }
    size_t num_chunks() {
        return chunks_.size();
    }
    void release_chunk(size_t ndx) {
        delete chunks_[ndx];
        chunks_[ndx] = nullptr;
        std::cout << "[" << ::elapsed() << "] chunk " << ndx << " released from arena " << id_ << std::endl;
    }

private:
    struct Chunk {
    public:
        Chunk(size_t ndx, const std::string& base_name, size_t size, bool dispose_handle_first) :
            map_ptr_(nullptr), tail_(nullptr), 
            handle_(INVALID_HANDLE_VALUE), size_(0), 
            dispose_handle_first_(dispose_handle_first) {

            name_ = name_for(base_name, ndx);
            if ((handle_ = create_temp_file(name_, size)) == INVALID_HANDLE_VALUE)
                handle_ = open_temp_file(name_, size);
            if (handle_ != INVALID_HANDLE_VALUE) {
                size_ = size;
                auto map_handle = ::CreateFileMappingA(handle_, nullptr, PAGE_READWRITE, 0, 0, nullptr);
                tail_ = map_ptr_ = (pointer)::MapViewOfFile(map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
                ::CloseHandle(map_handle); // no longer needed.
            }
        }
        ~Chunk() {
            if (dispose_handle_first_) {
                close_file();
                unmap_view();
            } else {
                unmap_view();
                close_file();
            }
        }
        size_t capacity() const {
            return size_;
        }
        pointer alloc(size_t sz) {
            pointer result = nullptr;
            if (tail_ + sz <= map_ptr_ + size_) {
                result = tail_;
                tail_ += sz;
            }
            return result;
        }

    private:
        static const DWORD kReadWrite = GENERIC_READ | GENERIC_WRITE;
        static const DWORD kFileSharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
        static const DWORD kTempFlags = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;

        static std::string name_for(const std::string& base_file_path, size_t ndx) {
            std::stringstream ss;
            ss << base_file_path << "." << ndx << ".chunk";
            return ss.str();
        }
        static HANDLE create_temp_file(const std::string& name, size_t& size) {
            auto h = CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, CREATE_NEW, kTempFlags, 0);
            if (h != INVALID_HANDLE_VALUE) {
                LARGE_INTEGER newpos;
                newpos.QuadPart = size;
                ::SetFilePointerEx(h, newpos, 0, FILE_BEGIN);
                ::SetEndOfFile(h);
            }
            return h;
        }
        static HANDLE open_temp_file(const std::string& name, size_t& size) {
            auto h = CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, OPEN_EXISTING, kTempFlags, 0);
            if (h != INVALID_HANDLE_VALUE) {
                LARGE_INTEGER sz;
                ::GetFileSizeEx(h, &sz);
                size = sz.QuadPart;
            }
            return h;
        }
        void close_file() {
            if (handle_ != INVALID_HANDLE_VALUE) {
                std::cout << "[" << ::elapsed() << "] " << name_ << " file handle closing" << std::endl;
                ::CloseHandle(handle_);
                std::cout << "[" << ::elapsed() << "] " << name_ << " file handle closed" << std::endl;
            }
        }
        void unmap_view() {
            if (map_ptr_) {
                std::cout << "[" << ::elapsed() << "] " << name_ << " view closing" << std::endl;
                ::UnmapViewOfFile(map_ptr_);
                std::cout << "[" << ::elapsed() << "] " << name_ << " view closed" << std::endl;
            }
        }

        HANDLE          handle_;
        std::string     name_;
        pointer         map_ptr_;
        size_t          size_;
        pointer         tail_;
        bool            dispose_handle_first_;
    };

    int id_;
    size_t pg_sz_;
    std::string base_name_;
    std::vector<Chunk*> chunks_;
    bool dispose_handle_first_;
};

static void TempFileMapping(bool dispose_handle_first) {
    const size_t chunk_size = 256 * 1024 * 1024;
    const size_t pg_size = 8192;
    const size_t n_pages = 100 * 1000;
    const char*  base_path = "data/page_pool";
    start_ts = ::GetTickCount64();

    if (dispose_handle_first)
        std::cout << "Mapping with 2 arenas and closing file handles before unmapping views." << std::endl;
    else
        std::cout << "Mapping with 2 arenas and unmapping views before closing file handles." << std::endl;
    {
        std::cout << "[" << ::elapsed() << "] " << "allocating " << n_pages << " pages through arena 1." << std::endl;
        PageArena arena1(1, base_path, pg_size, chunk_size, 1, dispose_handle_first);
        for (size_t i = 0; i < n_pages; i++) {
            auto ptr = arena1.alloc();
            memset(ptr, (i + 1) % 256, pg_size); // ensure pages are dirty.
        }
        std::cout << "[" << elapsed() << "] " << arena1.num_chunks() << " chunks created." << std::endl;
        {
            PageArena arena2(2, base_path, pg_size, chunk_size, arena1.num_chunks(), dispose_handle_first);
            std::cout << "[" << ::elapsed() << "] arena 2 loaded, going to release chunks 1 and 2 from arena 1" << std::endl;
            arena1.release_chunk(1);
            arena1.release_chunk(2);
        }
    }
}

कृपया इस सार को देखें जिसमें क्रमशः TempFileMapping(true) TempFileMapping(false) और TempFileMapping(true) चलते समय सिस्टम फ्री मेमोरी और डिस्क गतिविधि के स्क्रीन पर कब्जा करने के लिए ऊपर कोड और लिंक चलाने का उत्पादन होता है।


अगर मैं इसे सही ढंग से लेता हूं, तो कोड के Arena2 भाग को बाहर करने पर दूसरी प्रक्रिया की आवश्यकता के बिना समस्या पुन: उत्पन्न होगी। मैंने यह कोशिश की है:

  1. सुविधा के लिए निम्नानुसार मैंने base_path को संपादित किया है:

    char base_path[MAX_PATH];
    GetTempPathA(MAX_PATH, base_path);
    strcat_s(base_path, MAX_PATH, "page_pool");
  2. मैंने n_pages = 1536 * 128 संपादित किया है, जो कि आपकी स्मृति को ~ n_pages = 1536 * 128 की तुलना में 1.5 n_pages = 1536 * 128
  3. मैंने एक ही परिणाम के लिए, एक समय में TempFileMapping(false) और TempFileMapping(true) परीक्षण किया है।
  4. मैंने Arena2 साथ परीक्षण किया है और उसी परिणाम के लिए, बरकरार टिप्पणी की है।
  5. मैंने Win8.1 x64 और Win7 x64 पर परीक्षण किया है, ± 10% समान परिणामों के लिए
  6. मेरे परीक्षण में, कोड 2400ms ± 10% में चलता है, केवल 500ms ± 10% deallocating पर खर्च किया। यह स्पष्ट रूप से 1.5GB की फ्लश के लिए कम-स्पिनिंग चुप HDDs पर पर्याप्त नहीं है।

तो, सवाल यह है कि आप क्या देख रहे हैं? मैं सुझाव देता हूं कि आप:

  1. तुलना के लिए अपना समय प्रदान करें
  2. परीक्षणों के लिए एक अलग कंप्यूटर का उपयोग करें, "वही एंटीवायरस" जैसे सॉफ़्टवेयर समस्याओं को छोड़कर ध्यान दें
  3. सत्यापित करें कि आप एक RAM की कमी का सामना नहीं कर रहे हैं।
  4. फ्रीज के दौरान क्या हो रहा है यह देखने के लिए एक्सपरफ का उपयोग करें।

अपडेट मैंने अभी तक एक और Win7 x64 पर परीक्षण किया है, और समय 890 एमएमएस पूर्ण हैं, डेलोक पर 430 एमएमएस खर्च किए गए हैं। मैंने आपके परिणामों पर गौर किया है, और यह बहुत ही संदिग्ध है कि आपके मशीन पर हर बार फ्रीज में लगभग 4000 एमएस खर्च होता है। यह एक मात्र संयोग नहीं हो सकता है, मुझे विश्वास है। इसके अलावा, यह स्पष्ट नहीं है कि समस्या किसी तरह एक विशिष्ट मशीन के लिए बाध्य है जिसे आप उपयोग कर रहे हैं। तो मेरे सुझाव हैं:

  1. जैसा कि ऊपर बताया गया है, एक अन्य कंप्यूटर पर स्वयं परीक्षण करें
  2. जैसा कि ऊपर वर्णित है, XPerf का उपयोग करें, यह आपको फ्रीज के दौरान उपयोगकर्ता मोड और कर्नेल मोड में वास्तव में क्या होता है यह देखने की अनुमति देगा (मुझे वास्तव में मध्य में कुछ गैर-मानक ड्राइवर पर संदेह है)
  3. पृष्ठों की संख्या के साथ खेलते हैं और देखें कि यह फ़्रीज़ लंबाई को कैसे प्रभावित करता है।
  4. उसी कंप्यूटर पर एक अलग डिस्क ड्राइव पर फ़ाइलों को स्टोर करने का प्रयास करें जहां आपने शुरुआत में परीक्षण किया था।




temporary-files