it-swarm.dev

قراءة ملف نصي سريع في c ++

أنا أكتب حاليًا برنامجًا في c ++ يتضمن قراءة الكثير من الملفات النصية الكبيرة. يحتوي كل سطر على 400.000 سطر في الحالات القصوى 4000 حرف أو أكثر لكل سطر. فقط للاختبار ، قرأت أحد الملفات باستخدام ifstream والتنفيذ المقدم من cplusplus.com. استغرق الأمر حوالي 60 ثانية ، وهي فترة طويلة جدًا. الآن كنت أتساءل ، هل هناك طريقة مباشرة لتحسين سرعة القراءة؟

تحرير: الكود الذي أستخدمه هو أكثر أو أقل من هذا:

string tmpString;
ifstream txtFile(path);
if(txtFile.is_open())
{
    while(txtFile.good())
    {
        m_numLines++;
        getline(txtFile, tmpString);
    }
    txtFile.close();
}

تحرير 2: الملف الذي قرأته هو فقط 82 ميغابايت. قلت بشكل أساسي أنه يمكن أن يصل إلى 4000 لأنني اعتقدت أنه قد يكون من الضروري معرفته من أجل القيام بالتخزين المؤقت.

تحرير 3: شكرًا لك جميعًا على إجاباتك ، ولكن يبدو أنه لا يوجد مجال كبير للتحسين نظرًا لمشكلتي. يجب أن أستخدم readline ، لأنني أرغب في حساب عدد الأسطر. إنشاء مثيل لـ ifstream كـ ثنائي لم يجعل القراءة أسرع أيضًا. سأحاول موازاة ذلك قدر الإمكان ، وهذا يجب أن يعمل على الأقل.

تحرير 4: لذلك يبدو أن هناك بعض الأشياء التي يمكنني القيام بها. شكرا جزيلا لرؤيتكم لوضع الكثير من الوقت في هذا ، وأنا أقدر ذلك كثيرا! =)

54
Arne

التحديثات: تأكد من مراجعة التحديثات (المفاجئة) أسفل الإجابة الأولية


ملفات الذاكرة المعينة خدمتني جيدًا1:

#include <boost/iostreams/device/mapped_file.hpp> // for mmap
#include <algorithm>  // for std::find
#include <iostream>   // for std::cout
#include <cstring>

int main()
{
    boost::iostreams::mapped_file mmap("input.txt", boost::iostreams::mapped_file::readonly);
    auto f = mmap.const_data();
    auto l = f + mmap.size();

    uintmax_t m_numLines = 0;
    while (f && f!=l)
        if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}

هذا يجب أن يكون سريعا إلى حد ما.

تحديث

في حال كان هذا يساعدك على اختبار هذا النهج ، إليك إصدارباستخدام mmapمباشرة بدلاً من استخدام Boost: رؤيته مباشرة على Coliru

#include <algorithm>
#include <iostream>
#include <cstring>

// for mmap:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

const char* map_file(const char* fname, size_t& length);

int main()
{
    size_t length;
    auto f = map_file("test.cpp", length);
    auto l = f + length;

    uintmax_t m_numLines = 0;
    while (f && f!=l)
        if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}

void handle_error(const char* msg) {
    perror(msg); 
    exit(255);
}

const char* map_file(const char* fname, size_t& length)
{
    int fd = open(fname, O_RDONLY);
    if (fd == -1)
        handle_error("open");

    // obtain file size
    struct stat sb;
    if (fstat(fd, &sb) == -1)
        handle_error("fstat");

    length = sb.st_size;

    const char* addr = static_cast<const char*>(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0u));
    if (addr == MAP_FAILED)
        handle_error("mmap");

    // TODO close fd at some point in time, call munmap(...)
    return addr;
}

تحديث

آخر جزء من الأداء الذي يمكنني استخراجه من هذا وجدت من خلال النظر إلى مصدر GNU coreutils wc. لدهشتي باستخدام الكود التالي (المبسط للغاية) المقتبس من wcيعمل في حوالي 84 ٪ من الوقتمأخوذة مع ملف الذاكرة المعين أعلاه:

static uintmax_t wc(char const *fname)
{
    static const auto BUFFER_SIZE = 16*1024;
    int fd = open(fname, O_RDONLY);
    if(fd == -1)
        handle_error("open");

    /* Advise the kernel of our access pattern.  */
    posix_fadvise(fd, 0, 0, 1);  // FDADVICE_SEQUENTIAL

    char buf[BUFFER_SIZE + 1];
    uintmax_t lines = 0;

    while(size_t bytes_read = read(fd, buf, BUFFER_SIZE))
    {
        if(bytes_read == (size_t)-1)
            handle_error("read failed");
        if (!bytes_read)
            break;

        for(char *p = buf; (p = (char*) memchr(p, '\n', (buf + bytes_read) - p)); ++p)
            ++lines;
    }

    return lines;
}

1 انظر على سبيل المثال المعيار هنا: كيف يتم تحليل العوامات التي تفصل بينها مسافات في C++ بسرعة؟

68
sehe

4000 * 400،000 = 1.6 جيجابايت إذا كنت لا تعد محرك أقراص صلبة ، فمن المحتمل أن تحصل على قراءة متسلسلة تبلغ 100 ميجابايت في الثانية. هذا هو 16 ثانية فقط في I/O.

نظرًا لأنك لا توضح بالتفصيل الشفرة المحددة التي تستخدمها أو كيف تحتاج إلى تحليل هذه الملفات (هل تحتاج إلى قراءتها سطراً تلو الآخر ، هل لدى النظام الكثير من RAM) هل يمكنك قراءة الكل الملف إلى مخزن مؤقت كبيرRAM ثم تحليله؟) هناك القليل الذي يمكنك القيام به لتسريع العملية.

لن تقدم الملفات المعينة في الذاكرة أي تحسن في الأداء عند قراءة الملف بالتتابع. ربما يقدم تحليل أجزاء كبيرة يدويًا لخطوط جديدة بدلاً من استخدام "getline" تحسينًا.

تحريربعد القيام ببعض التعلم (شكراsehe). إليك حل الذاكرة المعيّن الذي سأستخدمه على الأرجح.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

int main() {
    char* fName = "big.txt";
    //
    struct stat sb;
    long cntr = 0;
    int fd, lineLen;
    char *data;
    char *line;
    // map the file
    fd = open(fName, O_RDONLY);
    fstat(fd, &sb);
    //// int pageSize;
    //// pageSize = getpagesize();
    //// data = mmap((caddr_t)0, pageSize, PROT_READ, MAP_PRIVATE, fd, pageSize);
    data = mmap((caddr_t)0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    line = data;
    // get lines
    while(cntr < sb.st_size) {
        lineLen = 0;
        line = data;
        // find the next line
        while(*data != '\n' && cntr < sb.st_size) {
            data++;
            cntr++;
            lineLen++;
        }
        /***** PROCESS LINE *****/
        // ... processLine(line, lineLen);
    }
    return 0;
}
9
Louis Ricci

نيل كيرك ، لسوء الحظ ، لا أستطيع الرد على تعليقك (سمعة غير كافية) لكنني أجريت اختبار أداء على سلسلة تيار والأداء ، وقراءة سطر ملف نصي سطرا ، هو نفسه تماما.

std::stringstream stream;
std::string line;
while(std::getline(stream, line)) {
}

هذا يستغرق 1426ms على ملف 106 ميغابايت.

std::ifstream stream;
std::string line;
while(ifstream.good()) {
    getline(stream, line);
}

هذا يستغرق 1433ms على نفس الملف.

التعليمة البرمجية التالية أسرع بدلاً من ذلك:

const int MAX_LENGTH = 524288;
char* line = new char[MAX_LENGTH];
while (iStream.getline(line, MAX_LENGTH) && strlen(line) > 0) {
}

هذا يستغرق 884ms على نفس الملف. إنه أمر صعب بعض الشيء حيث يتعين عليك تعيين الحد الأقصى لحجم المخزن المؤقت (أي الحد الأقصى للطول لكل سطر في ملف الإدخال).

3
user2434119

هل يجب عليك قراءة جميع الملفات في نفس الوقت؟ (في بداية التطبيق الخاص بك على سبيل المثال)

إذا قمت بذلك ، فكر في موازاة العملية.

في كلتا الحالتين ، ضع في اعتبارك استخدام التدفقات الثنائية ، أو غير المقيد القراءة لكتل ​​البيانات.

2
utnapistim

استخدم Random file access أو استخدم binary mode. بالنسبة للمتسلسل ، هذا كبير ولكن لا يزال يعتمد على ما تقرأ.

1
Shumail Mohyuddin

باعتباري شخصًا يتمتع بخلفية صغيرة في البرمجة التنافسية ، يمكنني أن أخبرك: على الأقل بالنسبة للأشياء البسيطة مثل تحليل عدد صحيح ، فإن التكلفة الرئيسية في C هي قفل تدفقات الملفات (والتي يتم تنفيذها افتراضيًا لعدة خيوط). استخدم إصدارات unlocked_stdio بدلاً من ذلك (fgetc_unlocked() ، fread_unlocked()). بالنسبة لـ C++ ، فإن العلم المشترك هو استخدام std::ios::sync_with_stdio(false) لكنني لا أعرف ما إذا كان الأمر سريعًا مثل unlocked_stdio.

كمرجع هنا هو رمز تحليل عدد صحيح معياري. انها الكثير أسرع من scanf ، كما قلت بشكل رئيسي بسبب عدم قفل الدفق. بالنسبة لي ، كان أسرع ما يكون من أفضل ملفات MMAP أو الإصدارات المخزنة مؤقتًا المخصصة التي استخدمتها سابقًا ، بدون دين الصيانة المجنونة.

int readint(void)
{
        int n, c;
        n = getchar_unlocked() - '0';
        while ((c = getchar_unlocked()) > ' ')
                n = 10*n + c-'0';
        return n;
}

(ملاحظة: هذا واحد يعمل فقط إذا كان هناك حرف واحد غير رقمي على وجه التحديد بين أي رقمين صحيحين).

وبالطبع تجنب تخصيص الذاكرة إذا كان ذلك ممكنا ...

1
Jo So