Ինչու՞ վճարել հզոր պրոցեսորի համար, եթե չես կարող օգտագործել այն ամբողջը:
8 միջուկով Intel i9–9900K-ը տատանվում է $450-ից $500-ի սահմաններում
Դա մեծ գումար է CPU-ի վրա ծախսելու համար:
Եվ եթե չես կարող այն օգտագործել իր առավելագույն չափով, ինչու՞ նույնիսկ ունենալ այն:
Բազմամշակումը թույլ է տալիս մեզ օգտագործել մեր պրոցեսորները իրենց առավելագույն չափով: Ծրագրերը տող առ տող գործարկելու փոխարեն մենք կարող ենք միաժամանակ գործարկել կոդի մի քանի հատված կամ կոդի նույն հատվածները մի քանի անգամ զուգահեռաբար: Եվ երբ մենք դա անում ենք, մենք կարող ենք այն բաժանել մեր պրոցեսորի մի քանի միջուկների միջև, ինչը նշանակում է, որ մենք կարող ենք շատ ավելի արագ հաշվարկել հաշվարկները:
Եվ, ի ուրախություն մեզ, Python-ն ունի ներկառուցված բազմամշակման գրադարան:
Գրադարանի հիմնական առանձնահատկությունը Process
դասն է: Երբ մենք ներկայացնում ենք Process
-ը, մենք նրան փոխանցում ենք երկու արգումենտ: target
, ֆունկցիան, որը մենք ցանկանում ենք, որ այն հաշվարկի, և args
, արգումենտները, որոնք ցանկանում ենք փոխանցել այդ թիրախային ֆունկցիային:
import multiprocessing process = multiprocessing.Process(target=func, args=(x, y, z))
Դասը օրինականացնելուց հետո այն կարող ենք սկսել .start()
մեթոդով:
process.start()
Unix-ի վրա հիմնված օպերացիոն համակարգերում, օրինակ՝ Linux, macOS և այլն, երբ գործընթացն ավարտվում է, բայց չի միացել, այն դառնում է զոմբի գործընթաց: Մենք կարող ենք դա լուծել process.join()
-ով:
Այս հոդվածում մենք կքննարկենք, թե ինչպես կարելի է օգտագործել Python-ի բազմամշակման գրադարանը, որպեսզի բարձր լուծաչափով պատկերները բեռնավորվեն անփույթ զանգվածներում, և բավական երկար ժամանակահատվածում՝ խնայել հաշվարկների ժամերը:
Բայց նախքան իրական աշխարհի օրինակում բազմապրոցեսինգի իրականացմանը անցնելը, եկեք մի փոքրիկ խաղալիք սցենար պատրաստենք՝ ցույց տալու, թե ինչպես է այն աշխատում:
Մենք կարող ենք ստեղծել որոշ պատահական տվյալներ՝ մշակելու համար.
import numpy as np fake_data = np.random.random((100, 1000000))
Այնուհետև մենք կարող ենք գրել մի գործառույթ, որը պատահական հաշվարկներ է կատարում տվյալների վրա.
if
հայտարարությունը, որը ստուգում է, թե արդյոք data_point
-ը None
է, տեխնիկապես անիմաստ է, քանի որ np.random.random
-ը երբեք չի վերադարձնի None
, բայց, անկախ նրանից, դա մեզ համար լրացուցիչ հաշվարկ է բազմամշակման միջոցով արագացնելու համար:
Մենք կարող ենք գնահատել, թե որքան ժամանակ է պահանջվում այս գործառույթը հաշվարկելու համար հետևյալով.
Իմ Intel i7–8700K 3,70 ԳՀց հաճախականությամբ պրոցեսորի վրա, այս աշխատանքը տևում է մոտավորապես 30 վայրկյան:
Դա շատ արագ չէ: Եվ կարելի է միայն պատկերացնել, թե ինչ տեսք կունենար, եթե տվյալներն ավելի մեծ լինեին, կամ հաշվարկներն ավելի թանկ պահանջեին:
Այսպիսով, հարց է առաջանում, թե ինչու է դա այդքան երկար տեւում հաշվարկելու համար:
Եկեք նայենք CPU-ի օգտագործման մատյանին, մինչ մենք աշխատում ենք սա.
Զարմանալի չէ, որ ֆունկցիան այդքան երկար է տևում, մենք ունենք մեր հաշվողական հզորության միայն մի մասը, որը հատկացված է այն գործարկելուն:
Եկեք դա շտկենք:
Նախքան սկսելը, մենք պետք է ներմուծենք ևս մեկ մոդուլ: Tenzing-ով SharedArray-ը Numpy զանգվածներ ստեղծելու մոդուլ է, որոնց հասանելի են համակարգչի տարբեր պրոցեսները:
Սովորական ndarray
-ի օգտագործումը չի աշխատի, քանի որ յուրաքանչյուր գործընթաց ունի առանձին հիշողություն և չի կարող փոխել գլոբալ զանգվածը:
Մենք կարող ենք տեղադրել SharedArray հետևյալով.
pip install SharedArray
և այնուհետև ներմուծել այն հետևյալով.
import SharedArray
SharedArray-ն ունի մի քանի հիմնական գործառույթ.
SharedArray.create(name, shape, dtype=float)
-ը ստեղծում է ընդհանուր հիշողության զանգվածSharedArray.attach(name)
փոփոխականին կցում է նախկինում ստեղծված ընդհանուր հիշողության զանգվածըSharedArray.delete(name)
-ը ջնջում է ընդհանուր հիշողության զանգվածը, սակայն գոյություն ունեցող հավելվածները մնում են վավեր
Կան բազմաթիվ այլ օգտակար հատկություններ SharedArray-ն առաջարկում է, և ես խորհուրդ կտայի կարդալ PyPI էջը փաստաթղթերի համար:
Multiprocessing ֆունկցիայի իրականացում
Բազմամշակման ֆունկցիայի ներսում մենք կարող ենք ստեղծել ընդհանուր հիշողության զանգված.
Այժմ մենք պետք է սահմանենք multiprocess_data()
-ի ներսում երեխա-գործառույթ, որը կհաշվարկի տվյալների առանձին տող:
Սա այնպես է, որ մենք կարողանանք փոխանցել multiprocessing.Process
target
ֆունկցիան, երբ մենք ստեղծենք մեր գործընթացները ավելի ուշ:
Այժմ, կեղծ տվյալների յուրաքանչյուր տողի համար մենք կարող ենք ստեղծել նոր Process
և սկսել այն.
Եվ վերջապես, բոլոր գործընթացները սկսելուց հետո մենք կարող ենք .join()
դրանք և վերադարձնել տվյալները.
Որպես ամփոփում, ահա ամբողջական գործառույթը, որն իրականացվում է բազմամշակումով.
Հիմա ժամանակն է գնահատել մեր բարելավումը:
Վազում…
…տպվում է մոտավորապես 6 վայրկյան:
Դա բավականին բարելավում է:
Եվ եթե ֆունկցիան գործարկելիս նայենք պրոցեսորի օգտագործման մատյանին…
Դա շատ ավելի լավ տեսք ունի:
Կարելի է հարցնել, եթե իմ պրոցեսորն ասում է, որ այն ունի 12 միջուկ, ինչո՞ւ է գործընթացը արագանում միայն 5-6 անգամ:
Շատ ժամանակակից պրոցեսորի ճարտարապետություններ օգտագործում են hyper-threading, ինչը նշանակում է, որ չնայած իմ ՕՀ-ին թվում է, որ ես ունեն 12 միջուկ; իրականում ես դրա միայն կեսն ունեմ, իսկ մյուս կեսը սիմուլյացված է:
Կարող եք նաև ջնջել ընդհանուր հիշողության ֆայլը հաշվարկների ավարտից հետո, որպեսզի սխալներ չառաջանան, եթե պատրաստվում եք վերագործարկել ձեր ծրագիրը:
Դուք կարող եք դա անել հետևյալով.
SharedArray.delete('data')
Multiprocessing իրական աշխարհում
Ենթադրենք, մենք Kaggle-ի մրցույթում ենք, որտեղ մենք պետք է բեռնենք բազմաթիվ պատկերներ: Հասկանալով ամպերը արբանյակային պատկերներից, ասենք. Միգուցե մենք ցանկանում ենք ստեղծել տվյալների գեներատոր ֆունկցիա, որը դուրս է հանում 300 պատկերների փաթեթ՝ ndarrray
ձևով:
Ենթադրելով, որ մենք ունենք պատկերի բոլոր տվյալները «train_images» անունով թղթապանակում, մենք կգրենք մի ֆունկցիա…
…տվյալները բեռնելու համար:
os.listdir
-ը վերադարձնում է գրացուցակի ամեն ինչի ցանկը, որը մեր դեպքում բոլոր պատկերների ֆայլերի անուններն ենcv2.imread
-ը կարդում է պատկերը և այն ավտոմատ կերպով վերածում Numpy զանգվածի: Դուք կարող եք կարդալ ավելին OpenCV-ն անիվների վրա (cv2) տեղադրելու և օգտագործելու մասին այստեղ:
Իհարկե, այս գործառույթը չի վերադարձնում պատկերների պիտակները, և այն ամեն անգամ տալիս է նույն 300 պատկերը, ուստի այն ամբողջովին գործնական չէ, բայց այդ գործառույթը հեշտ է ավելացնել:
Այնուհետև մենք կարող ենք չափել այս ֆունկցիայի հաշվարկման ժամանակը, օգտագործելով նույն մեթոդները, որոնք մենք ունեինք ավելի վաղ, որն ինձ համար մոտավորապես 12–13 վայրկյան է:
Դա լավ չէ: Եթե մենք պատրաստվում ենք դա անել գեներատորի ներսում՝ կանխատեսման մոդելի անցնելու համար, մեր ժամանակի մեծ մասը կծախսվի պատկերները բեռնելու վրա: Մենք չենք կարող բոլոր պատկերները միանգամից բեռնված պահել, քանի որ Numpy զանգվածներում միայն ~300 1400x2100 պատկեր բեռնելը պահանջում է 20–25 ԳԲ RAM:
Օգտագործելով այն հմտությունները, որոնք մենք սովորել ենք մեր խաղալիքի օրինակը բազմամշակելիս, մենք կարող ենք շատ արագացնել այս գործառույթը:
Նախ, մենք կարող ենք ստեղծել SharedArray
մեր տվյալների համար.
Հաջորդը, մենք մի փոքր այլ բան ենք անում: Քանի որ մենք չունենք պրոցեսորի անսահման միջուկներ, ավելի շատ գործընթացներ ստեղծելը, քան մեր միջուկները, ի վերջո կդանդաղեցնի գործառույթը: Սա լուծելու համար մենք կարող ենք ստեղծել workers
, որոնցից յուրաքանչյուրը կունենա որոշակի քանակությամբ պատկերներ բեռնելու համար՝ worker_amount
:
Դուք կարող եք փոխել աշխատողների թիվը՝ ձեր պրոցեսորի բնութագրերին համապատասխանելու համար:
Դրանից հետո մենք կարող ենք ստեղծել թիրախային ֆունկցիա, որը յուրաքանչյուր աշխատող կհաշվի. ֆունկցիան կունենա մեկնարկային ինդեքս՝ i
, իսկ բեռնվող պատկերների քանակը՝ n
:
Այնուհետև մենք կարող ենք նոր գործընթաց սկսել յուրաքանչյուր աշխատողի համար և նրան նշանակել worker_amount
պատկերներ՝ բեռնելու համար:
worker_amount*worker_num
-ը մեզ տալիս է այն ինդեքսը, որով պետք է սկսենք բեռնել պատկերների հաջորդ հավաքածուն:
Եվ վերջապես, մենք կարող ենք .join()
յուրաքանչյուր գործընթաց և վերադարձնել մեր ստեղծած տվյալները:
Որպես արագ ամփոփում, ահա ամբողջական գործառույթը, որը մենք պարզապես գրել ենք.
Այժմ օգտագործելով նույն մեթոդը, որը մենք օգտագործել ենք նախկինում, մենք կարող ենք ժամանակավորել մեր գործառույթը, որն ինձ համար տևում է 2 վայրկյանից մի փոքր պակաս:
Եթե մենք դա անեինք հարյուրավոր կամ նույնիսկ հազարավոր անգամներ, ապա 11 վայրկյան խնայողությունը յուրաքանչյուր բեռնման նստաշրջանի համար հավասարազոր է 18 րոպե և 3 ժամ խնայված ժամանակի100 անգամ: եւ համապատասխանաբար 1000 անգամ։
Եզրակացություն
Վերոնշյալ օրինակը արտացոլում է ոչ միայն այն, թե որքան շահավետ կարող է լինել բազմամշակումը, այլև այն, թե որքան կարևոր է մեզ համար օպտիմալացնել այն հաշվարկները, որոնք մենք առավել հաճախ ենք անում:
Անարդյունավետ օգտագործվող մեկ for
օղակը լրացուցիչ աշխատանքի վրա, բավական մեծ մասշտաբով, ընկերությանը կարժենա հարյուրավոր ժամեր և հազարավոր դոլարներ:
Նույնիսկ բազմամշակման բոլոր առավելություններով, մենք դեռ կարող ենք ավելի արագ գնալ: Ջորջ Սեյֆի այս փայլուն հոդվածը բացատրում է, թե ինչպես արագացնել տվյալների գիտությունը՝ օգտագործելով ձեր GPU-ն: Ձեր GPU-ն հատուկ նախագծված է զուգահեռ հաշվարկների համար, և երբ տրվում է բավականաչափ մեծ տվյալների բազա, էքսպոնենցիալ ավելի արագ է, քան ձեր պրոցեսորը:
Այնուամենայնիվ, GPU-ները սովորաբար ավելի դժվար են աշխատել և դուրս են այս հոդվածի շրջանակներից:
Ես նաև խորհուրդ կտայի ստուգել Manu NALEPA-ի Pandarallel գրադարանը, որը թույլ է տալիս բազմամշակում կատարել Pandas-ի տվյալների շրջանակների վրա:
Ցավոք, այս գրադարանը հասանելի է միայն Unix-ի վրա հիմնված օպերացիոն համակարգերի վրա՝ շնորհիվ իր հետին մասի:
Եվ ինչպես միշտ, այս հոդվածի կոդը հասանելի է այստեղ, իմ GitHub-ում:
Հետագա ընթերցում
Փաստաթղթեր և կայքեր.
- Բազմամշակման փաստաթղթեր՝ https://docs.python.org/3.7/library/multiprocessing.html
- SharedArray PyPi. https://pypi.org/project/SharedArray/
- Բացեք CV-ն անիվների վրա (cv2) GitHub՝ https://github.com/cancan101/opencv-python
- Pandarallel GitHub՝ https://github.com/nalepae/pandarallel
Լրացուցիչ GPU արագացված Python հոդվածներ.