C #. File.ReadLines () vs File.ReadAllLines () - և ինչու՞ պետք է հոգ տանել:

Մի քանի շաբաթ առաջ ես և երկու թիմեր, որոնց հետ ես աշխատում եմ, քննարկեցինք մեծ տեքստային ֆայլերը մշակելու արդյունավետ եղանակների մասին:

Սա հարուցեց մի քանի այլ նախորդ քննարկումներ, որոնք ես ունեցել եմ նախկինում այս թեմայի վերաբերյալ, մասնավորապես C # # ում եկամտաբերության վերադարձի օգտագործման վերաբերյալ (որի մասին ես հավանաբար կխոսեմ բլոգի ապագա գրառումում): Այնպես որ, ես կարծում էի, որ լավ մարտահրավեր կլինի ցույց տալ, թե ինչպես C # կարող է արդյունավետորեն մասշտաբել, երբ խոսքը վերաբերում է տվյալների մեծ կտորների մշակմանը:

Մարտահրավեր

Այնպես որ, քննարկվող խնդիրը հետևյալն է.

  • Ենթադրենք, որ CSV- ի մեծ ֆայլ կա, ասենք, սկզբնական համար 500 ՄԲ
  • Ծրագիրը պետք է անցնի ֆայլի յուրաքանչյուր տողով, վերլուծի այն և որոշ քարտեզ ստեղծի / նվազեցնի հիմնավորված հաշվարկները

Եվ քննարկման այս պահին հարցը հետևյալն է.

Ո՞րն է կոդ կազմելու ամենաարդյունավետ միջոցը, որն ի վիճակի է իրականացնել այս նպատակը: Միաժամանակ հետևելով.
թ) նվազագույնի հասցնել օգտագործված հիշողությունը և
ii) նվազագույնի հասցնել ծրագրի կոդերի տողերը (իհարկե, ողջամիտ չափով)

Փաստարկի համար մենք կարող ենք օգտագործել StreamReader- ը, բայց դա կհանգեցնի գրել ավելի շատ կոդ, որն անհրաժեշտ է, և, փաստորեն, C #- ն արդեն ունի File.ReadAllLines () և File.ReadLines () հարմարության մեթոդներ: Այսպիսով, մենք պետք է օգտագործենք դրանք:

Showույց տվեք ինձ կոդը

Օրինակի համար եկեք համարենք մի ծրագիր, որը.

  1. Վերցնում է տեքստային ֆայլ որպես մուտքագրում, որտեղ յուրաքանչյուր տող ամբողջ թիվ է
  2. Հաշվում է ֆայլի բոլոր համարների քանակը

Այս օրինակի համար մենք կկարողանանք շրջանցել գեղեցիկ վավերացման հաղորդագրություններ .-)

C #- ում դա հնարավոր է իրականացնել հետևյալ կոդով.

var sumOfLines = File.ReadAllLines (filePath)
    . Ընտրեք (տող => int.Parse (տող))
    .Սում ()

Բավականին պարզ է, չէ:

Ի՞նչ է պատահում, երբ մենք մեծ ծրագրով կերակրում ենք այս ծրագիրը:

Եթե ​​մենք իրականացնում ենք այս ծրագիրը 100 ՄԲ ֆայլ մշակելու համար, դա այն է, ինչ մենք ստանում ենք.

  • Այս հաշվարկն ավարտելու համար 2 ԳԲ RAM- ը սպառում է հիշողությունը
  • Շատ GC (յուրաքանչյուր դեղին կետ GC- ն է)
  • 18 վայրկյան ՝ կատարումը կատարելու համար
BTW- ն, այս կոդով 500 ՄԲ ֆայլով կերակրումը առաջացրեց, որ ծրագիրը վթարի է ենթարկվել OutOfMemoryException զվարճանքի հետ, այնպես չէ՞:

Հիմա փոխարենը փորձենք File.ReadLines ()

Եկեք փոխենք ծածկագիրը ՝ օգտագործելու File.ReadLines () -ը File.ReadAllLines- ի փոխարեն () և տեսնենք, թե ինչպես է այն ընթանում.

var sumOfLines = File.ReadLines (filePath)
    . Ընտրեք (տող => int.Parse (տող))
    .Սում ()

Այն գործարկելիս մենք հիմա ստանում ենք.

  • Սպառված RAM- ի 12 ՄԲ-ը `2 Գբ-ի փոխարեն (!!)
  • Գործում է ընդամենը 1 GC
  • 10 վայրկյան ավարտելու համար ՝ 18-ի փոխարեն

Ինչու է տեղի ունենում:

TL; DR հիմնական տարբերությունն այն է, որ File.ReadAllLines- ը () կառուցում է տող [], որը պարունակում է ֆայլի յուրաքանչյուր տող, որը պահանջում է բավարար հիշողություն ՝ ամբողջ ֆայլը բեռնելու համար; որպես հակառակ File.ReadLines- ին (), որը ծրագիրը կերակրում է յուրաքանչյուր տող միանգամից, և միայն մեկ գիծ բեռնելու համար պահանջվում է միայն հիշողություն:

Մի փոքր ավելի մանրամասն ՝

File.ReadAllLines- ը () միանգամից կարդում է ամբողջ ֆայլը և վերադարձնում է տող [], որտեղ զանգվածի յուրաքանչյուր կետը համապատասխանում է ֆայլի մի տողի: Սա նշանակում է, որ ծրագրին անհրաժեշտ է նույնքան հիշողություն, որքան ֆայլի չափը `բովանդակությունը ֆայլից բեռնելու համար: Գումարած անհրաժեշտ հիշողությունը `բոլոր տողերի տարրերը ներբեռնելու համար, այնուհետև հաշվարկելու գումարը ()

Մյուս կողմից, File.ReadLines- ը () ֆայլի վրա ստեղծում է հաշվիչ, կարդում է այն տող առ տող (ըստ էության, օգտագործելով StreamReader.ReadLine ()): Սա նշանակում է, որ յուրաքանչյուր տող ընթերցվում է, վերափոխվում և ավելացվում է գծային գծի ռեժիմով մասնակի գումարին:

Եզրակացություն

Այս թեման կարող է թվալ որպես ցածր մակարդակի իրականացման մանրամասնություն, բայց իրականում այն ​​շատ կարևոր է, քանի որ այն որոշում է, թե ինչպես է ծրագիրը մասշտաբելու, երբ մեծ սերնդի հետ սնվելու դեպքում:

Ծրագրաշարավորողների համար կարևոր է, որ կարողանան կանխատեսել այսպիսի իրավիճակները, քանի որ երբեք չգիտի, թե ինչ-որ մեկը պատրաստվում է տրամադրել մեծ ներդրում, որը նախատեսված չէր մշակման փուլում:

Բացի այդ, LINQ- ը բավականաչափ ճկուն է, որպեսզի այս երկու սցենարները սահուն կերպով կարգավորի և ապահովի գերազանց արդյունավետություն, երբ օգտագործվում է այն ծածկագրով, որն ապահովում է արժեքների «հոսք»:

Սա նշանակում է, որ ամեն ինչ չէ, որ պետք է լինի ցուցակ կամ T [], ինչը ենթադրում է, որ տվյալների ամբողջ փաթեթը բեռնված է հիշողության մեջ: IEnumerable - ով օգտագործելով ՝ մենք մեր կոդն ընդհանուր ենք դարձնում ՝ օգտագործելու այն մեթոդները, որոնք ապահովում են հիշողության ամբողջ փաթեթը կամ որոնք տրամադրում են արժեքներ «հոսքային» ռեժիմով: