لماذا قراءة خطوط من stdin أبطأ بكثير في C++ من بايثون؟




python benchmarking (7)

أنا وراء بضع سنوات هنا ، ولكن:

في "تعديل 4/5/6" من المشاركة الأصلية ، أنت تستخدم البناء:

$ /usr/bin/time cat big_file | program_to_benchmark

هذا خطأ بطريقتين مختلفتين:

  1. كنت في الواقع توقيت تنفيذ "القط" ، وليس معيارك. يُعد استخدام وحدة المعالجة المركزية 'المستخدم' و 'sys' الذي يتم عرضه بواسطة `time` هو` cat` ، وليس برنامجك المعياري. والأسوأ من ذلك أن الوقت "الحقيقي" ليس بالضرورة دقيقا. اعتمادًا على تنفيذ "cat" وخطوط الأنابيب في نظام التشغيل المحلي ، من الممكن أن يكتب "cat" مخزنًا مؤقتًا نهائيًا عملاقًا ويخرج قبل انتهاء عملية القارئ بفترة طويلة.

  2. استخدام `cat` غير ضروري وفي الواقع يأتي بنتائج عكسية ؛ كنت تضيف أجزاء متحركة. إذا كنت تستخدم نظامًا قديمًا بما فيه الكفاية (أي مع وحدة معالجة مركزية واحدة و- في أجيال معينة من أجهزة الكمبيوتر - I / O أسرع من وحدة المعالجة المركزية) - فإن مجرد حقيقة أن `cat` قيد التشغيل يمكن أن يؤدي إلى تلوين النتائج بشكل كبير. أنت أيضا تخضع لأي تخزين المدخلات والمخرجات وغيرها من معالجة `القط` قد تفعل. (من المحتمل أن يكسبك هذا جائزة "استخدام عديم الفائدة للقط" إذا كنت راندال شوارتز: https://en.wikipedia.org/wiki/Cat_(Unix)#UUOC_(Useless_Use_Of_Cat) )

أفضل بناء سيكون:

$ /usr/bin/time program_to_benchmark < big_file

في هذا البيان هو shell الذي يفتح ملف big_file ، ويمررها إلى برنامجك (حسناً ، في الواقع إلى `time` الذي يقوم بعد ذلك بتنفيذ البرنامج كعمل فرعي) بصفته واصف ملف مفتوح بالفعل. 100٪ من قراءة الملف هي مسؤولية البرنامج الذي تحاول تحديده. هذا يحصل لك قراءة حقيقية لأدائه دون مضاعفات زائفة.

سوف أذكر اثنين من "الإصلاحات" الممكنة ، ولكن في الواقع خاطئة ، والتي يمكن النظر فيها أيضًا (ولكنني "أعددها بشكل مختلف لأن هذه ليست أشياء خاطئة في المشاركة الأصلية):

ج: يمكنك "إصلاح" هذا عن طريق توقيت برنامجك فقط:

$ cat big_file | /usr/bin/time program_to_benchmark

ب أو عن طريق توقيت خط الأنابيب بأكمله:

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

هذه الأخطاء خاطئة لنفس الأسباب مثل # 2: فهي لا تزال تستخدم `cat` دون داعٍ. أذكر لهم لعدة أسباب:

  • إنهم أكثر "طبيعية" للأشخاص الذين لا يشعرون بالراحة الكاملة مع مرافق إعادة التوجيه I / O الخاصة بسلسلة POSIX

  • قد تكون هناك حالات تحتاج فيها كلمة `cat` (على سبيل المثال: يتطلب الملف المطلوب قراءته نوعًا من الامتياز للوصول إليه ، ولا تريد منح هذا الامتياز إلى البرنامج ليتم تقييمه:` sudo cat / dev / sda | / usr / bin / time my_compression_test --no-output`)

  • من الناحية العملية ، على الماكينات الحديثة ، فإن "القط" المضافة في خط الأنابيب ربما لا تكون نتيجة حقيقية

لكني أقول هذا الشيء الأخير مع بعض التردد. إذا فحصنا النتيجة الأخيرة في "تعديل 5" -

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU ...

- هذا يدعي أن `cat` استهلك 74٪ من وحدة المعالجة المركزية أثناء الاختبار ؛ والواقع أن 1.34 / 1.83 تقارب 74٪. ربما المدى من:

$ /usr/bin/time wc -l < temp_big_file

كان سيأخذ فقط المتبقية .49 ثانية! ربما لا: "القط" هنا كان عليه أن يدفع مقابل قراءة () نظام المكالمات (أو ما يعادلها) التي نقلت الملف من "القرص" (في الواقع ذاكرة التخزين المؤقت المخزن المؤقت) ، فضلا عن الأنابيب يكتب لتسليمها إلى `دبليو سي`. كان الاختبار الصحيح لا يزال يتعين عليه إجراء تلك المكالمات () ؛ فقط قد تم حفظ فقط مكالمات "الكتابة إلى الأنبوبة" و "قراءة - من - توجيه" ، ويجب أن تكون تلك رخيصة جدًا.

ومع ذلك ، أتوقع أنك ستتمكن من قياس الفرق بين ملف القطط wc -l` و `wc -l <file` وإيجاد فارق ملحوظ (نسبة من رقمين). كل من الاختبارات البطيئة سوف تدفع غرامة مماثلة في الوقت المطلق. والتي من شأنها أن تصل إلى جزء أصغر من مجموع الوقت الإجمالي.

في الحقيقة قمت ببعض الاختبارات السريعة مع ملف 1.5 غيغابايت من القمامة ، على نظام لينكس 3.13 (أوبنتو 14.04) ، والحصول على هذه النتائج (هذه هي في الواقع "أفضل 3 نتائج" ؛ بعد إعداد ذاكرة التخزين المؤقت ، بالطبع):

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)
$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)
$ time sh -c 'cat /tmp/junk | wc -l'
real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

لاحظ أن نتائج خطي الأنابيب تدعي أنها استغرقت وقتًا أكبر لوحدة المعالجة المركزية (user + sys) أكثر من الوقت الفعلي. هذا لأنني أستخدم أمر '' الوقت '' المدمج في الغلاف (Bash) ، الذي يدرك خط الأنابيب. وأنا على جهاز متعدد النواة حيث يمكن لعمليات منفصلة في خط أنابيب استخدام النوى منفصلة ، وتراكم وقت وحدة المعالجة المركزية أسرع من الوقت الحقيقي. باستخدام / usr / bin / time أرى وقت وحدة المعالجة المركزية أصغر من الوقت الفعلي - مما يدل على أنه لا يمكن إلا أن يمرر عنصر خط الأنابيب الوحيد إليه في سطر الأوامر الخاص به. أيضا ، إخراج shell يعطي ميلي ثانية بينما / usr / bin / time يعطي فقط hundreths من الثانية.

لذا على مستوى كفاءة `wc -l` ، فإن" القط "يحدث فرقًا كبيرًا: 409/283 = 1.453 أو 45.3٪ المزيد من الوقت الفعلي ، و 775/280 = 2.768 ، أو زيادة هائلة في استخدام وحدة المعالجة المركزية بنسبة 177٪! في مربع الاختبار العشوائي ، كان هناك في وقت الاختبار.

ينبغي أن أضيف أن هناك اختلافًا واحدًا آخرًا كبيرًا بين أساليب الاختبار هذه ، ولا يمكنني القول ما إذا كانت فائدة أو خطأ. عليك أن تقرر ذلك بنفسك:

عند تشغيل `القط big_file | / usr / bin / time my_program` ، يتلقى برنامجك مدخلاً من أنبوب ، وبسرعة الوتيرة المرسلة من قبل `cat` ، وفي أجزاء لا يزيد حجمها عن" cat` ".

عند تشغيل `/ usr / bin / time my_program <big_file` ، يتلقى البرنامج واصف ملف مفتوح إلى الملف الفعلي. قد يستغرق برنامجك - أو في كثير من الحالات مكتبات الإدخال / الإخراج الخاصة باللغة التي تمت كتابتها - إجراءات مختلفة عند تقديمه مع واصف ملف يشير إلى ملف عادي. قد يستخدم mmap (2) لتعيين ملف الإدخال في مساحة عنوانه ، بدلاً من استخدام مكالمات نظام قراءة واضحة (2). يمكن أن يكون لهذه الاختلافات تأثير أكبر بكثير على نتائجك المرجعية من التكلفة الصغيرة لتشغيل الملف الثنائي "cat`".

وبالطبع ، تعتبر هذه نتيجة قياسية مثيرة للاهتمام إذا كان نفس البرنامج يؤدي بشكل مختلف بشكل كبير بين الحالتين. يظهر ذلك ، في الواقع ، فإن البرنامج أو مكتبات I / O به يفعلون شيئًا مثيرًا للاهتمام ، مثل استخدام mmap (). لذلك من الناحية العملية ، قد يكون من الجيد تشغيل المعايير في كلا الاتجاهين ؛ ربما استبعاد نتيجة `القط` من قبل بعض العوامل الصغيرة إلى" التسامح "تكلفة تشغيل" القط "نفسها.

كنت أرغب في مقارنة خطوط القراءة لسلسلة المدخلات من stdin باستخدام Python و C ++ ، وقد صدمت لرؤية رمز C ++ الخاص بي تشغيل ترتيب حجم أبطأ من شفرة Python المكافئة. بما أن C ++ الخاص بي صدئ ولم أكن بعد خبير Pythonista ، أرجو أن تخبرني إذا كنت أفعل شيئًا خاطئًا أو إذا كنت أسيء فهم شيء ما.

(الإجابة عن TLDR: تتضمن العبارة: cin.sync_with_stdio(false) أو استخدم fgets بدلاً من ذلك.

نتائج TLDR: التمرير على طول الطريق لأسفل سؤالي وإلقاء نظرة على الجدول.)

كود C ++:

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

بيثون المكافئ:

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

وهنا نتائجي:

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

تحرير: يجب أن أشير إلى أنني حاولت هذا في إطار كل من Mac OS X v10.6.8 (Snow Leopard) و Linux 2.6.32 (Red Hat Linux 6.2). السابق هو ماك بوك برو ، وهذا الأخير هو خادم سمين جدا ، وليس هذا هو وثيقة الصلة للغاية.

تعديل 2: (تمت إزالة هذا التعديل ، لأنه لم يعد ساري المفعول)

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

تحرير 3:

حسنًا ، لقد جربت اقتراح JN بمحاولة امتلاك مخزن Python للخط الذي تمت قراءته: ولكن لم يحدث أي فرق في سرعة بايثون.

لقد حاولت أيضًا اقتراح JN باستخدام scanf في صفيف char بدلاً من getline في std::string . البنغو! هذا أدى إلى أداء مكافئ لكل من Python و C ++. (3333333 LPS مع بيانات المدخلات الخاصة بي ، والتي بالمناسبة هي خطوط قصيرة فقط من ثلاثة حقول لكل منها ، عادة حوالي 20 حرفًا ، مع أنه في بعض الأحيان أكثر).

الشفرة:

char input_a[512];
char input_b[32];
char input_c[512];
while(scanf("%s %s %s\n", input_a, input_b, input_c) != EOF) {
    line_count++;
};

سرعة:

$ cat test_lines | ./readline_test_cpp2
Read 10000000 lines in 3 seconds. LPS: 3333333
$ cat test_lines | ./readline_test2.py
Read 10000000 lines in 3 seconds. LPS: 3333333

(نعم ، ركضت عليه عدة مرات.) لذا ، أعتقد أنني scanf الآن scanf بدلاً من getline . ولكن ، ما زلت أشعر بالفضول إذا اعتقد الناس أن هذا الأداء من std::string / getline هو أمر نموذجي ومعقول.

تحرير 4 (كان: التعديل / الحل النهائي):

مضيفا:

cin.sync_with_stdio(false);

مباشرة فوق بلدي الأصلي أثناء حلقة فوق النتائج في التعليمات البرمجية التي يتم تشغيلها أسرع من بايثون.

مقارنة الأداء الجديدة (هذا على MacBook Pro 2011) ، باستخدام الكود الأصلي والأصل مع تعطيل المزامنة ورمز Python الأصلي ، على التوالي ، في ملف مع 20M سطر من النص. نعم ، ركضت عليه عدة مرات للقضاء على القرص التخزين الخلط.

$ /usr/bin/time cat test_lines_double | ./readline_test_cpp
       33.30 real         0.04 user         0.74 sys
Read 20000001 lines in 33 seconds. LPS: 606060
$ /usr/bin/time cat test_lines_double | ./readline_test_cpp1b
        3.79 real         0.01 user         0.50 sys
Read 20000000 lines in 4 seconds. LPS: 5000000
$ /usr/bin/time cat test_lines_double | ./readline_test.py
        6.88 real         0.01 user         0.38 sys
Read 20000000 lines in 6 seconds. LPS: 3333333

بفضلVaughn Cato لإجابته! يمكن لأي تفصيل يمكن أن يقوم به الأشخاص أو مراجع جيدة أن يشيروا إلى سبب حدوث هذه المزامنة ، وما يعنيه ، وعندما يكون مفيدًا ، وعندما يكون تعطيله أمرًا ممتعًا ، سيكون موضع تقدير كبير بالأجيال القادمة. :-)

تعديل 5 / حل أفضل:

كما اقترح Gandalf The Gray أدناه ، gets أسرع من scanf أو نهج cin غير المتزامن. تعلمت أيضا أن scanf gets على حد سواء UNSAFE ويجب عدم استخدامها بسبب احتمال تجاوز سعة المخزن المؤقت. لذلك ، كتبت هذا التكرار باستخدام fgets ، البديل الأكثر أمانًا. فيما يلي الخطوط ذات الصلة لزملائي noobs:

char input_line[MAX_LINE];
char *result;

//<snip>

while((result = fgets(input_line, MAX_LINE, stdin )) != NULL)
    line_count++;
if (ferror(stdin))
    perror("Error reading stdin.");

الآن ، فيما يلي النتائج باستخدام ملف أكبر (خطوط 100M ؛ ~ 3.4 غيغابايت) على خادم سريع مع قرص سريع للغاية ، مقارنة بين بيثون ، fgets غير المتزامنة ، fgets ، وكذلك مقارنة مع أداة wc . [تجزئة تجزئة إصدار scanf ولا أشعر أن استكشاف الأخطاء وإصلاحها.]:

$ /usr/bin/time cat temp_big_file | readline_test.py
0.03user 2.04system 0:28.06elapsed 7%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 28 seconds. LPS: 3571428

$ /usr/bin/time cat temp_big_file | readline_test_unsync_cin
0.03user 1.64system 0:08.10elapsed 20%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 8 seconds. LPS: 12500000

$ /usr/bin/time cat temp_big_file | readline_test_fgets
0.00user 0.93system 0:07.01elapsed 13%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 7 seconds. LPS: 14285714

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
100000000


Recap (lines per second):
python:         3,571,428
cin (no sync): 12,500,000
fgets:         14,285,714
wc:            54,644,808

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

لاحظ أيضًا أن المقايضة الصغيرة مع استخدام وحدة fgets char * و fgets مقابل cin غير متزامن إلى سلسلة هي أن الأخيرة يمكن أن تقرأ أي خطوط من أي طول ، بينما تتطلب السابقة إدخال قيود على عدد محدد من الأرقام. من الناحية العملية ، من المحتمل أن يكون هذا غير مشكلة في قراءة معظم ملفات الإدخال القائمة على الخطوط ، حيث يمكن تعيين المخزن المؤقت إلى قيمة كبيرة جدًا لا يمكن تجاوزها بواسطة إدخال صالح.

هذا كان تعليمي. شكرا للجميع على تعليقاتكم واقتراحاتكم.

تحرير 6:

كما اقترح JF Sebastian في التعليقات أدناه ، فإن الأداة المساعدة GNU wc تستخدم read() C واضحة read() (داخل غلاف safe.c المضمن) لقراءة قطع (من 16 كيلو بايت) في وقت واحد وحساب خطوط جديدة. إليك ما يعادل بايثون استنادًا إلى شفرة JF (فقط أظهر القصاصة ذات الصلة التي تحل محل بايثون للحلقة:

BUFFER_SIZE = 16384
count = sum(chunk.count('\n') for chunk in iter(partial(sys.stdin.read, BUFFER_SIZE), ''))

أداء هذا الإصدار سريع للغاية (على الرغم من أنه لا يزال أبطأ قليلاً من أداة C wc الأولية ، بالطبع):

$ /usr/bin/time cat temp_big_file | readline_test3.py
0.01user 1.16system 0:04.74elapsed 24%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 4.7275 seconds. LPS: 21152829

مرة أخرى ، من السخف بعض الشيء بالنسبة لي لمقارنة C ++ fgets / cin وأول رمز python من جهة إلى wc -l وآخر مقتطف Python من الجانب الآخر ، حيث أن الأخيرين لا fgets بالفعل خطوط القراءة ، ولكن مجرد الاعتماد على الخطوط الجديدة. لا يزال من المثير للاهتمام استكشاف جميع التطبيقات المختلفة والتفكير في الآثار المترتبة على الأداء. شكرًا لك مرة أخرى!

تحرير 7: إضافة مرجع صغيرة وملخص

للتأكد من اكتمالها ، فكرت في تحديث سرعة القراءة لنفس الملف على نفس المربع مع رمز C ++ الأصلي (الذي تمت مزامنته). مرة أخرى ، هذا ملف خط 100M على قرص سريع. هذا هو الجدول الكامل الآن:

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808

التعليمة البرمجية التالية كانت أسرع بالنسبة لي من الشفرة الأخرى المنشورة هنا حتى الآن: (ملف Visual Studio 2013 ، 64 بت ، 500 ميجابايت مع طول الخط بشكل موحد في [0 ، 1000)).

const int buffer_size = 500 * 1024;  // Too large/small buffer is not good.
std::vector<char> buffer(buffer_size);
int size;
while ((size = fread(buffer.data(), sizeof(char), buffer_size, stdin)) > 0) {
    line_count += count_if(buffer.begin(), buffer.begin() + size, [](char ch) { return ch == '\n'; });
}

يدق كل محاولات بايثون أكثر من عامل 2.


بالمناسبة ، فإن سبب عد السطر لإصدار C ++ هو واحد أكبر من العدد الخاص بإصدار Python هو أن علامة eof تتحقق فقط عند محاولة القراءة للقراءة خارج نطاق eof. لذا فإن الحلقة الصحيحة ستكون:

while (cin) {
    getline(cin, input_line);

    if (!cin.eof())
        line_count++;
};

بشكل افتراضي ، تتم مزامنة cin بـ stdio ، مما يؤدي إلى تجنب أي تخزين مؤقت للتخزين. إذا أضفت هذا إلى الجزء الرئيسي من الصفحة الرئيسية ، فيجب أن تشاهد أداء أفضل بكثير:

std::ios_base::sync_with_stdio(false);

عادة ، عندما يتم تخزين دفق إدخال مؤقت ، بدلاً من قراءة حرف واحد في كل مرة ، سيتم قراءة الدفق في أجزاء أكبر. هذا يقلل من عدد مكالمات النظام ، والتي عادة ما تكون مكلفة نسبيا. ومع ذلك ، نظرًا لأن stdio و iostreams المستند إلى FILE* غالبًا ما يكون لهما تطبيقات منفصلة وبالتالي مخزونات منفصلة ، فقد يؤدي ذلك إلى حدوث مشكلة إذا تم استخدام كليهما معًا. فمثلا:

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);

إذا تمت قراءة المزيد من المدخلات cin من العدد المطلوب فعليًا ، فإن قيمة العدد الصحيح الثاني لن تكون متاحة لوظيفة scanf ، التي تحتوي على مخزن مؤقت مستقل خاص بها. هذا من شأنه أن يؤدي إلى نتائج غير متوقعة.

لتجنب هذا ، بشكل افتراضي ، تتم مزامنة تدفقات مع stdio . إحدى الطرق الشائعة لتحقيق ذلك هي أن يقرأ cin كل حرف واحد في كل مرة حسب الحاجة باستخدام وظائف stdio . لسوء الحظ ، هذا يقدم الكثير من النفقات العامة. بالنسبة إلى كميات صغيرة من المدخلات ، هذه ليست مشكلة كبيرة ، ولكن عندما تقرأ الملايين من الأسطر ، تكون عقوبة الأداء مهمة.

لحسن الحظ ، قرر مصممي المكتبات أنه يجب أيضًا تمكين هذه الميزة للحصول على أداء محسن إذا كنت تعرف ما كنت تفعله ، لذا فقد قدموا طريقة sync_with_stdio .


فقط من الفضول لقد dtruss/strace نظرة على ما يحدث تحت غطاء محرك السيارة ، واستخدمت dtruss/strace على كل اختبار.

C ++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050

syscalls sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958

الثعبان

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402

syscalls sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29

في المثال الثاني (مع scanf ()) السبب في أن هذا لا يزال أبطأ قد يكون بسبب scanf ("٪ s") يوزع سلسلة ويبحث عن أي حرف مساحة (مسافة ، علامة تبويب ، سطر جديد).

أيضا ، نعم ، CPython يفعل بعض التخزين المؤقت لتجنب القراءة harddisk.


يمكن أن يكون getline ، مشغلات الدفق ، scanf ، مناسبًا إذا كنت لا تهتم وقت تحميل الملف أو إذا كنت تقوم بتحميل ملفات نصية صغيرة. ولكن ، إذا كان الأداء شيئًا يهمك ، فيجب أن تقوم فقط بتخزين الملف بأكمله في الذاكرة (على افتراض أنه سيكون مناسبًا).

إليك مثال على ذلك:

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

إذا كنت تريد ، يمكنك لف دفق حول هذا المخزن المؤقت للوصول أكثر ملاءمة مثل هذا:

std::istrstream header(&buffer[0], length);

أيضًا ، إذا كنت تتحكم في الملف ، ففكر في استخدام تنسيق بيانات ثنائي مسطح بدلاً من النص. إنه أكثر موثوقية للقراءة والكتابة لأنك لست مضطرًا للتعامل مع جميع نقاط الغموض الخاصة بالمسافة البيضاء. انها أيضا أصغر وأسرع بكثير لتحليل.





getline